diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a4e3137 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[env] +KERNELX_HOME = { value = ".", relative = true } +ARCH = { value = "riscv" } +ARCH_BITS = { value = "64" } + +[build] +target = "riscv64gc-unknown-none-elf" \ No newline at end of file diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..df3a88b --- /dev/null +++ b/.gdbinit @@ -0,0 +1,6 @@ +# file ./target/riscv64gc-unknown-none-elf/debug/kernelx +add-symbol-file ./build/riscv64/vmkernelx -s .init 0x80200000 +target remote 127.0.0.1:1234 +break *0x80200000 +layout asm +c \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6127b3..fb4b769 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,23 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# clang +compile_commands.json + +*.dts +*.dtb +kernel.asm + +.cache + +/sdcard-* +/alpine-* + +# Kconfig generated files +.config* + +# Added by cargo + +/target +/build \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..84af3f9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "KernelX", + "type": "cppdbg", + "request": "launch", + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/build/qemu-virt-riscv64/vmkernelx", + "MIMode": "gdb", + "miDebuggerPath": "gdb-multiarch", + "miDebuggerServerAddress": "127.0.0.1:1234", + "miDebuggerArgs": "-ex 'break *0x80200000'", + "externalConsole": true, + "stopAtEntry": true + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8900f8f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "rust-analyzer.check.extraArgs": [ + "--target", + "riscv64gc-unknown-none-elf", + ], + "rust-analyzer.cargo.features": [ + "log-trace", + "swap-memory", + ], + "rust-analyzer.cargo.extraArgs": [ + "--target", + "riscv64gc-unknown-none-elf", + ], + "rust-analyzer.server.extraEnv": { + "KERNELX_HOME": "${workspaceFolder}", + "ARCH": "riscv", + "ARCH_BITS": "64" + }, + "rust-analyzer.trace.server": "verbose", + "cmake.ignoreCMakeListsMissing": true +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5b53b79 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,472 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.110", +] + +[[package]] +name = "bitfield-struct" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de05f8756f1c68937349406d4632ae96ae35901019b5e59c508d9c38c64715fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "buddy_system_allocator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44d578cadd17312c75e7d0ef489361f160ace58f7139aa32001fee1a51b89b5" +dependencies = [ + "spin", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "device_tree_parser" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5879ed67a77cff200a337cd795e65778f90c5395acfff5ddc3542c5c977a5161" + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ext4_rs" +version = "1.3.2" +dependencies = [ + "bitflags", + "log", +] + +[[package]] +name = "fdt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "kernelx" +version = "0.1.0" +dependencies = [ + "bitfield-struct", + "bitflags", + "bitvec", + "buddy_system_allocator", + "cc", + "cfg-if", + "device_tree_parser", + "downcast-rs", + "ext4_rs", + "fdt", + "lwext4_rust", + "num_enum", + "spin", + "virtio-drivers", + "visionfive2-sd", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lwext4_rust" +version = "0.2.0" +dependencies = [ + "bindgen", + "log", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "preprint" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "067d7939a17011d73ee0f868eb26b569680437d379583e5f97c18ca570b4a32f" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.110", +] + +[[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 = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "virtio-drivers" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a39747311dabb3d37807037ed1c3c38d39f99198d091b5b79ecd5c8d82f799" +dependencies = [ + "bitflags", + "enumn", + "log", + "zerocopy", +] + +[[package]] +name = "visionfive2-sd" +version = "0.1.0" +source = "git+https://github.com/os-module/visionfive2-sd.git?rev=af75139#af751394eb81d2064786135656df3a6c0dd74485" +dependencies = [ + "bitfield-struct", + "log", + "preprint", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..def2e3e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "kernelx" +version = "0.1.0" +edition = "2024" +build = "scripts/build.rs" + +[features] +default = ["deadlock-detect", "log-info", "no-smp"] + +log-warn = [] +log-info = ["log-warn"] +log-debug = ["log-info"] +log-trace = ["log-debug"] +log-trace-syscall = [] +warn-unimplemented-syscall = [] + +deadlock-detect = [] +no-smp = [] +swap-memory = [] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[dependencies] +buddy_system_allocator = { version = "0.9", default-features = false, features = ["use_spin"] } +spin = "0.9" +bitflags = "2.0" +virtio-drivers = "0.7.5" +cfg-if = "1.0" +num_enum = { version = "0.5", default-features = false} +downcast-rs = { version = "2.0.1", default-features = false , features = ["sync"]} +device_tree_parser = "0.4.0" +fdt = "0.1.5" +bitfield-struct = "0.8.0" +bitvec = { version = "1.0.1" , default-features = false, features = ["alloc"] } +visionfive2-sd = { git = "https://github.com/os-module/visionfive2-sd.git", rev = "af75139" } +ext4_rs = { path = "./lib/ext4_rs"} +lwext4_rust = { path = "./lib/lwext4_rust" } + +[build-dependencies] +cc = "1.0" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e0f0ef3 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +KERNEL = build/$(PLATFORM)/vmkernelx +KERNEL_IMAGE = build/$(PLATFORM)/Image + +all: kernel + +include config/config.mk + +init: + @ git submodule init + @ git submodule update --remote + # @ make -C ./lib/opensbi CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic FW_JUMP=y FW_JUMP_ADDR=0x80200000 + +kernel: + @ $(MAKE) -f build.mk kernel $(KERNEL_CONFIG) + +vdso: + @ make -f build.mk vdso $(KERNEL_CONFIG) + +clib: + @ make -f build.mk clib $(KERNEL_CONFIG) + +check: + @ make -f build.mk check $(KERNEL_CONFIG) + +run: kernel + @ make -f scripts/qemu.mk qemu-run $(QEMU_ARGS) + +clean: + @ make -f build.mk clean + +qemu-dts: + @ make -f scripts/qemu.mk qemu-dts $(QEMU_ARGS) + +gdb: kernel + @ make -f scripts/qemu.mk qemu-gdb $(QEMU_ARGS) + +objdump: kernel + @ $(CROSS_COMPILE)objdump -d $(KERNEL) > kernel.asm + @ echo "Generated kernel.asm" + +package: kernel + @ KERNEL_IMAGE=$(KERNEL_IMAGE) IMAGE=$(IMAGE) scripts/package.sh + @ echo "Packaged image: $(IMAGE)" + +count: + @ find src clib/src -type f -name "*.rs" -o -name "*.c" -o -name "*.h" -o -name "*.S" | xargs wc -l + +.PHONY: all init run gdb clean count check menuconfig objdump kernel vdso clib diff --git a/README.md b/README.md index 61216ee..9ba9314 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# KXOS \ No newline at end of file +# KernelX \ No newline at end of file diff --git a/build.mk b/build.mk new file mode 100644 index 0000000..df26310 --- /dev/null +++ b/build.mk @@ -0,0 +1,102 @@ +COMPILE_MODE ?= debug + +KERNELX_HOME := $(strip $(patsubst %/, %, $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))) + +BUILD = $(abspath build/$(ARCH)$(ARCH_BITS)) +KERNEL_VM = $(BUILD)/vmkernelx +KERNEL_IMAGE = $(BUILD)/Image + +CLIB = clib/build/$(ARCH)$(ARCH_BITS)/libkernelx_clib.a +VDSO = vdso/build/$(ARCH)$(ARCH_BITS)/vdso.o + +BUILD_ENV = \ + PLATFORM=$(PLATFORM) \ + ARCH=$(ARCH) \ + ARCH_BITS=$(ARCH_BITS) \ + CROSS_COMPILE=$(CROSS_COMPILE) \ + KERNELX_INITPATH=$(INITPATH) \ + KERNELX_INITCWD=$(INITCWD) \ + KERNELX_RELEASE=$(KERNELX_RELEASE) \ + KERNELX_HOME=$(KERNELX_HOME) + +RUST_TARGET = riscv64gc-unknown-none-elf +RUST_TARGET_DIR ?= $(abspath target/$(RUST_TARGET)/$(COMPILE_MODE)) +RUST_KERNEL ?= $(RUST_TARGET_DIR)/kernelx +RUST_DEPENDENCIES = $(RUST_TARGET_DIR)/kernelx.d + +# ------ Configure log level features using a more elegant lookup ------ # +LOG_FEATURES_trace = log-trace +LOG_FEATURES_debug = log-debug +LOG_FEATURES_info = log-info +LOG_FEATURES_warn = log-warn + +ifeq ($(LOG_LEVEL),) +RUST_FEATURES += log-info +else ifneq ($(LOG_FEATURES_$(LOG_LEVEL)),) +RUST_FEATURES += $(LOG_FEATURES_$(LOG_LEVEL)) +else +$(warning Invalid LOG_LEVEL: $(LOG_LEVEL). Valid values: trace, debug, info, warn) +endif +# ------ Configure log level features using a more elegant lookup ------ # + +ifeq ($(CONFIG_LOG_SYSCALL),y) +RUST_FEATURES += log-trace-syscall +endif + +ifeq ($(CONFIG_ENABLE_SWAP_MEMORY),y) +RUST_FEATURES += swap-memory +endif + +ifeq ($(CONFIG_WARN_UNIMPLEMENTED_SYSCALL),y) +RUST_FEATURES += warn-unimplemented-syscall +endif + +RUST_FEATURES += no-smp + +CARGO_FLAGS += --target $(RUST_TARGET) +CARGO_FLAGS += --features "$(RUST_FEATURES)" +ifeq ($(COMPILE_MODE),release) +CARGO_FLAGS += --release +endif + +all: kernel + +kernel: $(RUST_KERNEL) + @ mkdir -p $(BUILD) + @ cp $(RUST_KERNEL) $(KERNEL_VM) + @ $(CROSS_COMPILE)objcopy -O binary $(RUST_KERNEL) $(KERNEL_IMAGE) + +$(KERNEL_VM): $(RUST_KERNEL) + @ mkdir -p $(BUILD) + @ cp $(RUST_KERNEL) $(KERNEL_VM) + +$(KERNEL_IMAGE): $(RUST_KERNEL) + @ mkdir -p $(BUILD) + @ $(CROSS_COMPILE)objcopy -O binary $(RUST_KERNEL) $(KERNEL_IMAGE) + +clib: $(CLIB) + +$(CLIB): + @ echo $(KERNELX_HOME) + @ $(BUILD_ENV) make -C clib all + +vdso: $(VDSO) + +$(VDSO): + @ $(BUILD_ENV) make -C vdso all + +$(RUST_KERNEL): $(CLIB) $(VDSO) + $(BUILD_ENV) cargo build $(CARGO_FLAGS) + +check: + @ $(BUILD_ENV) cargo check $(CARGO_FLAGS) + +objcopy: + @ $(CROSS_COMPILE)objcopy -O binary $(KERNEL) build/$(PLATFORM)/kernel.bin + @ echo "Generated kernel.bin" + +clean: + @ $(BUILD_ENV) make -C clib clean + @ $(BUILD_ENV) cargo clean + +.PHONY: all $(CLIB) $(VDSO) $(RUST_KERNEL) diff --git a/clib/.gitignore b/clib/.gitignore new file mode 100644 index 0000000..d73e005 --- /dev/null +++ b/clib/.gitignore @@ -0,0 +1,2 @@ +.cache +build \ No newline at end of file diff --git a/clib/CMakeLists.txt b/clib/CMakeLists.txt new file mode 100644 index 0000000..3c04a99 --- /dev/null +++ b/clib/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.16) + +include(${CMAKE_SOURCE_DIR}/cmake/filelist.cmake) + +set(CMAKE_ASM_COMPILER clang) +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +project(kernelx_clib C CXX ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(${CMAKE_SOURCE_DIR}/lib/tlsf) +add_subdirectory(${CMAKE_SOURCE_DIR}/lib/libfdt) + +add_library(kernelx_clib STATIC ${SRCS}) +target_include_directories(kernelx_clib PUBLIC ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(kernelx_clib PRIVATE tlsf libfdt) + +include(${KERNELX_HOME}/scripts/cmake/env.cmake) + +set(COMMON_FLAGS -Wall -Wextra -fno-common -fno-builtin -nostdlib -ffreestanding) +list(APPEND COMMON_FLAGS -g -ggdb -Og) +list(APPEND COMMON_FLAGS ${ARCH_COMMON_FLAGS_LIST}) + +message("COMMON_FLAGS: ${COMMON_FLAGS}") + +target_compile_options(tlsf PRIVATE ${COMMON_FLAGS}) +target_compile_options(libfdt PRIVATE ${COMMON_FLAGS}) +target_compile_options(kernelx_clib PRIVATE ${COMMON_FLAGS}) diff --git a/clib/Makefile b/clib/Makefile new file mode 100644 index 0000000..8eb42c5 --- /dev/null +++ b/clib/Makefile @@ -0,0 +1,25 @@ +ARCH ?= riscv +PLATFORM ?= qemu-virt-riscv64 +TARGET ?= riscv64-linux-gnu + +TOOLCHAINE_FILE = cmake/toolchain/$(PLATFORM).cmake + +CMAKE_DEFINITIONS += ARCH=$(ARCH) +CMAKE_DEFINITIONS += ARCH_BITS=$(ARCH_BITS) +CMAKE_DEFINITIONS += PLATFORM=$(PLATFORM) +CMAKE_DEFINITIONS += TARGET=${TARGET} +CMAKE_DEFINITIONS += CMAKE_BUILD_TYPE=Release +CMAKE_DEFINITIONS += CMAKE_TOOLCHAIN_FILE=$(TOOLCHAINE_FILE) +CMAKE_DEFINITIONS += KERNELX_HOME=$(KERNELX_HOME) +CMAKE_DEFINITIONS += CMAKE_EXPORT_COMPILE_COMMANDS=1 +CMAKE_FLAGS += $(addprefix -D, $(CMAKE_DEFINITIONS)) + +BUILD_DIR = build/$(ARCH)$(ARCH_BITS) + +all: + @ mkdir -p $(BUILD_DIR) + @ cmake -B $(BUILD_DIR) $(CMAKE_FLAGS) + @ cmake --build $(BUILD_DIR) + +clean: + @ cmake --build $(BUILD_DIR) --target clean diff --git a/clib/cmake/filelist.cmake b/clib/cmake/filelist.cmake new file mode 100644 index 0000000..1188b4e --- /dev/null +++ b/clib/cmake/filelist.cmake @@ -0,0 +1,7 @@ +file( + GLOB_RECURSE SRCS + ${CMAKE_SOURCE_DIR}/src/klib/*.* + ${CMAKE_SOURCE_DIR}/src/fs/*.* + ${CMAKE_SOURCE_DIR}/src/arch/${ARCH}/*.* + ${CMAKE_SOURCE_DIR}/src/platform/${PLATFORM}/*.* +) diff --git a/clib/cmake/toolchain/qemu-virt-riscv64.cmake b/clib/cmake/toolchain/qemu-virt-riscv64.cmake new file mode 100644 index 0000000..85e123a --- /dev/null +++ b/clib/cmake/toolchain/qemu-virt-riscv64.cmake @@ -0,0 +1,7 @@ +set(CMAKE_ASM_COMPILER clang) +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +set(COMMON_FLAGS -Wall -Wextra -Werror -fno-common -fno-builtin -nostdlib -ffreestanding) +set(COMMON_FLAGS ${COMMON_FLAGS} --target=${TARGET}) +set(COMMON_FLAGS ${COMMON_FLAGS} -mabi=lp64d -march=rv64gc) diff --git a/clib/include/arch/riscv/asm.h b/clib/include/arch/riscv/asm.h new file mode 100644 index 0000000..f18bf45 --- /dev/null +++ b/clib/include/arch/riscv/asm.h @@ -0,0 +1,8 @@ +#ifndef __KERNELX_ARCH_RISCV_ASM_H__ +#define __KERNELX_ARCH_RISCV_ASM_H__ + +#ifndef __riscv_xlen + #error "__riscv_xlen not defined" +#endif + +#endif // __KERNELX_ARCH_RISCV_ASM_H__ diff --git a/clib/include/arch/riscv/entry.h b/clib/include/arch/riscv/entry.h new file mode 100644 index 0000000..fbbacb0 --- /dev/null +++ b/clib/include/arch/riscv/entry.h @@ -0,0 +1,21 @@ +#ifndef __ARCH_RISCV_ENTEY_H__ +#define __ARCH_RISCV_ENTEY_H__ + +#include + +#define __init_text __attribute__((section(".text.init"))) +#define __init_data __attribute__((section(".data.init"))) + +#define PGSIZE 4096 + +uintptr_t __riscv_load_fdt(const void *fdt); +uintptr_t __riscv_map_kaddr(uintptr_t kaddr_offset, uintptr_t memory_top); + +uintptr_t *__riscv_init_load_kernel_end(); +uintptr_t *__riscv_init_load_kpgtable_root(); +void **__riscv_init_load_copied_fdt(); +uintptr_t *__riscv_init_load_kaddr_offset(); + +void __riscv_init_die(const char *reason); + +#endif // __ARCH_RISCV_ENTRY_H__ diff --git a/clib/include/klib/klib.h b/clib/include/klib/klib.h new file mode 100644 index 0000000..0fcdecb --- /dev/null +++ b/clib/include/klib/klib.h @@ -0,0 +1,16 @@ +#ifndef __KLIB_KLIB_H__ +#define __KLIB_KLIB_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void klib_init(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/clib/include/klib/stdlib.h b/clib/include/klib/stdlib.h new file mode 100644 index 0000000..b344a9a --- /dev/null +++ b/clib/include/klib/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __KLIB_STDIO_H__ +#define __KLIB_STDIO_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void *malloc(size_t size); +void free(void *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/clib/lib/libfdt/.gitignore b/clib/lib/libfdt/.gitignore new file mode 100644 index 0000000..fed4603 --- /dev/null +++ b/clib/lib/libfdt/.gitignore @@ -0,0 +1 @@ +libfdt.so.1 diff --git a/clib/lib/libfdt/CMakeLists.txt b/clib/lib/libfdt/CMakeLists.txt new file mode 100644 index 0000000..44ed913 --- /dev/null +++ b/clib/lib/libfdt/CMakeLists.txt @@ -0,0 +1,17 @@ +file( + GLOB_RECURSE LIBFDT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/*.c +) + +add_library(libfdt OBJECT ${LIBFDT_SOURCES}) +add_library(libfdt::libfdt ALIAS libfdt) + +target_include_directories(libfdt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(libfdt PRIVATE + KERNEL_BUILD=1 +) + +set_target_properties(libfdt PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib +) diff --git a/clib/lib/libfdt/Makefile.libfdt b/clib/lib/libfdt/Makefile.libfdt new file mode 100644 index 0000000..b763b2e --- /dev/null +++ b/clib/lib/libfdt/Makefile.libfdt @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +# Makefile.libfdt +# +# This is not a complete Makefile of itself. Instead, it is designed to +# be easily embeddable into other systems of Makefiles. +# + +LIBFDT_so = libfdt.$(SHAREDLIB_EXT) +LIBFDT_soname = libfdt.$(SHAREDLIB_EXT).1 +LIBFDT_INCLUDES = fdt.h libfdt.h libfdt_env.h +LIBFDT_VERSION = version.lds +LIBFDT_SRCS = fdt.c fdt_ro.c fdt_wip.c fdt_sw.c fdt_rw.c fdt_strerror.c fdt_empty_tree.c \ + fdt_addresses.c fdt_overlay.c fdt_check.c +LIBFDT_OBJS = $(LIBFDT_SRCS:%.c=%.o) +LIBFDT_LIB = libfdt.$(SHAREDLIB_EXT).$(DTC_VERSION) + +libfdt_clean: + @$(VECHO) CLEAN "(libfdt)" + rm -f $(STD_CLEANFILES:%=$(LIBFDT_dir)/%) + rm -f $(LIBFDT_dir)/$(LIBFDT_so) + rm -f $(LIBFDT_dir)/$(LIBFDT_soname) + rm -f $(LIBFDT_dir)/$(LIBFDT_LIB) diff --git a/clib/lib/libfdt/TODO b/clib/lib/libfdt/TODO new file mode 100644 index 0000000..288437e --- /dev/null +++ b/clib/lib/libfdt/TODO @@ -0,0 +1,3 @@ +- Tree traversal functions +- Graft function +- Complete libfdt.h documenting comments diff --git a/clib/lib/libfdt/fdt.c b/clib/lib/libfdt/fdt.c new file mode 100644 index 0000000..20c6415 --- /dev/null +++ b/clib/lib/libfdt/fdt.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +/* + * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks + * that the given buffer contains what appears to be a flattened + * device tree with sane information in its header. + */ +int32_t fdt_ro_probe_(const void *fdt) +{ + uint32_t totalsize = fdt_totalsize(fdt); + + if (can_assume(VALID_DTB)) + return totalsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) == FDT_MAGIC) { + /* Complete tree */ + if (!can_assume(LATEST)) { + if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + if (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + /* Unfinished sequential-write blob */ + if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0) + return -FDT_ERR_BADSTATE; + } else { + return -FDT_ERR_BADMAGIC; + } + + if (totalsize < INT32_MAX) + return totalsize; + else + return -FDT_ERR_TRUNCATED; +} + +static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) +{ + return (off >= hdrsize) && (off <= totalsize); +} + +static int check_block_(uint32_t hdrsize, uint32_t totalsize, + uint32_t base, uint32_t size) +{ + if (!check_off_(hdrsize, totalsize, base)) + return 0; /* block start out of bounds */ + if ((base + size) < base) + return 0; /* overflow */ + if (!check_off_(hdrsize, totalsize, base + size)) + return 0; /* block end out of bounds */ + return 1; +} + +size_t fdt_header_size_(uint32_t version) +{ + if (version <= 1) + return FDT_V1_SIZE; + else if (version <= 2) + return FDT_V2_SIZE; + else if (version <= 3) + return FDT_V3_SIZE; + else if (version <= 16) + return FDT_V16_SIZE; + else + return FDT_V17_SIZE; +} + +size_t fdt_header_size(const void *fdt) +{ + return can_assume(LATEST) ? FDT_V17_SIZE : + fdt_header_size_(fdt_version(fdt)); +} + +int fdt_check_header(const void *fdt) +{ + size_t hdrsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) != FDT_MAGIC) + return -FDT_ERR_BADMAGIC; + if (!can_assume(LATEST)) { + if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + || (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION)) + return -FDT_ERR_BADVERSION; + if (fdt_version(fdt) < fdt_last_comp_version(fdt)) + return -FDT_ERR_BADVERSION; + } + hdrsize = fdt_header_size(fdt); + if (!can_assume(VALID_DTB)) { + if ((fdt_totalsize(fdt) < hdrsize) + || (fdt_totalsize(fdt) > INT_MAX)) + return -FDT_ERR_TRUNCATED; + + /* Bounds check memrsv block */ + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_mem_rsvmap(fdt))) + return -FDT_ERR_TRUNCATED; + + /* Bounds check structure block */ + if (!can_assume(LATEST) && fdt_version(fdt) < 17) { + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } else { + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } + + /* Bounds check strings block */ + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_strings(fdt), + fdt_size_dt_strings(fdt))) + return -FDT_ERR_TRUNCATED; + } + + return 0; +} + +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) +{ + unsigned int uoffset = offset; + unsigned int absoffset = offset + fdt_off_dt_struct(fdt); + + if (offset < 0) + return NULL; + + if (!can_assume(VALID_INPUT)) + if ((absoffset < uoffset) + || ((absoffset + len) < absoffset) + || (absoffset + len) > fdt_totalsize(fdt)) + return NULL; + + if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) + if (((uoffset + len) < uoffset) + || ((offset + len) > fdt_size_dt_struct(fdt))) + return NULL; + + return fdt_offset_ptr_(fdt, offset); +} + +uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) +{ + const fdt32_t *tagp, *lenp; + uint32_t tag, len, sum; + int offset = startoffset; + const char *p; + + *nextoffset = -FDT_ERR_TRUNCATED; + tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); + if (!can_assume(VALID_DTB) && !tagp) + return FDT_END; /* premature end */ + tag = fdt32_to_cpu(*tagp); + offset += FDT_TAGSIZE; + + *nextoffset = -FDT_ERR_BADSTRUCTURE; + switch (tag) { + case FDT_BEGIN_NODE: + /* skip name */ + do { + p = fdt_offset_ptr(fdt, offset++, 1); + } while (p && (*p != '\0')); + if (!can_assume(VALID_DTB) && !p) + return FDT_END; /* premature end */ + break; + + case FDT_PROP: + lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); + if (!can_assume(VALID_DTB) && !lenp) + return FDT_END; /* premature end */ + + len = fdt32_to_cpu(*lenp); + sum = len + offset; + if (!can_assume(VALID_DTB) && + (INT_MAX <= sum || sum < (uint32_t) offset)) + return FDT_END; /* premature end */ + + /* skip-name offset, length and value */ + offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len; + + if (!can_assume(LATEST) && + fdt_version(fdt) < 0x10 && len >= 8 && + ((offset - len) % 8) != 0) + offset += 4; + break; + + case FDT_END: + case FDT_END_NODE: + case FDT_NOP: + break; + + default: + return FDT_END; + } + + if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) + return FDT_END; /* premature end */ + + *nextoffset = FDT_TAGALIGN(offset); + return tag; +} + +int fdt_check_node_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_check_prop_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_next_node(const void *fdt, int offset, int *depth) +{ + int nextoffset = 0; + uint32_t tag; + + if (offset >= 0) + if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) + return nextoffset; + + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_PROP: + case FDT_NOP: + break; + + case FDT_BEGIN_NODE: + if (depth) + (*depth)++; + break; + + case FDT_END_NODE: + if (depth && ((--(*depth)) < 0)) + return nextoffset; + break; + + case FDT_END: + if ((nextoffset >= 0) + || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) + return -FDT_ERR_NOTFOUND; + else + return nextoffset; + } + } while (tag != FDT_BEGIN_NODE); + + return offset; +} + +int fdt_first_subnode(const void *fdt, int offset) +{ + int depth = 0; + + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth != 1) + return -FDT_ERR_NOTFOUND; + + return offset; +} + +int fdt_next_subnode(const void *fdt, int offset) +{ + int depth = 1; + + /* + * With respect to the parent, the depth of the next subnode will be + * the same as the last. + */ + do { + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth < 1) + return -FDT_ERR_NOTFOUND; + } while (depth > 1); + + return offset; +} + +const char *fdt_find_string_(const char *strtab, int tabsize, const char *s) +{ + int len = strlen(s) + 1; + const char *last = strtab + tabsize - len; + const char *p; + + for (p = strtab; p <= last; p++) + if (memcmp(p, s, len) == 0) + return p; + return NULL; +} + +int fdt_move(const void *fdt, void *buf, int bufsize) +{ + if (!can_assume(VALID_INPUT) && bufsize < 0) + return -FDT_ERR_NOSPACE; + + FDT_RO_PROBE(fdt); + + if (fdt_totalsize(fdt) > (unsigned int)bufsize) + return -FDT_ERR_NOSPACE; + + memmove(buf, fdt, fdt_totalsize(fdt)); + return 0; +} diff --git a/clib/lib/libfdt/fdt.h b/clib/lib/libfdt/fdt.h new file mode 100644 index 0000000..0c91aa7 --- /dev/null +++ b/clib/lib/libfdt/fdt.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef FDT_H +#define FDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#ifndef __ASSEMBLY__ + +struct fdt_header { + fdt32_t magic; /* magic word FDT_MAGIC */ + fdt32_t totalsize; /* total size of DT block */ + fdt32_t off_dt_struct; /* offset to structure */ + fdt32_t off_dt_strings; /* offset to strings */ + fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ + fdt32_t version; /* format version */ + fdt32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + fdt32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + fdt32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + fdt32_t size_dt_struct; /* size of the structure block */ +}; + +struct fdt_reserve_entry { + fdt64_t address; + fdt64_t size; +}; + +struct fdt_node_header { + fdt32_t tag; + char name[]; +}; + +struct fdt_property { + fdt32_t tag; + fdt32_t len; + fdt32_t nameoff; + char data[]; +}; + +#endif /* !__ASSEMBLY */ + +#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ +#define FDT_TAGSIZE sizeof(fdt32_t) + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, + size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 + +#define FDT_V1_SIZE (7*sizeof(fdt32_t)) +#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) +#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) +#define FDT_V16_SIZE FDT_V3_SIZE +#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) + +#endif /* FDT_H */ diff --git a/clib/lib/libfdt/fdt_addresses.c b/clib/lib/libfdt/fdt_addresses.c new file mode 100644 index 0000000..c40ba09 --- /dev/null +++ b/clib/lib/libfdt/fdt_addresses.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2014 David Gibson + * Copyright (C) 2018 embedded brains GmbH + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_cells(const void *fdt, int nodeoffset, const char *name) +{ + const fdt32_t *c; + uint32_t val; + int len; + + c = fdt_getprop(fdt, nodeoffset, name, &len); + if (!c) + return len; + + if (len != sizeof(*c)) + return -FDT_ERR_BADNCELLS; + + val = fdt32_to_cpu(*c); + if (val > FDT_MAX_NCELLS) + return -FDT_ERR_BADNCELLS; + + return (int)val; +} + +int fdt_address_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#address-cells"); + if (val == 0) + return -FDT_ERR_BADNCELLS; + if (val == -FDT_ERR_NOTFOUND) + return 2; + return val; +} + +int fdt_size_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#size-cells"); + if (val == -FDT_ERR_NOTFOUND) + return 1; + return val; +} + +/* This function assumes that [address|size]_cells is 1 or 2 */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size) +{ + int addr_cells, size_cells, ret; + uint8_t data[sizeof(fdt64_t) * 2], *prop; + + ret = fdt_address_cells(fdt, parent); + if (ret < 0) + return ret; + addr_cells = ret; + + ret = fdt_size_cells(fdt, parent); + if (ret < 0) + return ret; + size_cells = ret; + + /* check validity of address */ + prop = data; + if (addr_cells == 1) { + if ((addr > UINT32_MAX) || (((uint64_t) UINT32_MAX + 1 - addr) < size)) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)addr); + } else if (addr_cells == 2) { + fdt64_st(prop, addr); + } else { + return -FDT_ERR_BADNCELLS; + } + + /* check validity of size */ + prop += addr_cells * sizeof(fdt32_t); + if (size_cells == 1) { + if (size > UINT32_MAX) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)size); + } else if (size_cells == 2) { + fdt64_st(prop, size); + } else { + return -FDT_ERR_BADNCELLS; + } + + return fdt_appendprop(fdt, nodeoffset, name, data, + (addr_cells + size_cells) * sizeof(fdt32_t)); +} diff --git a/clib/lib/libfdt/fdt_check.c b/clib/lib/libfdt/fdt_check.c new file mode 100644 index 0000000..a21ebbc --- /dev/null +++ b/clib/lib/libfdt/fdt_check.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_check_full(const void *fdt, size_t bufsize) +{ + int err; + int num_memrsv; + int offset, nextoffset = 0; + uint32_t tag; + unsigned int depth = 0; + const void *prop; + const char *propname; + bool expect_end = false; + + if (bufsize < FDT_V1_SIZE) + return -FDT_ERR_TRUNCATED; + if (bufsize < fdt_header_size(fdt)) + return -FDT_ERR_TRUNCATED; + err = fdt_check_header(fdt); + if (err != 0) + return err; + if (bufsize < fdt_totalsize(fdt)) + return -FDT_ERR_TRUNCATED; + + num_memrsv = fdt_num_mem_rsv(fdt); + if (num_memrsv < 0) + return num_memrsv; + + while (1) { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + + if (nextoffset < 0) + return nextoffset; + + /* If we see two root nodes, something is wrong */ + if (expect_end && tag != FDT_END) + return -FDT_ERR_BADSTRUCTURE; + + switch (tag) { + case FDT_NOP: + break; + + case FDT_END: + if (depth != 0) + return -FDT_ERR_BADSTRUCTURE; + return 0; + + case FDT_BEGIN_NODE: + depth++; + if (depth > INT_MAX) + return -FDT_ERR_BADSTRUCTURE; + + /* The root node must have an empty name */ + if (depth == 1) { + const char *name; + int len; + + name = fdt_get_name(fdt, offset, &len); + if (!name) + return len; + + if (*name || len) + return -FDT_ERR_BADSTRUCTURE; + } + break; + + case FDT_END_NODE: + if (depth == 0) + return -FDT_ERR_BADSTRUCTURE; + depth--; + if (depth == 0) + expect_end = true; + break; + + case FDT_PROP: + prop = fdt_getprop_by_offset(fdt, offset, &propname, + &err); + if (!prop) + return err; + break; + + default: + return -FDT_ERR_INTERNAL; + } + } +} diff --git a/clib/lib/libfdt/fdt_empty_tree.c b/clib/lib/libfdt/fdt_empty_tree.c new file mode 100644 index 0000000..49d54d4 --- /dev/null +++ b/clib/lib/libfdt/fdt_empty_tree.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2012 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_create_empty_tree(void *buf, int bufsize) +{ + int err; + + err = fdt_create(buf, bufsize); + if (err) + return err; + + err = fdt_finish_reservemap(buf); + if (err) + return err; + + err = fdt_begin_node(buf, ""); + if (err) + return err; + + err = fdt_end_node(buf); + if (err) + return err; + + err = fdt_finish(buf); + if (err) + return err; + + return fdt_open_into(buf, buf, bufsize); +} diff --git a/clib/lib/libfdt/fdt_overlay.c b/clib/lib/libfdt/fdt_overlay.c new file mode 100644 index 0000000..28b667f --- /dev/null +++ b/clib/lib/libfdt/fdt_overlay.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +/** + * overlay_get_target_phandle - retrieves the target phandle of a fragment + * @fdto: pointer to the device tree overlay blob + * @fragment: node offset of the fragment in the overlay + * + * overlay_get_target_phandle() retrieves the target phandle of an + * overlay fragment when that fragment uses a phandle (target + * property) instead of a path (target-path property). + * + * returns: + * the phandle pointed by the target property + * 0, if the phandle was not found + * -1, if the phandle was malformed + */ +static uint32_t overlay_get_target_phandle(const void *fdto, int fragment) +{ + const fdt32_t *val; + int len; + + val = fdt_getprop(fdto, fragment, "target", &len); + if (!val) + return 0; + + if ((len != sizeof(*val)) || (fdt32_to_cpu(*val) == (uint32_t)-1)) + return (uint32_t)-1; + + return fdt32_to_cpu(*val); +} + +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp) +{ + uint32_t phandle; + const char *path = NULL; + int path_len = 0, ret; + + /* Try first to do a phandle based lookup */ + phandle = overlay_get_target_phandle(fdto, fragment_offset); + if (phandle == (uint32_t)-1) + return -FDT_ERR_BADPHANDLE; + + /* no phandle, try path */ + if (!phandle) { + /* And then a path based lookup */ + path = fdt_getprop(fdto, fragment_offset, "target-path", &path_len); + if (path) + ret = fdt_path_offset(fdt, path); + else + ret = path_len; + } else + ret = fdt_node_offset_by_phandle(fdt, phandle); + + /* + * If we haven't found either a target or a + * target-path property in a node that contains a + * __overlay__ subnode (we wouldn't be called + * otherwise), consider it a improperly written + * overlay + */ + if (ret < 0 && path_len == -FDT_ERR_NOTFOUND) + ret = -FDT_ERR_BADOVERLAY; + + /* return on error */ + if (ret < 0) + return ret; + + /* return pointer to path (if available) */ + if (pathp) + *pathp = path ? path : NULL; + + return ret; +} + +/** + * overlay_phandle_add_offset - Increases a phandle by an offset + * @fdt: Base device tree blob + * @node: Device tree overlay blob + * @name: Name of the property to modify (phandle or linux,phandle) + * @delta: offset to apply + * + * overlay_phandle_add_offset() increments a node phandle by a given + * offset. + * + * returns: + * 0 on success. + * Negative error code on error + */ +static int overlay_phandle_add_offset(void *fdt, int node, + const char *name, uint32_t delta) +{ + fdt32_t *valp, val; + int len; + + valp = fdt_getprop_w(fdt, node, name, &len); + if (!valp) + return len; + + if (len != sizeof(val)) + return -FDT_ERR_BADPHANDLE; + + val = fdt32_ld(valp); + if (val + delta < val || val + delta == (uint32_t)-1) + return -FDT_ERR_NOPHANDLES; + + fdt32_st(valp, val + delta); + return 0; +} + +/** + * overlay_adjust_node_phandles - Offsets the phandles of a node + * @fdto: Device tree overlay blob + * @node: Offset of the node we want to adjust + * @delta: Offset to shift the phandles of + * + * overlay_adjust_node_phandles() adds a constant to all the phandles + * of a given node. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_node_phandles(void *fdto, int node, + uint32_t delta) +{ + int child; + int ret; + + ret = overlay_phandle_add_offset(fdto, node, "phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + ret = overlay_phandle_add_offset(fdto, node, "linux,phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + fdt_for_each_subnode(child, fdto, node) { + ret = overlay_adjust_node_phandles(fdto, child, delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_adjust_local_phandles - Adjust the phandles of a whole overlay + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_adjust_local_phandles() adds a constant to all the + * phandles of an overlay. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_phandles(void *fdto, uint32_t delta) +{ + /* + * Start adjusting the phandles from the overlay root + */ + return overlay_adjust_node_phandles(fdto, 0, delta); +} + +/** + * overlay_update_local_node_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @tree_node: Node offset of the node to operate on + * @fixup_node: Node offset of the matching local fixups node + * @delta: Offset to shift the phandles of + * + * overlay_update_local_nodes_references() update the phandles + * pointing to a node within the device tree overlay by adding a + * constant delta. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_node_references(void *fdto, + int tree_node, + int fixup_node, + uint32_t delta) +{ + int fixup_prop; + int fixup_child; + int ret; + + fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { + const fdt32_t *fixup_val; + const char *name; + char *tree_val; + int fixup_len; + int tree_len; + int i; + + fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, + &name, &fixup_len); + if (!fixup_val) + return fixup_len; + + if (fixup_len % sizeof(uint32_t)) + return -FDT_ERR_BADOVERLAY; + fixup_len /= sizeof(uint32_t); + + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); + if (!tree_val) { + if (tree_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + + return tree_len; + } + + for (i = 0; i < fixup_len; i++) { + fdt32_t *refp; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + + /* + * phandles to fixup can be unaligned, so use + * fdt32_{ld,st}() to read/write them. + */ + fdt32_st(refp, fdt32_ld(refp) + delta); + } + } + + fdt_for_each_subnode(fixup_child, fdto, fixup_node) { + const char *fixup_child_name = fdt_get_name(fdto, fixup_child, + NULL); + int tree_child; + + tree_child = fdt_subnode_offset(fdto, tree_node, + fixup_child_name); + if (tree_child == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (tree_child < 0) + return tree_child; + + ret = overlay_update_local_node_references(fdto, + tree_child, + fixup_child, + delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_update_local_references() update all the phandles pointing + * to a node within the device tree overlay by adding a constant + * delta to not conflict with the base overlay. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_references(void *fdto, uint32_t delta) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups < 0) { + /* There's no local phandles to adjust, bail out */ + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + + return fixups; + } + + /* + * Update our local references from the root of the tree + */ + return overlay_update_local_node_references(fdto, 0, fixups, + delta); +} + +/** + * overlay_fixup_one_phandle - Set an overlay phandle to the base one + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @path: Path to a node holding a phandle in the overlay + * @path_len: number of path characters to consider + * @name: Name of the property holding the phandle reference in the overlay + * @name_len: number of name characters to consider + * @poffset: Offset within the overlay property where the phandle is stored + * @phandle: Phandle referencing the node + * + * overlay_fixup_one_phandle() resolves an overlay phandle pointing to + * a node in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_one_phandle(void *fdt, void *fdto, + int symbols_off, + const char *path, uint32_t path_len, + const char *name, uint32_t name_len, + int poffset, uint32_t phandle) +{ + fdt32_t phandle_prop; + int fixup_off; + + if (symbols_off < 0) + return symbols_off; + + fixup_off = fdt_path_offset_namelen(fdto, path, path_len); + if (fixup_off == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (fixup_off < 0) + return fixup_off; + + phandle_prop = cpu_to_fdt32(phandle); + return fdt_setprop_inplace_namelen_partial(fdto, fixup_off, + name, name_len, poffset, + &phandle_prop, + sizeof(phandle_prop)); +}; + +/** + * overlay_fixup_phandle - Set an overlay phandle to the base one + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @property: Property offset in the overlay holding the list of fixups + * + * overlay_fixup_phandle() resolves all the overlay phandles pointed + * to in a __fixups__ property, and updates them to match the phandles + * in use in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, + int property) +{ + const char *value; + const char *label; + int len; + const char *symbol_path; + int prop_len; + int symbol_off; + uint32_t phandle; + + value = fdt_getprop_by_offset(fdto, property, + &label, &len); + if (!value) { + if (len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + + return len; + } + + symbol_path = fdt_getprop(fdt, symbols_off, label, &prop_len); + if (!symbol_path) + return prop_len; + + symbol_off = fdt_path_offset(fdt, symbol_path); + if (symbol_off < 0) + return symbol_off; + + phandle = fdt_get_phandle(fdt, symbol_off); + if (!phandle) + return -FDT_ERR_NOTFOUND; + + do { + const char *path, *name, *fixup_end; + const char *fixup_str = value; + uint32_t path_len, name_len; + uint32_t fixup_len; + char *sep, *endptr; + int poffset, ret; + + fixup_end = memchr(value, '\0', len); + if (!fixup_end) + return -FDT_ERR_BADOVERLAY; + fixup_len = fixup_end - fixup_str; + + len -= fixup_len + 1; + value += fixup_len + 1; + + path = fixup_str; + sep = memchr(fixup_str, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + path_len = sep - path; + if (path_len == (fixup_len - 1)) + return -FDT_ERR_BADOVERLAY; + + fixup_len -= path_len + 1; + name = sep + 1; + sep = memchr(name, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + name_len = sep - name; + if (!name_len) + return -FDT_ERR_BADOVERLAY; + + poffset = strtoul(sep + 1, &endptr, 10); + if ((*endptr != '\0') || (endptr <= (sep + 1))) + return -FDT_ERR_BADOVERLAY; + + ret = overlay_fixup_one_phandle(fdt, fdto, symbols_off, + path, path_len, name, name_len, + poffset, phandle); + if (ret) + return ret; + } while (len > 0); + + return 0; +} + +/** + * overlay_fixup_phandles - Resolve the overlay phandles to the base + * device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_fixup_phandles() resolves all the overlay phandles pointing + * to nodes in the base device tree. + * + * This is one of the steps of the device tree overlay application + * process, when you want all the phandles in the overlay to point to + * the actual base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandles(void *fdt, void *fdto) +{ + int fixups_off, symbols_off; + int property; + + /* We can have overlays without any fixups */ + fixups_off = fdt_path_offset(fdto, "/__fixups__"); + if (fixups_off == -FDT_ERR_NOTFOUND) + return 0; /* nothing to do */ + if (fixups_off < 0) + return fixups_off; + + /* And base DTs without symbols */ + symbols_off = fdt_path_offset(fdt, "/__symbols__"); + if ((symbols_off < 0 && (symbols_off != -FDT_ERR_NOTFOUND))) + return symbols_off; + + fdt_for_each_property_offset(property, fdto, fixups_off) { + int ret; + + ret = overlay_fixup_phandle(fdt, fdto, symbols_off, property); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_adjust_local_conflicting_phandle: Changes a phandle value + * @fdto: Device tree overlay + * @node: The node the phandle is set for + * @fdt_phandle: The new value for the phandle + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_conflicting_phandle(void *fdto, int node, + uint32_t fdt_phandle) +{ + const fdt32_t *php; + int len, ret; + + php = fdt_getprop(fdto, node, "phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "phandle", fdt_phandle); + if (ret) + return ret; + } + + php = fdt_getprop(fdto, node, "linux,phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "linux,phandle", fdt_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_node_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @tree_node: Node to recurse into + * @fixup_node: Node offset of the matching local fixups node + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_node_conflicting_references(void *fdto, int tree_node, + int fixup_node, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixup_prop; + int fixup_child; + int ret; + + fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { + const fdt32_t *fixup_val; + const char *name; + char *tree_val; + int fixup_len; + int tree_len; + int i; + + fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, + &name, &fixup_len); + if (!fixup_val) + return fixup_len; + + if (fixup_len % sizeof(uint32_t)) + return -FDT_ERR_BADOVERLAY; + fixup_len /= sizeof(uint32_t); + + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); + if (!tree_val) { + if (tree_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + + return tree_len; + } + + for (i = 0; i < fixup_len; i++) { + fdt32_t *refp; + uint32_t valp; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + valp = fdt32_ld(refp); + + if (valp == fdto_phandle) + fdt32_st(refp, fdt_phandle); + } + } + + fdt_for_each_subnode(fixup_child, fdto, fixup_node) { + const char *fixup_child_name = fdt_get_name(fdto, fixup_child, NULL); + int tree_child; + + tree_child = fdt_subnode_offset(fdto, tree_node, fixup_child_name); + + if (tree_child == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (tree_child < 0) + return tree_child; + + ret = overlay_update_node_conflicting_references(fdto, tree_child, + fixup_child, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_conflicting_references(void *fdto, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + if (fixups < 0) + return fixups; + + return overlay_update_node_conflicting_references(fdto, 0, fixups, + fdt_phandle, + fdto_phandle); +} + +/** + * overlay_prevent_phandle_overwrite_node - Helper function for overlay_prevent_phandle_overwrite + * @fdt: Base Device tree blob + * @fdtnode: Node in fdt that is checked for an overwrite + * @fdto: Device tree overlay blob + * @fdtonode: Node in fdto matching @fdtnode + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite_node(void *fdt, int fdtnode, + void *fdto, int fdtonode) +{ + uint32_t fdt_phandle, fdto_phandle; + int fdtochild; + + fdt_phandle = fdt_get_phandle(fdt, fdtnode); + fdto_phandle = fdt_get_phandle(fdto, fdtonode); + + if (fdt_phandle && fdto_phandle) { + int ret; + + ret = overlay_adjust_local_conflicting_phandle(fdto, fdtonode, + fdt_phandle); + if (ret) + return ret; + + ret = overlay_update_local_conflicting_references(fdto, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + fdt_for_each_subnode(fdtochild, fdto, fdtonode) { + const char *name = fdt_get_name(fdto, fdtochild, NULL); + int fdtchild; + int ret; + + fdtchild = fdt_subnode_offset(fdt, fdtnode, name); + if (fdtchild == -FDT_ERR_NOTFOUND) + /* + * no further overwrites possible here as this node is + * new + */ + continue; + + ret = overlay_prevent_phandle_overwrite_node(fdt, fdtchild, + fdto, fdtochild); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_prevent_phandle_overwrite - Fixes overlay phandles to not overwrite base phandles + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * Checks recursively if applying fdto overwrites phandle values in the base + * dtb. When such a phandle is found, the fdto is changed to use the fdt's + * phandle value to not break references in the base. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (overlay == -FDT_ERR_NOTFOUND) + continue; + + if (overlay < 0) + return overlay; + + target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); + if (target == -FDT_ERR_NOTFOUND) + /* + * The subtree doesn't exist in the base, so nothing + * will be overwritten. + */ + continue; + else if (target < 0) + return target; + + ret = overlay_prevent_phandle_overwrite_node(fdt, target, + fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_apply_node - Merges a node into the base device tree + * @fdt: Base Device Tree blob + * @target: Node offset in the base device tree to apply the fragment to + * @fdto: Device tree overlay blob + * @node: Node offset in the overlay holding the changes to merge + * + * overlay_apply_node() merges a node into a target base device tree + * node pointed. + * + * This is part of the final step in the device tree overlay + * application process, when all the phandles have been adjusted and + * resolved and you just have to merge overlay into the base device + * tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_apply_node(void *fdt, int target, + void *fdto, int node) +{ + int property; + int subnode; + + fdt_for_each_property_offset(property, fdto, node) { + const char *name; + const void *prop; + int prop_len; + int ret; + + prop = fdt_getprop_by_offset(fdto, property, &name, + &prop_len); + if (prop_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + if (prop_len < 0) + return prop_len; + + ret = fdt_setprop(fdt, target, name, prop, prop_len); + if (ret) + return ret; + } + + fdt_for_each_subnode(subnode, fdto, node) { + const char *name = fdt_get_name(fdto, subnode, NULL); + int nnode; + int ret; + + nnode = fdt_add_subnode(fdt, target, name); + if (nnode == -FDT_ERR_EXISTS) { + nnode = fdt_subnode_offset(fdt, target, name); + if (nnode == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + } + + if (nnode < 0) + return nnode; + + ret = overlay_apply_node(fdt, nnode, fdto, subnode); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_merge - Merge an overlay into its base device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_merge() merges an overlay into its base device tree. + * + * This is the next to last step in the device tree overlay application + * process, when all the phandles have been adjusted and resolved and + * you just have to merge overlay into the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_merge(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + /* + * Each fragments will have an __overlay__ node. If + * they don't, it's not supposed to be merged + */ + overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (overlay == -FDT_ERR_NOTFOUND) + continue; + + if (overlay < 0) + return overlay; + + target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); + if (target < 0) + return target; + + ret = overlay_apply_node(fdt, target, fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +static int get_path_len(const void *fdt, int nodeoffset) +{ + int len = 0, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + for (;;) { + name = fdt_get_name(fdt, nodeoffset, &namelen); + if (!name) + return namelen; + + /* root? we're done */ + if (namelen == 0) + break; + + nodeoffset = fdt_parent_offset(fdt, nodeoffset); + if (nodeoffset < 0) + return nodeoffset; + len += namelen + 1; + } + + /* in case of root pretend it's "/" */ + if (len == 0) + len++; + return len; +} + +/** + * overlay_symbol_update - Update the symbols of base tree after a merge + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_symbol_update() updates the symbols of the base tree with the + * symbols of the applied overlay + * + * This is the last step in the device tree overlay application + * process, allowing the reference of overlay symbols by subsequent + * overlay operations. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_symbol_update(void *fdt, void *fdto) +{ + int root_sym, ov_sym, prop, path_len, fragment, target; + int len, frag_name_len, ret, rel_path_len; + const char *s, *e; + const char *path; + const char *name; + const char *frag_name; + const char *rel_path; + const char *target_path; + char *buf; + void *p; + + ov_sym = fdt_subnode_offset(fdto, 0, "__symbols__"); + + /* if no overlay symbols exist no problem */ + if (ov_sym < 0) + return 0; + + root_sym = fdt_subnode_offset(fdt, 0, "__symbols__"); + + /* it no root symbols exist we should create them */ + if (root_sym == -FDT_ERR_NOTFOUND) + root_sym = fdt_add_subnode(fdt, 0, "__symbols__"); + + /* any error is fatal now */ + if (root_sym < 0) + return root_sym; + + /* iterate over each overlay symbol */ + fdt_for_each_property_offset(prop, fdto, ov_sym) { + path = fdt_getprop_by_offset(fdto, prop, &name, &path_len); + if (!path) + return path_len; + + /* verify it's a string property (terminated by a single \0) */ + if (path_len < 1 || memchr(path, '\0', path_len) != &path[path_len - 1]) + return -FDT_ERR_BADVALUE; + + /* keep end marker to avoid strlen() */ + e = path + path_len; + + if (*path != '/') + return -FDT_ERR_BADVALUE; + + /* get fragment name first */ + s = strchr(path + 1, '/'); + if (!s) { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + frag_name = path + 1; + frag_name_len = s - path - 1; + + /* verify format; safe since "s" lies in \0 terminated prop */ + len = sizeof("/__overlay__/") - 1; + if ((e - s) > len && (memcmp(s, "/__overlay__/", len) == 0)) { + /* //__overlay__/ */ + rel_path = s + len; + rel_path_len = e - rel_path - 1; + } else if ((e - s) == len + && (memcmp(s, "/__overlay__", len - 1) == 0)) { + /* //__overlay__ */ + rel_path = ""; + rel_path_len = 0; + } else { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + /* find the fragment index in which the symbol lies */ + ret = fdt_subnode_offset_namelen(fdto, 0, frag_name, + frag_name_len); + /* not found? */ + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + fragment = ret; + + /* an __overlay__ subnode must exist */ + ret = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + + /* get the target of the fragment */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + + /* if we have a target path use */ + if (!target_path) { + ret = get_path_len(fdt, target); + if (ret < 0) + return ret; + len = ret; + } else { + len = strlen(target_path); + } + + ret = fdt_setprop_placeholder(fdt, root_sym, name, + len + (len > 1) + rel_path_len + 1, &p); + if (ret < 0) + return ret; + + if (!target_path) { + /* again in case setprop_placeholder changed it */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + } + + buf = p; + if (len > 1) { /* target is not root */ + if (!target_path) { + ret = fdt_get_path(fdt, target, buf, len + 1); + if (ret < 0) + return ret; + } else + memcpy(buf, target_path, len + 1); + + } else + len--; + + buf[len] = '/'; + memcpy(buf + len + 1, rel_path, rel_path_len); + buf[len + 1 + rel_path_len] = '\0'; + } + + return 0; +} + +int fdt_overlay_apply(void *fdt, void *fdto) +{ + uint32_t delta; + int ret; + + FDT_RO_PROBE(fdt); + FDT_RO_PROBE(fdto); + + ret = fdt_find_max_phandle(fdt, &delta); + if (ret) + goto err; + + /* Increase all phandles in the fdto by delta */ + ret = overlay_adjust_local_phandles(fdto, delta); + if (ret) + goto err; + + /* Adapt the phandle values in fdto to the above increase */ + ret = overlay_update_local_references(fdto, delta); + if (ret) + goto err; + + /* Update fdto's phandles using symbols from fdt */ + ret = overlay_fixup_phandles(fdt, fdto); + if (ret) + goto err; + + /* Don't overwrite phandles in fdt */ + ret = overlay_prevent_phandle_overwrite(fdt, fdto); + if (ret) + goto err; + + ret = overlay_merge(fdt, fdto); + if (ret) + goto err; + + ret = overlay_symbol_update(fdt, fdto); + if (ret) + goto err; + + /* + * The overlay has been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + return 0; + +err: + /* + * The overlay might have been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + /* + * The base device tree might have been damaged, erase its + * magic. + */ + fdt_set_magic(fdt, ~0); + + return ret; +} diff --git a/clib/lib/libfdt/fdt_ro.c b/clib/lib/libfdt/fdt_ro.c new file mode 100644 index 0000000..b78c4e4 --- /dev/null +++ b/clib/lib/libfdt/fdt_ro.c @@ -0,0 +1,888 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_nodename_eq_(const void *fdt, int offset, + const char *s, int len) +{ + int olen; + const char *p = fdt_get_name(fdt, offset, &olen); + + if (!p || olen < len) + /* short match */ + return 0; + + if (memcmp(p, s, len) != 0) + return 0; + + if (p[len] == '\0') + return 1; + else if (!memchr(s, '@', len) && (p[len] == '@')) + return 1; + else + return 0; +} + +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) +{ + int32_t totalsize; + uint32_t absoffset; + size_t len; + int err; + const char *s, *n; + + if (can_assume(VALID_INPUT)) { + s = (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; + + if (lenp) + *lenp = strlen(s); + return s; + } + totalsize = fdt_ro_probe_(fdt); + err = totalsize; + if (totalsize < 0) + goto fail; + + err = -FDT_ERR_BADOFFSET; + absoffset = stroffset + fdt_off_dt_strings(fdt); + if (absoffset >= (unsigned)totalsize) + goto fail; + len = totalsize - absoffset; + + if (fdt_magic(fdt) == FDT_MAGIC) { + if (stroffset < 0) + goto fail; + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + if ((unsigned)stroffset >= fdt_size_dt_strings(fdt)) + goto fail; + if ((fdt_size_dt_strings(fdt) - stroffset) < len) + len = fdt_size_dt_strings(fdt) - stroffset; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + unsigned int sw_stroffset = -stroffset; + + if ((stroffset >= 0) || + (sw_stroffset > fdt_size_dt_strings(fdt))) + goto fail; + if (sw_stroffset < len) + len = sw_stroffset; + } else { + err = -FDT_ERR_INTERNAL; + goto fail; + } + + s = (const char *)fdt + absoffset; + n = memchr(s, '\0', len); + if (!n) { + /* missing terminating NULL */ + err = -FDT_ERR_TRUNCATED; + goto fail; + } + + if (lenp) + *lenp = n - s; + return s; + +fail: + if (lenp) + *lenp = err; + return NULL; +} + +const char *fdt_string(const void *fdt, int stroffset) +{ + return fdt_get_string(fdt, stroffset, NULL); +} + +static int fdt_string_eq_(const void *fdt, int stroffset, + const char *s, int len) +{ + int slen; + const char *p = fdt_get_string(fdt, stroffset, &slen); + + return p && (slen == len) && (memcmp(p, s, len) == 0); +} + +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max = 0; + int offset = -1; + + while (true) { + uint32_t value; + + offset = fdt_next_node(fdt, offset, NULL); + if (offset < 0) { + if (offset == -FDT_ERR_NOTFOUND) + break; + + return offset; + } + + value = fdt_get_phandle(fdt, offset); + + if (value > max) + max = value; + } + + if (phandle) + *phandle = max; + + return 0; +} + +int fdt_generate_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max; + int err; + + err = fdt_find_max_phandle(fdt, &max); + if (err < 0) + return err; + + if (max == FDT_MAX_PHANDLE) + return -FDT_ERR_NOPHANDLES; + + if (phandle) + *phandle = max + 1; + + return 0; +} + +static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) +{ + unsigned int offset = n * sizeof(struct fdt_reserve_entry); + unsigned int absoffset = fdt_off_mem_rsvmap(fdt) + offset; + + if (!can_assume(VALID_INPUT)) { + if (absoffset < fdt_off_mem_rsvmap(fdt)) + return NULL; + if (absoffset > fdt_totalsize(fdt) - + sizeof(struct fdt_reserve_entry)) + return NULL; + } + return fdt_mem_rsv_(fdt, n); +} + +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) +{ + const struct fdt_reserve_entry *re; + + FDT_RO_PROBE(fdt); + re = fdt_mem_rsv(fdt, n); + if (!can_assume(VALID_INPUT) && !re) + return -FDT_ERR_BADOFFSET; + + *address = fdt64_ld_(&re->address); + *size = fdt64_ld_(&re->size); + return 0; +} + +int fdt_num_mem_rsv(const void *fdt) +{ + int i; + const struct fdt_reserve_entry *re; + + for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { + if (fdt64_ld_(&re->size) == 0) + return i; + } + return -FDT_ERR_TRUNCATED; +} + +static int nextprop_(const void *fdt, int offset) +{ + uint32_t tag; + int nextoffset; + + do { + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_END: + if (nextoffset >= 0) + return -FDT_ERR_BADSTRUCTURE; + else + return nextoffset; + + case FDT_PROP: + return offset; + } + offset = nextoffset; + } while (tag == FDT_NOP); + + return -FDT_ERR_NOTFOUND; +} + +int fdt_subnode_offset_namelen(const void *fdt, int offset, + const char *name, int namelen) +{ + int depth; + + FDT_RO_PROBE(fdt); + + for (depth = 0; + (offset >= 0) && (depth >= 0); + offset = fdt_next_node(fdt, offset, &depth)) + if ((depth == 1) + && fdt_nodename_eq_(fdt, offset, name, namelen)) + return offset; + + if (depth < 0) + return -FDT_ERR_NOTFOUND; + return offset; /* error */ +} + +int fdt_subnode_offset(const void *fdt, int parentoffset, + const char *name) +{ + return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) +{ + const char *end = path + namelen; + const char *p = path; + int offset = 0; + + FDT_RO_PROBE(fdt); + + if (!can_assume(VALID_INPUT) && namelen <= 0) + return -FDT_ERR_BADPATH; + + /* see if we have an alias */ + if (*path != '/') { + const char *q = memchr(path, '/', end - p); + + if (!q) + q = end; + + p = fdt_get_alias_namelen(fdt, p, q - p); + if (!p) + return -FDT_ERR_BADPATH; + offset = fdt_path_offset(fdt, p); + + p = q; + } + + while (p < end) { + const char *q; + + while (*p == '/') { + p++; + if (p == end) + return offset; + } + q = memchr(p, '/', end - p); + if (! q) + q = end; + + offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); + if (offset < 0) + return offset; + + p = q; + } + + return offset; +} + +int fdt_path_offset(const void *fdt, const char *path) +{ + return fdt_path_offset_namelen(fdt, path, strlen(path)); +} + +const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) +{ + const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); + const char *nameptr; + int err; + + if (((err = fdt_ro_probe_(fdt)) < 0) + || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0)) + goto fail; + + nameptr = nh->name; + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + /* + * For old FDT versions, match the naming conventions of V16: + * give only the leaf name (after all /). The actual tree + * contents are loosely checked. + */ + const char *leaf; + leaf = strrchr(nameptr, '/'); + if (leaf == NULL) { + err = -FDT_ERR_BADSTRUCTURE; + goto fail; + } + nameptr = leaf+1; + } + + if (len) + *len = strlen(nameptr); + + return nameptr; + + fail: + if (len) + *len = err; + return NULL; +} + +int fdt_first_property_offset(const void *fdt, int nodeoffset) +{ + int offset; + + if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +int fdt_next_property_offset(const void *fdt, int offset) +{ + if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, + int offset, + int *lenp) +{ + int err; + const struct fdt_property *prop; + + if (!can_assume(VALID_INPUT) && + (err = fdt_check_prop_offset_(fdt, offset)) < 0) { + if (lenp) + *lenp = err; + return NULL; + } + + prop = fdt_offset_ptr_(fdt, offset); + + if (lenp) + *lenp = fdt32_ld_(&prop->len); + + return prop; +} + +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_by_offset_(fdt, offset, lenp); +} + +static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, + int offset, + const char *name, + int namelen, + int *lenp, + int *poffset) +{ + for (offset = fdt_first_property_offset(fdt, offset); + (offset >= 0); + (offset = fdt_next_property_offset(fdt, offset))) { + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!can_assume(LIBFDT_FLAWLESS) && !prop) { + offset = -FDT_ERR_INTERNAL; + break; + } + if (fdt_string_eq_(fdt, fdt32_ld_(&prop->nameoff), + name, namelen)) { + if (poffset) + *poffset = offset; + return prop; + } + } + + if (lenp) + *lenp = offset; + return NULL; +} + + +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int offset, + const char *name, + int namelen, int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_namelen_(fdt, offset, name, namelen, lenp, + NULL); +} + + +const struct fdt_property *fdt_get_property(const void *fdt, + int nodeoffset, + const char *name, int *lenp) +{ + return fdt_get_property_namelen(fdt, nodeoffset, name, + strlen(name), lenp); +} + +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp) +{ + int poffset; + const struct fdt_property *prop; + + prop = fdt_get_property_namelen_(fdt, nodeoffset, name, namelen, lenp, + &poffset); + if (!prop) + return NULL; + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (poffset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp) +{ + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!prop) + return NULL; + if (namep) { + const char *name; + int namelen; + + if (!can_assume(VALID_INPUT)) { + name = fdt_get_string(fdt, fdt32_ld_(&prop->nameoff), + &namelen); + *namep = name; + if (!name) { + if (lenp) + *lenp = namelen; + return NULL; + } + } else { + *namep = fdt_string(fdt, fdt32_ld_(&prop->nameoff)); + } + } + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (offset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); +} + +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) +{ + const fdt32_t *php; + int len; + + /* FIXME: This is a bit sub-optimal, since we potentially scan + * over all the properties twice. */ + php = fdt_getprop(fdt, nodeoffset, "phandle", &len); + if (!php || (len != sizeof(*php))) { + php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); + if (!php || (len != sizeof(*php))) + return 0; + } + + return fdt32_ld_(php); +} + +static const void *fdt_path_getprop_namelen(const void *fdt, const char *path, + const char *propname, int propnamelen, + int *lenp) +{ + int offset = fdt_path_offset(fdt, path); + + if (offset < 0) + return NULL; + + return fdt_getprop_namelen(fdt, offset, propname, propnamelen, lenp); +} + +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen) +{ + int len; + const char *alias; + + alias = fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, &len); + + if (!can_assume(VALID_DTB) && + !(alias && len > 0 && alias[len - 1] == '\0' && *alias == '/')) + return NULL; + + return alias; +} + +const char *fdt_get_alias(const void *fdt, const char *name) +{ + return fdt_get_alias_namelen(fdt, name, strlen(name)); +} + +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen) +{ + return fdt_path_getprop_namelen(fdt, "/__symbols__", name, namelen, NULL); +} + +const char *fdt_get_symbol(const void *fdt, const char *name) +{ + return fdt_get_symbol_namelen(fdt, name, strlen(name)); +} + +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) +{ + int pdepth = 0, p = 0; + int offset, depth, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + if (buflen < 2) + return -FDT_ERR_NOSPACE; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + while (pdepth > depth) { + do { + p--; + } while (buf[p-1] != '/'); + pdepth--; + } + + if (pdepth >= depth) { + name = fdt_get_name(fdt, offset, &namelen); + if (!name) + return namelen; + if ((p + namelen + 1) <= buflen) { + memcpy(buf + p, name, namelen); + p += namelen; + buf[p++] = '/'; + pdepth++; + } + } + + if (offset == nodeoffset) { + if (pdepth < (depth + 1)) + return -FDT_ERR_NOSPACE; + + if (p > 1) /* special case so that root path is "/", not "" */ + p--; + buf[p] = '\0'; + return 0; + } + } + + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + + return offset; /* error from fdt_next_node() */ +} + +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth) +{ + int offset, depth; + int supernodeoffset = -FDT_ERR_INTERNAL; + + FDT_RO_PROBE(fdt); + + if (supernodedepth < 0) + return -FDT_ERR_NOTFOUND; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + if (depth == supernodedepth) + supernodeoffset = offset; + + if (offset == nodeoffset) { + if (nodedepth) + *nodedepth = depth; + + if (supernodedepth > depth) + return -FDT_ERR_NOTFOUND; + else + return supernodeoffset; + } + } + + if (!can_assume(VALID_INPUT)) { + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_depth(const void *fdt, int nodeoffset) +{ + int nodedepth; + int err; + + err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); + if (err) + return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err : + -FDT_ERR_INTERNAL; + return nodedepth; +} + +int fdt_parent_offset(const void *fdt, int nodeoffset) +{ + int nodedepth = fdt_node_depth(fdt, nodeoffset); + + if (nodedepth < 0) + return nodedepth; + return fdt_supernode_atdepth_offset(fdt, nodeoffset, + nodedepth - 1, NULL); +} + +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen) +{ + int offset; + const void *val; + int len; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_getprop(), then if that didn't + * find what we want, we scan over them again making our way + * to the next node. Still it's the easiest to implement + * approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + val = fdt_getprop(fdt, offset, propname, &len); + if (val && (len == proplen) + && (memcmp(val, propval, len) == 0)) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) +{ + int offset; + + if ((phandle == 0) || (phandle == ~0U)) + return -FDT_ERR_BADPHANDLE; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we + * potentially scan each property of a node in + * fdt_get_phandle(), then if that didn't find what + * we want, we scan over them again making our way to the next + * node. Still it's the easiest to implement approach; + * performance can come later. */ + for (offset = fdt_next_node(fdt, -1, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + if (fdt_get_phandle(fdt, offset) == phandle) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) +{ + int len = strlen(str); + const char *p; + + while (listlen >= len) { + if (memcmp(str, strlist, len+1) == 0) + return 1; + p = memchr(strlist, '\0', listlen); + if (!p) + return 0; /* malformed strlist.. */ + listlen -= (p-strlist) + 1; + strlist = p + 1; + } + return 0; +} + +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) +{ + const char *list, *end; + int length, count = 0; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + list += length; + count++; + } + + return count; +} + +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string) +{ + int length, len, idx = 0; + const char *list, *end; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + len = strlen(string) + 1; + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + if (length == len && memcmp(list, string, length) == 0) + return idx; + + list += length; + idx++; + } + + return -FDT_ERR_NOTFOUND; +} + +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int idx, + int *lenp) +{ + const char *list, *end; + int length; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) { + if (lenp) + *lenp = length; + + return NULL; + } + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) { + if (lenp) + *lenp = -FDT_ERR_BADVALUE; + + return NULL; + } + + if (idx == 0) { + if (lenp) + *lenp = length - 1; + + return list; + } + + list += length; + idx--; + } + + if (lenp) + *lenp = -FDT_ERR_NOTFOUND; + + return NULL; +} + +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible) +{ + const void *prop; + int len; + + prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); + if (!prop) + return len; + + return !fdt_stringlist_contains(prop, len, compatible); +} + +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible) +{ + int offset, err; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_node_check_compatible(), then if + * that didn't find what we want, we scan over them again + * making our way to the next node. Still it's the easiest to + * implement approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + err = fdt_node_check_compatible(fdt, offset, compatible); + if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) + return err; + else if (err == 0) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} diff --git a/clib/lib/libfdt/fdt_rw.c b/clib/lib/libfdt/fdt_rw.c new file mode 100644 index 0000000..3621d36 --- /dev/null +++ b/clib/lib/libfdt/fdt_rw.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_blocks_misordered_(const void *fdt, + int mem_rsv_size, int struct_size) +{ + return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8)) + || (fdt_off_dt_struct(fdt) < + (fdt_off_mem_rsvmap(fdt) + mem_rsv_size)) + || (fdt_off_dt_strings(fdt) < + (fdt_off_dt_struct(fdt) + struct_size)) + || (fdt_totalsize(fdt) < + (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); +} + +static int fdt_rw_probe_(void *fdt) +{ + if (can_assume(VALID_DTB)) + return 0; + FDT_RO_PROBE(fdt); + + if (!can_assume(LATEST) && fdt_version(fdt) < 17) + return -FDT_ERR_BADVERSION; + if (fdt_blocks_misordered_(fdt, sizeof(struct fdt_reserve_entry), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_BADLAYOUT; + if (!can_assume(LATEST) && fdt_version(fdt) > 17) + fdt_set_version(fdt, 17); + + return 0; +} + +#define FDT_RW_PROBE(fdt) \ + { \ + int err_; \ + if ((err_ = fdt_rw_probe_(fdt)) != 0) \ + return err_; \ + } + +static inline unsigned int fdt_data_size_(void *fdt) +{ + return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); +} + +static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen) +{ + char *p = splicepoint; + unsigned int dsize = fdt_data_size_(fdt); + size_t soff = p - (char *)fdt; + + if ((oldlen < 0) || (soff + oldlen < soff) || (soff + oldlen > dsize)) + return -FDT_ERR_BADOFFSET; + if ((p < (char *)fdt) || (dsize + newlen < (unsigned)oldlen)) + return -FDT_ERR_BADOFFSET; + if (dsize - oldlen + newlen > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + memmove(p + newlen, p + oldlen, ((char *)fdt + dsize) - (p + oldlen)); + return 0; +} + +static int fdt_splice_mem_rsv_(void *fdt, struct fdt_reserve_entry *p, + int oldn, int newn) +{ + int delta = (newn - oldn) * sizeof(*p); + int err; + err = fdt_splice_(fdt, p, oldn * sizeof(*p), newn * sizeof(*p)); + if (err) + return err; + fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +static int fdt_splice_struct_(void *fdt, void *p, + int oldlen, int newlen) +{ + int delta = newlen - oldlen; + int err; + + if ((err = fdt_splice_(fdt, p, oldlen, newlen))) + return err; + + fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int newlen = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); +} + +static int fdt_splice_string_(void *fdt, int newlen) +{ + void *p = (char *)fdt + + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); + int err; + + if ((err = fdt_splice_(fdt, p, 0, newlen))) + return err; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); + return 0; +} + +/** + * fdt_find_add_string_() - Find or allocate a string + * + * @fdt: pointer to the device tree to check/adjust + * @s: string to find/add + * @allocated: Set to 0 if the string was found, 1 if not found and so + * allocated. Ignored if can_assume(NO_ROLLBACK) + * @return offset of string in the string table (whether found or added) + */ +static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +{ + char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); + const char *p; + char *new; + int len = strlen(s) + 1; + int err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 0; + + p = fdt_find_string_(strtab, fdt_size_dt_strings(fdt), s); + if (p) + /* found it */ + return (p - strtab); + + new = strtab + fdt_size_dt_strings(fdt); + err = fdt_splice_string_(fdt, len); + if (err) + return err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 1; + + memcpy(new, s, len); + return (new - strtab); +} + +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) +{ + struct fdt_reserve_entry *re; + int err; + + FDT_RW_PROBE(fdt); + + re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt)); + err = fdt_splice_mem_rsv_(fdt, re, 0, 1); + if (err) + return err; + + re->address = cpu_to_fdt64(address); + re->size = cpu_to_fdt64(size); + return 0; +} + +int fdt_del_mem_rsv(void *fdt, int n) +{ + struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n); + + FDT_RW_PROBE(fdt); + + if (n >= fdt_num_mem_rsv(fdt)) + return -FDT_ERR_NOTFOUND; + + return fdt_splice_mem_rsv_(fdt, re, 1, 0); +} + +static int fdt_resize_property_(void *fdt, int nodeoffset, const char *name, + int len, struct fdt_property **prop) +{ + int oldlen; + int err; + + *prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (!*prop) + return oldlen; + + if ((err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(len)))) + return err; + + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, + int len, struct fdt_property **prop) +{ + int proplen; + int nextoffset; + int namestroff; + int err; + int allocated; + + if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return nextoffset; + + namestroff = fdt_find_add_string_(fdt, name, &allocated); + if (namestroff < 0) + return namestroff; + + *prop = fdt_offset_ptr_w_(fdt, nextoffset); + proplen = sizeof(**prop) + FDT_TAGALIGN(len); + + err = fdt_splice_struct_(fdt, *prop, 0, proplen); + if (err) { + /* Delete the string if we failed to add it */ + if (!can_assume(NO_ROLLBACK) && allocated) + fdt_del_last_string_(fdt, name); + return err; + } + + (*prop)->tag = cpu_to_fdt32(FDT_PROP); + (*prop)->nameoff = cpu_to_fdt32(namestroff); + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +int fdt_set_name(void *fdt, int nodeoffset, const char *name) +{ + char *namep; + int oldlen, newlen; + int err; + + FDT_RW_PROBE(fdt); + + namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); + if (!namep) + return oldlen; + + newlen = strlen(name); + + err = fdt_splice_struct_(fdt, namep, FDT_TAGALIGN(oldlen+1), + FDT_TAGALIGN(newlen+1)); + if (err) + return err; + + memcpy(namep, name, newlen+1); + return 0; +} + +int fdt_setprop_placeholder(void *fdt, int nodeoffset, const char *name, + int len, void **prop_data) +{ + struct fdt_property *prop; + int err; + + FDT_RW_PROBE(fdt); + + err = fdt_resize_property_(fdt, nodeoffset, name, len, &prop); + if (err == -FDT_ERR_NOTFOUND) + err = fdt_add_property_(fdt, nodeoffset, name, len, &prop); + if (err) + return err; + + *prop_data = prop->data; + return 0; +} + +int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + void *prop_data; + int err; + + err = fdt_setprop_placeholder(fdt, nodeoffset, name, len, &prop_data); + if (err) + return err; + + if (len) + memcpy(prop_data, val, len); + return 0; +} + +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + struct fdt_property *prop; + int err, oldlen, newlen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (prop) { + newlen = len + oldlen; + err = fdt_splice_struct_(fdt, prop->data, + FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(newlen)); + if (err) + return err; + prop->len = cpu_to_fdt32(newlen); + memcpy(prop->data + oldlen, val, len); + } else { + err = fdt_add_property_(fdt, nodeoffset, name, len, &prop); + if (err) + return err; + memcpy(prop->data, val, len); + } + return 0; +} + +int fdt_delprop(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len, proplen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + proplen = sizeof(*prop) + FDT_TAGALIGN(len); + return fdt_splice_struct_(fdt, prop, proplen, 0); +} + +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen) +{ + struct fdt_node_header *nh; + int offset, nextoffset; + int nodelen; + int err; + uint32_t tag; + fdt32_t *endtag; + + FDT_RW_PROBE(fdt); + + offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); + if (offset >= 0) + return -FDT_ERR_EXISTS; + else if (offset != -FDT_ERR_NOTFOUND) + return offset; + + /* Try to place the new node after the parent's properties */ + tag = fdt_next_tag(fdt, parentoffset, &nextoffset); + /* the fdt_subnode_offset_namelen() should ensure this never hits */ + if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE)) + return -FDT_ERR_INTERNAL; + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + } while ((tag == FDT_PROP) || (tag == FDT_NOP)); + + nh = fdt_offset_ptr_w_(fdt, offset); + nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; + + err = fdt_splice_struct_(fdt, nh, 0, nodelen); + if (err) + return err; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); + memcpy(nh->name, name, namelen); + endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE); + *endtag = cpu_to_fdt32(FDT_END_NODE); + + return offset; +} + +int fdt_add_subnode(void *fdt, int parentoffset, const char *name) +{ + return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_del_node(void *fdt, int nodeoffset) +{ + int endoffset; + + FDT_RW_PROBE(fdt); + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset), + endoffset - nodeoffset, 0); +} + +static void fdt_packblocks_(const char *old, char *new, + int mem_rsv_size, + int struct_size, + int strings_size) +{ + int mem_rsv_off, struct_off, strings_off; + + mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8); + struct_off = mem_rsv_off + mem_rsv_size; + strings_off = struct_off + struct_size; + + memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size); + fdt_set_off_mem_rsvmap(new, mem_rsv_off); + + memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size); + fdt_set_off_dt_struct(new, struct_off); + fdt_set_size_dt_struct(new, struct_size); + + memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size); + fdt_set_off_dt_strings(new, strings_off); + fdt_set_size_dt_strings(new, fdt_size_dt_strings(old)); +} + +int fdt_open_into(const void *fdt, void *buf, int bufsize) +{ + int err; + int mem_rsv_size, struct_size; + int newsize; + const char *fdtstart = fdt; + const char *fdtend = fdtstart + fdt_totalsize(fdt); + char *tmp; + + FDT_RO_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + struct_size = fdt_size_dt_struct(fdt); + } else if (fdt_version(fdt) == 16) { + struct_size = 0; + while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END) + ; + if (struct_size < 0) + return struct_size; + } else { + return -FDT_ERR_BADVERSION; + } + + if (can_assume(LIBFDT_ORDER) || + !fdt_blocks_misordered_(fdt, mem_rsv_size, struct_size)) { + /* no further work necessary */ + err = fdt_move(fdt, buf, bufsize); + if (err) + return err; + fdt_set_version(buf, 17); + fdt_set_size_dt_struct(buf, struct_size); + fdt_set_totalsize(buf, bufsize); + return 0; + } + + /* Need to reorder */ + newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size + + struct_size + fdt_size_dt_strings(fdt); + + if (bufsize < newsize) + return -FDT_ERR_NOSPACE; + + /* First attempt to build converted tree at beginning of buffer */ + tmp = buf; + /* But if that overlaps with the old tree... */ + if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) { + /* Try right after the old tree instead */ + tmp = (char *)(uintptr_t)fdtend; + if ((tmp + newsize) > ((char *)buf + bufsize)) + return -FDT_ERR_NOSPACE; + } + + fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size, + fdt_size_dt_strings(fdt)); + memmove(buf, tmp, newsize); + + fdt_set_magic(buf, FDT_MAGIC); + fdt_set_totalsize(buf, bufsize); + fdt_set_version(buf, 17); + fdt_set_last_comp_version(buf, 16); + fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt)); + + return 0; +} + +int fdt_pack(void *fdt) +{ + int mem_rsv_size; + + FDT_RW_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt), + fdt_size_dt_strings(fdt)); + fdt_set_totalsize(fdt, fdt_data_size_(fdt)); + + return 0; +} diff --git a/clib/lib/libfdt/fdt_strerror.c b/clib/lib/libfdt/fdt_strerror.c new file mode 100644 index 0000000..d852b77 --- /dev/null +++ b/clib/lib/libfdt/fdt_strerror.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +struct fdt_errtabent { + const char *str; +}; + +#define FDT_ERRTABENT(val) \ + [(val)] = { .str = #val, } + +static struct fdt_errtabent fdt_errtable[] = { + FDT_ERRTABENT(FDT_ERR_NOTFOUND), + FDT_ERRTABENT(FDT_ERR_EXISTS), + FDT_ERRTABENT(FDT_ERR_NOSPACE), + + FDT_ERRTABENT(FDT_ERR_BADOFFSET), + FDT_ERRTABENT(FDT_ERR_BADPATH), + FDT_ERRTABENT(FDT_ERR_BADPHANDLE), + FDT_ERRTABENT(FDT_ERR_BADSTATE), + + FDT_ERRTABENT(FDT_ERR_TRUNCATED), + FDT_ERRTABENT(FDT_ERR_BADMAGIC), + FDT_ERRTABENT(FDT_ERR_BADVERSION), + FDT_ERRTABENT(FDT_ERR_BADSTRUCTURE), + FDT_ERRTABENT(FDT_ERR_BADLAYOUT), + FDT_ERRTABENT(FDT_ERR_INTERNAL), + FDT_ERRTABENT(FDT_ERR_BADNCELLS), + FDT_ERRTABENT(FDT_ERR_BADVALUE), + FDT_ERRTABENT(FDT_ERR_BADOVERLAY), + FDT_ERRTABENT(FDT_ERR_NOPHANDLES), + FDT_ERRTABENT(FDT_ERR_BADFLAGS), + FDT_ERRTABENT(FDT_ERR_ALIGNMENT), +}; +#define FDT_ERRTABSIZE ((int)(sizeof(fdt_errtable) / sizeof(fdt_errtable[0]))) + +const char *fdt_strerror(int errval) +{ + if (errval > 0) + return ""; + else if (errval == 0) + return ""; + else if (-errval < FDT_ERRTABSIZE) { + const char *s = fdt_errtable[-errval].str; + + if (s) + return s; + } + + return ""; +} diff --git a/clib/lib/libfdt/fdt_sw.c b/clib/lib/libfdt/fdt_sw.c new file mode 100644 index 0000000..4c569ee --- /dev/null +++ b/clib/lib/libfdt/fdt_sw.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_sw_probe_(void *fdt) +{ + if (!can_assume(VALID_INPUT)) { + if (fdt_magic(fdt) == FDT_MAGIC) + return -FDT_ERR_BADSTATE; + else if (fdt_magic(fdt) != FDT_SW_MAGIC) + return -FDT_ERR_BADMAGIC; + } + + return 0; +} + +#define FDT_SW_PROBE(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_(fdt)) != 0) \ + return err; \ + } + +/* 'memrsv' state: Initial state after fdt_create() + * + * Allowed functions: + * fdt_add_reservemap_entry() + * fdt_finish_reservemap() [moves to 'struct' state] + */ +static int fdt_sw_probe_memrsv_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && fdt_off_dt_strings(fdt) != 0) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_MEMRSV(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \ + return err; \ + } + +/* 'struct' state: Enter this state after fdt_finish_reservemap() + * + * Allowed functions: + * fdt_begin_node() + * fdt_end_node() + * fdt_property*() + * fdt_finish() [moves to 'complete' state] + */ +static int fdt_sw_probe_struct_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && + fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_STRUCT(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ + return err; \ + } + +static inline uint32_t sw_flags(void *fdt) +{ + /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ + return fdt_last_comp_version(fdt); +} + +/* 'complete' state: Enter this state after fdt_finish() + * + * Allowed functions: none + */ + +static void *fdt_grab_space_(void *fdt, size_t len) +{ + unsigned int offset = fdt_size_dt_struct(fdt); + unsigned int spaceleft; + + spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) + - fdt_size_dt_strings(fdt); + + if ((offset + len < offset) || (offset + len > spaceleft)) + return NULL; + + fdt_set_size_dt_struct(fdt, offset + len); + return fdt_offset_ptr_w_(fdt, offset); +} + +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) +{ + const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry)); + void *fdt = buf; + + if (bufsize < hdrsize) + return -FDT_ERR_NOSPACE; + + if (flags & ~FDT_CREATE_FLAGS_ALL) + return -FDT_ERR_BADFLAGS; + + memset(buf, 0, bufsize); + + /* + * magic and last_comp_version keep intermediate state during the fdt + * creation process, which is replaced with the proper FDT format by + * fdt_finish(). + * + * flags should be accessed with sw_flags(). + */ + fdt_set_magic(fdt, FDT_SW_MAGIC); + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); + fdt_set_last_comp_version(fdt, flags); + + fdt_set_totalsize(fdt, bufsize); + + fdt_set_off_mem_rsvmap(fdt, hdrsize); + fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); + fdt_set_off_dt_strings(fdt, 0); + + return 0; +} + +int fdt_create(void *buf, int bufsize) +{ + return fdt_create_with_flags(buf, bufsize, 0); +} + +int fdt_resize(void *fdt, void *buf, int bufsize) +{ + size_t headsize, tailsize; + char *oldtail, *newtail; + + FDT_SW_PROBE(fdt); + + if (bufsize < 0) + return -FDT_ERR_NOSPACE; + + headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + tailsize = fdt_size_dt_strings(fdt); + + if (!can_assume(VALID_DTB) && + headsize + tailsize > fdt_totalsize(fdt)) + return -FDT_ERR_INTERNAL; + + if ((headsize + tailsize) > (unsigned)bufsize) + return -FDT_ERR_NOSPACE; + + oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; + newtail = (char *)buf + bufsize - tailsize; + + /* Two cases to avoid clobbering data if the old and new + * buffers partially overlap */ + if (buf <= fdt) { + memmove(buf, fdt, headsize); + memmove(newtail, oldtail, tailsize); + } else { + memmove(newtail, oldtail, tailsize); + memmove(buf, fdt, headsize); + } + + fdt_set_totalsize(buf, bufsize); + if (fdt_off_dt_strings(buf)) + fdt_set_off_dt_strings(buf, bufsize); + + return 0; +} + +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) +{ + struct fdt_reserve_entry *re; + int offset; + + FDT_SW_PROBE_MEMRSV(fdt); + + offset = fdt_off_dt_struct(fdt); + if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + + re = (struct fdt_reserve_entry *)((char *)fdt + offset); + re->address = cpu_to_fdt64(addr); + re->size = cpu_to_fdt64(size); + + fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); + + return 0; +} + +int fdt_finish_reservemap(void *fdt) +{ + int err = fdt_add_reservemap_entry(fdt, 0, 0); + + if (err) + return err; + + fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); + return 0; +} + +int fdt_begin_node(void *fdt, const char *name) +{ + struct fdt_node_header *nh; + int namelen; + + FDT_SW_PROBE_STRUCT(fdt); + + namelen = strlen(name) + 1; + nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); + if (! nh) + return -FDT_ERR_NOSPACE; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memcpy(nh->name, name, namelen); + return 0; +} + +int fdt_end_node(void *fdt) +{ + fdt32_t *en; + + FDT_SW_PROBE_STRUCT(fdt); + + en = fdt_grab_space_(fdt, FDT_TAGSIZE); + if (! en) + return -FDT_ERR_NOSPACE; + + *en = cpu_to_fdt32(FDT_END_NODE); + return 0; +} + +static int fdt_add_string_(void *fdt, const char *s) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + unsigned int strtabsize = fdt_size_dt_strings(fdt); + unsigned int len = strlen(s) + 1; + unsigned int struct_top, offset; + + offset = strtabsize + len; + struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + if (fdt_totalsize(fdt) - offset < struct_top) + return 0; /* no more room :( */ + + memcpy(strtab - offset, s, len); + fdt_set_size_dt_strings(fdt, strtabsize + len); + return -offset; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int strtabsize = fdt_size_dt_strings(fdt); + int len = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, strtabsize - len); +} + +static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + int strtabsize = fdt_size_dt_strings(fdt); + const char *p; + + *allocated = 0; + + p = fdt_find_string_(strtab - strtabsize, strtabsize, s); + if (p) + return p - strtab; + + *allocated = 1; + + return fdt_add_string_(fdt, s); +} + +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) +{ + struct fdt_property *prop; + int nameoff; + int allocated; + + FDT_SW_PROBE_STRUCT(fdt); + + /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ + if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { + allocated = 1; + nameoff = fdt_add_string_(fdt, name); + } else { + nameoff = fdt_find_add_string_(fdt, name, &allocated); + } + if (nameoff == 0) + return -FDT_ERR_NOSPACE; + + prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); + if (! prop) { + if (allocated) + fdt_del_last_string_(fdt, name); + return -FDT_ERR_NOSPACE; + } + + prop->tag = cpu_to_fdt32(FDT_PROP); + prop->nameoff = cpu_to_fdt32(nameoff); + prop->len = cpu_to_fdt32(len); + *valp = prop->data; + return 0; +} + +int fdt_property(void *fdt, const char *name, const void *val, int len) +{ + void *ptr; + int ret; + + ret = fdt_property_placeholder(fdt, name, len, &ptr); + if (ret) + return ret; + memcpy(ptr, val, len); + return 0; +} + +int fdt_finish(void *fdt) +{ + char *p = (char *)fdt; + fdt32_t *end; + int oldstroffset, newstroffset; + uint32_t tag; + int offset, nextoffset; + + FDT_SW_PROBE_STRUCT(fdt); + + /* Add terminator */ + end = fdt_grab_space_(fdt, sizeof(*end)); + if (! end) + return -FDT_ERR_NOSPACE; + *end = cpu_to_fdt32(FDT_END); + + /* Relocate the string table */ + oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); + newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); + fdt_set_off_dt_strings(fdt, newstroffset); + + /* Walk the structure, correcting string offsets */ + offset = 0; + while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { + if (tag == FDT_PROP) { + struct fdt_property *prop = + fdt_offset_ptr_w_(fdt, offset); + int nameoff; + + nameoff = fdt32_to_cpu(prop->nameoff); + nameoff += fdt_size_dt_strings(fdt); + prop->nameoff = cpu_to_fdt32(nameoff); + } + offset = nextoffset; + } + if (nextoffset < 0) + return nextoffset; + + /* Finally, adjust the header */ + fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); + + /* And fix up fields that were keeping intermediate state. */ + fdt_set_last_comp_version(fdt, FDT_LAST_COMPATIBLE_VERSION); + fdt_set_magic(fdt, FDT_MAGIC); + + return 0; +} diff --git a/clib/lib/libfdt/fdt_wip.c b/clib/lib/libfdt/fdt_wip.c new file mode 100644 index 0000000..c2d7566 --- /dev/null +++ b/clib/lib/libfdt/fdt_wip.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len) +{ + void *propval; + int proplen; + + propval = fdt_getprop_namelen_w(fdt, nodeoffset, name, namelen, + &proplen); + if (!propval) + return proplen; + + if ((unsigned)proplen < (len + idx)) + return -FDT_ERR_NOSPACE; + + memcpy((char *)propval + idx, val, len); + return 0; +} + +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + const void *propval; + int proplen; + + propval = fdt_getprop(fdt, nodeoffset, name, &proplen); + if (!propval) + return proplen; + + if (proplen != len) + return -FDT_ERR_NOSPACE; + + return fdt_setprop_inplace_namelen_partial(fdt, nodeoffset, name, + strlen(name), 0, + val, len); +} + +static void fdt_nop_region_(void *start, int len) +{ + fdt32_t *p; + + for (p = start; (char *)p < ((char *)start + len); p++) + *p = cpu_to_fdt32(FDT_NOP); +} + +int fdt_nop_property(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len; + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + fdt_nop_region_(prop, len + sizeof(*prop)); + + return 0; +} + +int fdt_node_end_offset_(void *fdt, int offset) +{ + int depth = 0; + + while ((offset >= 0) && (depth >= 0)) + offset = fdt_next_node(fdt, offset, &depth); + + return offset; +} + +int fdt_nop_node(void *fdt, int nodeoffset) +{ + int endoffset; + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + fdt_nop_region_(fdt_offset_ptr_w(fdt, nodeoffset, 0), + endoffset - nodeoffset); + return 0; +} diff --git a/clib/lib/libfdt/libfdt.h b/clib/lib/libfdt/libfdt.h new file mode 100644 index 0000000..96782bc --- /dev/null +++ b/clib/lib/libfdt/libfdt.h @@ -0,0 +1,2214 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_H +#define LIBFDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FDT_FIRST_SUPPORTED_VERSION 0x02 +#define FDT_LAST_COMPATIBLE_VERSION 0x10 +#define FDT_LAST_SUPPORTED_VERSION 0x11 + +/* Error codes: informative error codes */ +#define FDT_ERR_NOTFOUND 1 + /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ +#define FDT_ERR_EXISTS 2 + /* FDT_ERR_EXISTS: Attempted to create a node or property which + * already exists */ +#define FDT_ERR_NOSPACE 3 + /* FDT_ERR_NOSPACE: Operation needed to expand the device + * tree, but its buffer did not have sufficient space to + * contain the expanded tree. Use fdt_open_into() to move the + * device tree to a buffer with more space. */ + +/* Error codes: codes for bad parameters */ +#define FDT_ERR_BADOFFSET 4 + /* FDT_ERR_BADOFFSET: Function was passed a structure block + * offset which is out-of-bounds, or which points to an + * unsuitable part of the structure for the operation. */ +#define FDT_ERR_BADPATH 5 + /* FDT_ERR_BADPATH: Function was passed a badly formatted path + * (e.g. missing a leading / for a function which requires an + * absolute path) */ +#define FDT_ERR_BADPHANDLE 6 + /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. + * This can be caused either by an invalid phandle property + * length, or the phandle value was either 0 or -1, which are + * not permitted. */ +#define FDT_ERR_BADSTATE 7 + /* FDT_ERR_BADSTATE: Function was passed an incomplete device + * tree created by the sequential-write functions, which is + * not sufficiently complete for the requested operation. */ + +/* Error codes: codes for bad device tree blobs */ +#define FDT_ERR_TRUNCATED 8 + /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly + * terminated (overflows, goes outside allowed bounds, or + * isn't properly terminated). */ +#define FDT_ERR_BADMAGIC 9 + /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a + * device tree at all - it is missing the flattened device + * tree magic number. */ +#define FDT_ERR_BADVERSION 10 + /* FDT_ERR_BADVERSION: Given device tree has a version which + * can't be handled by the requested operation. For + * read-write functions, this may mean that fdt_open_into() is + * required to convert the tree to the expected version. */ +#define FDT_ERR_BADSTRUCTURE 11 + /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt + * structure block or other serious error (e.g. misnested + * nodes, or subnodes preceding properties). */ +#define FDT_ERR_BADLAYOUT 12 + /* FDT_ERR_BADLAYOUT: For read-write functions, the given + * device tree has it's sub-blocks in an order that the + * function can't handle (memory reserve map, then structure, + * then strings). Use fdt_open_into() to reorganize the tree + * into a form suitable for the read-write operations. */ + +/* "Can't happen" error indicating a bug in libfdt */ +#define FDT_ERR_INTERNAL 13 + /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. + * Should never be returned, if it is, it indicates a bug in + * libfdt itself. */ + +/* Errors in device tree content */ +#define FDT_ERR_BADNCELLS 14 + /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells + * or similar property with a bad format or value */ + +#define FDT_ERR_BADVALUE 15 + /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected + * value. For example: a property expected to contain a string list + * is not NUL-terminated within the length of its value. */ + +#define FDT_ERR_BADOVERLAY 16 + /* FDT_ERR_BADOVERLAY: The device tree overlay, while + * correctly structured, cannot be applied due to some + * unexpected or missing value, property or node. */ + +#define FDT_ERR_NOPHANDLES 17 + /* FDT_ERR_NOPHANDLES: The device tree doesn't have any + * phandle available anymore without causing an overflow */ + +#define FDT_ERR_BADFLAGS 18 + /* FDT_ERR_BADFLAGS: The function was passed a flags field that + * contains invalid flags or an invalid combination of flags. */ + +#define FDT_ERR_ALIGNMENT 19 + /* FDT_ERR_ALIGNMENT: The device tree base address is not 8-byte + * aligned. */ + +#define FDT_ERR_MAX 19 + +/* constants */ +#define FDT_MAX_PHANDLE 0xfffffffe + /* Valid values for phandles range from 1 to 2^32-2. */ + +/**********************************************************************/ +/* Low-level functions (you probably don't need these) */ +/**********************************************************************/ + +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); +#endif +static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) +{ + return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); +} + +uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); + +/* + * External helpers to access words from a device tree blob. They're built + * to work even with unaligned pointers on platforms (such as ARMv5) that don't + * like unaligned loads and stores. + */ +static inline uint16_t fdt16_ld(const fdt16_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint16_t)bp[0] << 8) | bp[1]; +} + +static inline uint32_t fdt32_ld(const fdt32_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint32_t)bp[0] << 24) + | ((uint32_t)bp[1] << 16) + | ((uint32_t)bp[2] << 8) + | bp[3]; +} + +static inline void fdt32_st(void *property, uint32_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 24; + bp[1] = (value >> 16) & 0xff; + bp[2] = (value >> 8) & 0xff; + bp[3] = value & 0xff; +} + +static inline uint64_t fdt64_ld(const fdt64_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint64_t)bp[0] << 56) + | ((uint64_t)bp[1] << 48) + | ((uint64_t)bp[2] << 40) + | ((uint64_t)bp[3] << 32) + | ((uint64_t)bp[4] << 24) + | ((uint64_t)bp[5] << 16) + | ((uint64_t)bp[6] << 8) + | bp[7]; +} + +static inline void fdt64_st(void *property, uint64_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 56; + bp[1] = (value >> 48) & 0xff; + bp[2] = (value >> 40) & 0xff; + bp[3] = (value >> 32) & 0xff; + bp[4] = (value >> 24) & 0xff; + bp[5] = (value >> 16) & 0xff; + bp[6] = (value >> 8) & 0xff; + bp[7] = value & 0xff; +} + +/**********************************************************************/ +/* Traversal functions */ +/**********************************************************************/ + +int fdt_next_node(const void *fdt, int offset, int *depth); + +/** + * fdt_first_subnode() - get offset of first direct subnode + * @fdt: FDT blob + * @offset: Offset of node to check + * + * Return: offset of first subnode, or -FDT_ERR_NOTFOUND if there is none + */ +int fdt_first_subnode(const void *fdt, int offset); + +/** + * fdt_next_subnode() - get offset of next direct subnode + * @fdt: FDT blob + * @offset: Offset of previous subnode + * + * After first calling fdt_first_subnode(), call this function repeatedly to + * get direct subnodes of a parent node. + * + * Return: offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more + * subnodes + */ +int fdt_next_subnode(const void *fdt, int offset); + +/** + * fdt_for_each_subnode - iterate over all subnodes of a parent + * + * @node: child node (int, lvalue) + * @fdt: FDT blob (const void *) + * @parent: parent node (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_subnode(node, fdt, parent) { + * Use node + * ... + * } + * + * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and @node is used as + * iterator in the loop. The parent variable be constant or even a + * literal. + */ +#define fdt_for_each_subnode(node, fdt, parent) \ + for (node = fdt_first_subnode(fdt, parent); \ + node >= 0; \ + node = fdt_next_subnode(fdt, node)) + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ +#define fdt_get_header(fdt, field) \ + (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) +#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) +#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) +#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) +#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) +#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) +#define fdt_version(fdt) (fdt_get_header(fdt, version)) +#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) +#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) +#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) +#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) + +#define fdt_set_hdr_(name) \ + static inline void fdt_set_##name(void *fdt, uint32_t val) \ + { \ + struct fdt_header *fdth = (struct fdt_header *)fdt; \ + fdth->name = cpu_to_fdt32(val); \ + } +fdt_set_hdr_(magic); +fdt_set_hdr_(totalsize); +fdt_set_hdr_(off_dt_struct); +fdt_set_hdr_(off_dt_strings); +fdt_set_hdr_(off_mem_rsvmap); +fdt_set_hdr_(version); +fdt_set_hdr_(last_comp_version); +fdt_set_hdr_(boot_cpuid_phys); +fdt_set_hdr_(size_dt_strings); +fdt_set_hdr_(size_dt_struct); +#undef fdt_set_hdr_ + +/** + * fdt_header_size - return the size of the tree's header + * @fdt: pointer to a flattened device tree + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size(const void *fdt); + +/** + * fdt_header_size_ - internal function to get header size from a version number + * @version: device tree version number + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size_(uint32_t version); + +/** + * fdt_check_header - sanity check a device tree header + * @fdt: pointer to data which might be a flattened device tree + * + * fdt_check_header() checks that the given buffer contains what + * appears to be a flattened device tree, and that the header contains + * valid information (to the extent that can be determined from the + * header alone). + * + * returns: + * 0, if the buffer appears to contain a valid device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings, as above + */ +int fdt_check_header(const void *fdt); + +/** + * fdt_move - move a device tree around in memory + * @fdt: pointer to the device tree to move + * @buf: pointer to memory where the device is to be moved + * @bufsize: size of the memory space at buf + * + * fdt_move() relocates, if possible, the device tree blob located at + * fdt to the buffer at buf of size bufsize. The buffer may overlap + * with the existing device tree blob at fdt. Therefore, + * fdt_move(fdt, fdt, fdt_totalsize(fdt)) + * should always succeed. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_move(const void *fdt, void *buf, int bufsize); + +/**********************************************************************/ +/* Read-only functions */ +/**********************************************************************/ + +int fdt_check_full(const void *fdt, size_t bufsize); + +/** + * fdt_get_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * @lenp: optional pointer to return the string's length + * + * fdt_get_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt, and optionally also + * returns the string's length in *lenp. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); + +/** + * fdt_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * + * fdt_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_string(const void *fdt, int stroffset); + +/** + * fdt_find_max_phandle - find and return the highest phandle in a tree + * @fdt: pointer to the device tree blob + * @phandle: return location for the highest phandle value found in the tree + * + * fdt_find_max_phandle() finds the highest phandle value in the given device + * tree. The value returned in @phandle is only valid if the function returns + * success. + * + * returns: + * 0 on success or a negative error code on failure + */ +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_get_max_phandle - retrieves the highest phandle in a tree + * @fdt: pointer to the device tree blob + * + * fdt_get_max_phandle retrieves the highest phandle in the given + * device tree. This will ignore badly formatted phandles, or phandles + * with a value of 0 or -1. + * + * This function is deprecated in favour of fdt_find_max_phandle(). + * + * returns: + * the highest phandle on success + * 0, if no phandle was found in the device tree + * -1, if an error occurred + */ +static inline uint32_t fdt_get_max_phandle(const void *fdt) +{ + uint32_t phandle; + int err; + + err = fdt_find_max_phandle(fdt, &phandle); + if (err < 0) + return (uint32_t)-1; + + return phandle; +} + +/** + * fdt_generate_phandle - return a new, unused phandle for a device tree blob + * @fdt: pointer to the device tree blob + * @phandle: return location for the new phandle + * + * Walks the device tree blob and looks for the highest phandle value. On + * success, the new, unused phandle value (one higher than the previously + * highest phandle value in the device tree blob) will be returned in the + * @phandle parameter. + * + * Return: 0 on success or a negative error-code on failure + */ +int fdt_generate_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_num_mem_rsv - retrieve the number of memory reserve map entries + * @fdt: pointer to the device tree blob + * + * Returns the number of entries in the device tree blob's memory + * reservation map. This does not include the terminating 0,0 entry + * or any other (0,0) entries reserved for expansion. + * + * returns: + * the number of entries + */ +int fdt_num_mem_rsv(const void *fdt); + +/** + * fdt_get_mem_rsv - retrieve one memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: index of reserve map entry + * @address: pointer to 64-bit variable to hold the start address + * @size: pointer to 64-bit variable to hold the size of the entry + * + * On success, @address and @size will contain the address and size of + * the n-th reserve map entry from the device tree blob, in + * native-endian format. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); + +/** + * fdt_subnode_offset_namelen - find a subnode based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * @namelen: number of characters of name to consider + * + * Identical to fdt_subnode_offset(), but only examine the first + * namelen characters of name for matching the subnode name. This is + * useful for finding subnodes based on a portion of a larger string, + * such as a full path. + * + * Return: offset of the subnode or -FDT_ERR_NOTFOUND if name not found. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, + const char *name, int namelen); +#endif +/** + * fdt_subnode_offset - find a subnode of a given node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_subnode_offset() finds a subnode of the node at structure block + * offset parentoffset with the given name. name may include a unit + * address, in which case fdt_subnode_offset() will find the subnode + * with that unit address, or the unit address may be omitted, in + * which case fdt_subnode_offset() will find an arbitrary subnode + * whose name excluding unit address matches the given name. + * + * returns: + * structure block offset of the requested subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); + +/** + * fdt_path_offset_namelen - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * @namelen: number of characters of path to consider + * + * Identical to fdt_path_offset(), but only consider the first namelen + * characters of path as the path name. + * + * Return: offset of the node or negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); +#endif + +/** + * fdt_path_offset - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * + * fdt_path_offset() finds a node of a given path in the device tree. + * Each path component may omit the unit address portion, but the + * results of this are undefined if any such path component is + * ambiguous (that is if there are multiple nodes at the relevant + * level matching the given component, differentiated only by unit + * address). + * + * If the path is not absolute (i.e. does not begin with '/'), the + * first component is treated as an alias. That is, the property by + * that name is looked up in the /aliases node, and the value of that + * property used in place of that first component. + * + * For example, for this small fragment + * + * / { + * aliases { + * i2c2 = &foo; // RHS compiles to "/soc@0/i2c@30a40000/eeprom@52" + * }; + * soc@0 { + * foo: i2c@30a40000 { + * bar: eeprom@52 { + * }; + * }; + * }; + * }; + * + * these would be equivalent: + * + * /soc@0/i2c@30a40000/eeprom@52 + * i2c2/eeprom@52 + * + * returns: + * structure block offset of the node with the requested path (>=0), on + * success + * -FDT_ERR_BADPATH, given path does not begin with '/' and the first + * component is not a valid alias + * -FDT_ERR_NOTFOUND, if the requested node does not exist + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_path_offset(const void *fdt, const char *path); + +/** + * fdt_get_name - retrieve the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the starting node + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_name() retrieves the name (including unit address) of the + * device tree node at structure block offset nodeoffset. If lenp is + * non-NULL, the length of this name is also returned, in the integer + * pointed to by lenp. + * + * returns: + * pointer to the node's name, on success + * If lenp is non-NULL, *lenp contains the length of that name + * (>=0) + * NULL, on error + * if lenp is non-NULL *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); + +/** + * fdt_first_property_offset - find the offset of a node's first property + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * + * fdt_first_property_offset() finds the first property of the node at + * the given structure block offset. + * + * returns: + * structure block offset of the property (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested node has no properties + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_first_property_offset(const void *fdt, int nodeoffset); + +/** + * fdt_next_property_offset - step through a node's properties + * @fdt: pointer to the device tree blob + * @offset: structure block offset of a property + * + * fdt_next_property_offset() finds the property immediately after the + * one at the given structure block offset. This will be a property + * of the same node as the given property. + * + * returns: + * structure block offset of the next property (>=0), on success + * -FDT_ERR_NOTFOUND, if the given property is the last in its node + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_next_property_offset(const void *fdt, int offset); + +/** + * fdt_for_each_property_offset - iterate over all properties of a node + * + * @property: property offset (int, lvalue) + * @fdt: FDT blob (const void *) + * @node: node offset (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_property_offset(property, fdt, node) { + * Use property + * ... + * } + * + * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and property is used as + * iterator in the loop. The node variable can be constant or even a + * literal. + */ +#define fdt_for_each_property_offset(property, fdt, node) \ + for (property = fdt_first_property_offset(fdt, node); \ + property >= 0; \ + property = fdt_next_property_offset(fdt, property)) + +/** + * fdt_get_property_by_offset - retrieve the property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to retrieve + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property_by_offset() retrieves a pointer to the + * fdt_property structure within the device tree blob at the given + * offset. If lenp is non-NULL, the length of the property value is + * also returned, in the integer pointed to by lenp. + * + * Note that this code only works on device tree versions >= 16. fdt_getprop() + * works on all versions. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp); +static inline struct fdt_property *fdt_get_property_by_offset_w(void *fdt, + int offset, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property_by_offset(fdt, offset, lenp); +} + +/** + * fdt_get_property_namelen - find a property based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_get_property(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the structure representing the property, or NULL + * if not found + */ +#ifndef SWIG /* Not available in Python */ +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int nodeoffset, + const char *name, + int namelen, int *lenp); +#endif + +/** + * fdt_get_property - find a given property in a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property() retrieves a pointer to the fdt_property + * structure within the device tree blob corresponding to the property + * named 'name' of the node at offset nodeoffset. If lenp is + * non-NULL, the length of the property value is also returned, in the + * integer pointed to by lenp. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, + const char *name, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_getprop_by_offset - retrieve the value of a property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to read + * @namep: pointer to a string variable (will be overwritten) or NULL + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop_by_offset() retrieves a pointer to the value of the + * property at structure block offset 'offset' (this will be a pointer + * to within the device blob itself, not a copy of the value). If + * lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by lenp. If namep is non-NULL, + * the property's name will also be returned in the char * pointed to + * by namep (this will be a pointer to within the device tree's string + * block, not a new copy of the name). + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * if namep is non-NULL *namep contains a pointer to the property + * name. + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp); +#endif + +/** + * fdt_getprop_namelen - get property value based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_getprop(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the property's value or NULL on error + */ +#ifndef SWIG /* Not available in Python */ +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp); +static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, + const char *name, int namelen, + int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, + namelen, lenp); +} +#endif + +/** + * fdt_getprop - retrieve the value of a given property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop() retrieves a pointer to the value of the property + * named @name of the node at offset @nodeoffset (this will be a + * pointer to within the device blob itself, not a copy of the value). + * If @lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by @lenp. + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline void *fdt_getprop_w(void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_get_phandle - retrieve the phandle of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the node + * + * fdt_get_phandle() retrieves the phandle of the device tree node at + * structure block offset nodeoffset. + * + * returns: + * the phandle of the node at nodeoffset, on success (!= 0, != -1) + * 0, if the node has no phandle, or another error occurs + */ +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); + +/** + * fdt_get_alias_namelen - get alias based on substring + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_alias(), but only examine the first @namelen + * characters of @name for matching the alias name. + * + * Return: a pointer to the expansion of the alias named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_alias - retrieve the path referenced by a given alias + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * + * fdt_get_alias() retrieves the value of a given alias. That is, the + * value of the property named @name in the node /aliases. + * + * returns: + * a pointer to the expansion of the alias named 'name', if it exists + * NULL, if the given alias or the /aliases node does not exist + */ +const char *fdt_get_alias(const void *fdt, const char *name); + +/** + * fdt_get_symbol_namelen - get symbol based on substring + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_symbol(), but only examine the first @namelen + * characters of @name for matching the symbol name. + * + * Return: a pointer to the expansion of the symbol named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_symbol - retrieve the path referenced by a given symbol + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * + * fdt_get_symbol() retrieves the value of a given symbol. That is, + * the value of the property named @name in the node + * /__symbols__. Such a node exists only for a device tree blob that + * has been compiled with the -@ dtc option. Each property corresponds + * to a label appearing in the device tree source, with the name of + * the property being the label and the value being the full path of + * the node it is attached to. + * + * returns: + * a pointer to the expansion of the symbol named 'name', if it exists + * NULL, if the given symbol or the /__symbols__ node does not exist + */ +const char *fdt_get_symbol(const void *fdt, const char *name); + +/** + * fdt_get_path - determine the full path of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose path to find + * @buf: character buffer to contain the returned path (will be overwritten) + * @buflen: size of the character buffer at buf + * + * fdt_get_path() computes the full path of the node at offset + * nodeoffset, and records that path in the buffer at buf. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * 0, on success + * buf contains the absolute path of the node at + * nodeoffset, as a NUL-terminated string. + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) + * characters and will not fit in the given buffer. + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + +/** + * fdt_supernode_atdepth_offset - find a specific ancestor of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * @supernodedepth: depth of the ancestor to find + * @nodedepth: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_supernode_atdepth_offset() finds an ancestor of the given node + * at a specific depth from the root (where the root itself has depth + * 0, its immediate subnodes depth 1 and so forth). So + * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); + * will always return 0, the offset of the root node. If the node at + * nodeoffset has depth D, then: + * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); + * will return nodeoffset itself. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * structure block offset of the node at node offset's ancestor + * of depth supernodedepth (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of + * nodeoffset + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth); + +/** + * fdt_node_depth - find the depth of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_node_depth() finds the depth of a given node. The root node + * has depth 0, its immediate subnodes depth 1 and so forth. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * depth of the node at nodeoffset (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_depth(const void *fdt, int nodeoffset); + +/** + * fdt_parent_offset - find the parent of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_parent_offset() locates the parent node of a given node (that + * is, it finds the offset of the node which contains the node at + * nodeoffset as a subnode). + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset, *twice*. + * + * returns: + * structure block offset of the parent of the node at nodeoffset + * (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_parent_offset(const void *fdt, int nodeoffset); + +/** + * fdt_node_offset_by_prop_value - find nodes with a given property value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @propname: property name to check + * @propval: property value to search for + * @proplen: length of the value in propval + * + * fdt_node_offset_by_prop_value() returns the offset of the first + * node after startoffset, which has a property named propname whose + * value is of length proplen and has value equal to propval; or if + * startoffset is -1, the very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, + * propval, proplen); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, + * propval, proplen); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen); + +/** + * fdt_node_offset_by_phandle - find the node with a given phandle + * @fdt: pointer to the device tree blob + * @phandle: phandle value + * + * fdt_node_offset_by_phandle() returns the offset of the node + * which has the given phandle value. If there is more than one node + * in the tree with the given phandle (an invalid tree), results are + * undefined. + * + * returns: + * structure block offset of the located node (>= 0), on success + * -FDT_ERR_NOTFOUND, no node with that phandle exists + * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); + +/** + * fdt_node_check_compatible - check a node's compatible property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @compatible: string to match against + * + * fdt_node_check_compatible() returns 0 if the given node contains a + * @compatible property with the given string as one of its elements, + * it returns non-zero otherwise, or on error. + * + * returns: + * 0, if the node has a 'compatible' property listing the given string + * 1, if the node has a 'compatible' property, but it does not list + * the given string + * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property + * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible); + +/** + * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @compatible: 'compatible' string to match against + * + * fdt_node_offset_by_compatible() returns the offset of the first + * node after startoffset, which has a 'compatible' property which + * lists the given compatible string; or if startoffset is -1, the + * very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible); + +/** + * fdt_stringlist_contains - check a string list property for a string + * @strlist: Property containing a list of strings to check + * @listlen: Length of property + * @str: String to search for + * + * This is a utility function provided for convenience. The list contains + * one or more strings, each terminated by \0, as is found in a device tree + * "compatible" property. + * + * Return: 1 if the string is found in the list, 0 not found, or invalid list + */ +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); + +/** + * fdt_stringlist_count - count the number of strings in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * + * Return: + * the number of strings in the given property + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); + +/** + * fdt_stringlist_search - find a string in a string list and return its index + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @string: string to look up in the string list + * + * Note that it is possible for this function to succeed on property values + * that are not NUL-terminated. That's because the function will stop after + * finding the first occurrence of @string. This can for example happen with + * small-valued cell properties, such as #address-cells, when searching for + * the empty string. + * + * return: + * the index of the string in the list of strings + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist or does not contain + * the given string + */ +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string); + +/** + * fdt_stringlist_get() - obtain the string at a given index in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @index: index of the string to return + * @lenp: return location for the string length or an error code on failure + * + * Note that this will successfully extract strings from properties with + * non-NUL-terminated values. For example on small-valued cell properties + * this function will return the empty string. + * + * If non-NULL, the length of the string (on success) or a negative error-code + * (on failure) will be stored in the integer pointer to by lenp. + * + * Return: + * A pointer to the string at the given index in the string list or NULL on + * failure. On success the length of the string will be stored in the memory + * location pointed to by the lenp parameter, if non-NULL. On failure one of + * the following negative error codes will be returned in the lenp parameter + * (if non-NULL): + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int index, + int *lenp); + +/**********************************************************************/ +/* Read-only functions (addressing related) */ +/**********************************************************************/ + +/** + * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells + * + * This is the maximum value for #address-cells, #size-cells and + * similar properties that will be processed by libfdt. IEE1275 + * requires that OF implementations handle values up to 4. + * Implementations may support larger values, but in practice higher + * values aren't used. + */ +#define FDT_MAX_NCELLS 4 + +/** + * fdt_address_cells - retrieve address size for a bus represented in the tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address size for + * + * When the node has a valid #address-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 2, if the node has no #address-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_address_cells(const void *fdt, int nodeoffset); + +/** + * fdt_size_cells - retrieve address range size for a bus represented in the + * tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address range size for + * + * When the node has a valid #size-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 1, if the node has no #size-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #size-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_size_cells(const void *fdt, int nodeoffset); + + +/**********************************************************************/ +/* Write-in-place functions */ +/**********************************************************************/ + +/** + * fdt_setprop_inplace_namelen_partial - change a property's value, + * but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @idx: index of the property to change in the array + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * Identical to fdt_setprop_inplace(), but modifies the given property + * starting from the given index, and using only the first characters + * of the name. It is useful when you want to manipulate only one value of + * an array and you have a string that doesn't end with \0. + * + * Return: 0 on success, negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len); +#endif + +/** + * fdt_setprop_inplace - change a property's value, but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * fdt_setprop_inplace() replaces the value of a given property with + * the data in val, of length len. This function cannot change the + * size of a property, and so will only work if len is equal to the + * current length of the property. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if len is not equal to the property's current length + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len); +#endif + +/** + * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to replace the property with + * + * fdt_setprop_inplace_u32() replaces the value of a given property + * with the 32-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 4. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to replace the property with + * + * fdt_setprop_inplace_u64() replaces the value of a given property + * with the 64-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 8. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_cell - change the value of a single-cell property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node containing the property + * @name: name of the property to change the value of + * @val: new value of the 32-bit cell + * + * This is an alternative name for fdt_setprop_inplace_u32() + * Return: 0 on success, negative libfdt error number otherwise. + */ +static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_nop_property - replace a property with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_nop_property() will replace a given property's representation + * in the blob with FDT_NOP tags, effectively removing it from the + * tree. + * + * This function will alter only the bytes in the blob which contain + * the property, and will not alter or move any other part of the + * tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_property(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_nop_node - replace a node (subtree) with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_nop_node() will replace a given node's representation in the + * blob, including all its subnodes, if any, with FDT_NOP tags, + * effectively removing it from the tree. + * + * This function will alter only the bytes in the blob which contain + * the node and its properties and subnodes, and will not alter or + * move any other part of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_node(void *fdt, int nodeoffset); + +/**********************************************************************/ +/* Sequential write functions */ +/**********************************************************************/ + +/* fdt_create_with_flags flags */ +#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 + /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property + * names in the fdt. This can result in faster creation times, but + * a larger fdt. */ + +#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) + +/** + * fdt_create_with_flags - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. + * + * fdt_create_with_flags() begins the process of creating a new fdt with + * the sequential write interface. + * + * fdt creation process must end with fdt_finish() to produce a valid fdt. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + * -FDT_ERR_BADFLAGS, flags is not valid + */ +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); + +/** + * fdt_create - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * + * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + */ +int fdt_create(void *buf, int bufsize); + +int fdt_resize(void *fdt, void *buf, int bufsize); +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); +int fdt_finish_reservemap(void *fdt); +int fdt_begin_node(void *fdt, const char *name); +int fdt_property(void *fdt, const char *name, const void *val, int len); +static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} +static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} + +#ifndef SWIG /* Not available in Python */ +static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) +{ + return fdt_property_u32(fdt, name, val); +} +#endif + +/** + * fdt_property_placeholder - add a new property and return a ptr to its value + * + * @fdt: pointer to the device tree blob + * @name: name of property to add + * @len: length of property value in bytes + * @valp: returns a pointer to where where the value should be placed + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_NOSPACE, standard meanings + */ +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); + +#define fdt_property_string(fdt, name, str) \ + fdt_property(fdt, name, str, strlen(str)+1) +int fdt_end_node(void *fdt); +int fdt_finish(void *fdt); + +/**********************************************************************/ +/* Read-write functions */ +/**********************************************************************/ + +int fdt_create_empty_tree(void *buf, int bufsize); +int fdt_open_into(const void *fdt, void *buf, int bufsize); +int fdt_pack(void *fdt); + +/** + * fdt_add_mem_rsv - add one memory reserve map entry + * @fdt: pointer to the device tree blob + * @address: 64-bit start address of the reserve map entry + * @size: 64-bit size of the reserved region + * + * Adds a reserve map entry to the given blob reserving a region at + * address address of length size. + * + * This function will insert data into the reserve map and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new reservation entry + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); + +/** + * fdt_del_mem_rsv - remove a memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: entry to remove + * + * fdt_del_mem_rsv() removes the n-th memory reserve map entry from + * the blob. + * + * This function will delete data from the reservation table and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there + * are less than n+1 reserve map entries) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_mem_rsv(void *fdt, int n); + +/** + * fdt_set_name - change the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * @name: name to give the node + * + * fdt_set_name() replaces the name (including unit address, if any) + * of the given node with the given string. NOTE: this function can't + * efficiently check if the new name is unique amongst the given + * node's siblings; results are undefined if this function is invoked + * with a name equal to one of the given node's siblings. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob + * to contain the new name + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_set_name(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_setprop - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_setprop_placeholder - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholder() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_placeholder(void *fdt, int nodeoffset, const char *name, + int len, void **prop_data); + +/** + * fdt_setprop_u32 - set a property to a 32-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * fdt_setprop_u32() sets the value of the named property in the given + * node to the given 32-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_u64 - set a property to a 64-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value for the property (native endian) + * + * fdt_setprop_u64() sets the value of the named property in the given + * node to the given 64-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, + uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_cell - set a property to a single cell value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * This is an alternative name for fdt_setprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + return fdt_setprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_setprop_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value for the property + * + * fdt_setprop_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_string(fdt, nodeoffset, name, str) \ + fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + + +/** + * fdt_setprop_empty - set a property to an empty value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * + * fdt_setprop_empty() sets the value of the named property in the + * given node to an empty (zero length) value, or creates a new empty + * property if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_empty(fdt, nodeoffset, name) \ + fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) + +/** + * fdt_appendprop - append to or create a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to append to + * @val: pointer to data to append to the property value + * @len: length of the data to append to the property value + * + * fdt_appendprop() appends the value to the named property in the + * given node, creating the property if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_appendprop_u32 - append a 32-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u32() appends the given 32-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_u64 - append a 64-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u64() appends the given 64-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_cell - append a single cell value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * This is an alternative name for fdt_appendprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_appendprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_appendprop_string - append a string to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value to append to the property + * + * fdt_appendprop_string() appends the given string to the value of + * the named property in the given node, or creates a new property + * with that value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ + fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_appendprop_addrrange - append a address range property + * @fdt: pointer to the device tree blob + * @parent: offset of the parent node + * @nodeoffset: offset of the node to add a property at + * @name: name of property + * @addr: start address of a given range + * @size: size of a given range + * + * fdt_appendprop_addrrange() appends an address range value (start + * address and size) to the value of the named property in the given + * node, or creates a new property with that value if it does not + * already exist. + * + * Cell sizes are determined by parent's #address-cells and #size-cells. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain a new property + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size); + +/** + * fdt_delprop - delete a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_delprop() will delete the given property. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_delprop(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_add_subnode_namelen - creates a new node based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to create + * @namelen: number of characters of name to consider + * + * Identical to fdt_add_subnode(), but use only the first @namelen + * characters of @name as the name of the new node. This is useful for + * creating subnodes based on a portion of a larger string, such as a + * full path. + * + * Return: structure block offset of the created subnode (>=0), + * negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen); +#endif + +/** + * fdt_add_subnode - creates a new node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_add_subnode() creates a new node as a subnode of the node at + * structure block offset parentoffset, with the given name (which + * should include the unit address, if any). + * + * This function will insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * structure block offset of the created subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of + * the given name + * -FDT_ERR_NOSPACE, if there is insufficient free space in the + * blob to contain the new node + * -FDT_ERR_NOSPACE + * -FDT_ERR_BADLAYOUT + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_add_subnode(void *fdt, int parentoffset, const char *name); + +/** + * fdt_del_node - delete a node (subtree) + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_del_node() will remove the given node, including all its + * subnodes if any, from the blob. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_node(void *fdt, int nodeoffset); + +/** + * fdt_overlay_apply - Applies a DT overlay on a base DT + * @fdt: pointer to the base device tree blob + * @fdto: pointer to the device tree overlay blob + * + * fdt_overlay_apply() will apply the given device tree overlay on the + * given base device tree. + * + * Expect the base device tree to be modified, even if the function + * returns an error. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there's not enough space in the base device tree + * -FDT_ERR_NOTFOUND, the overlay points to some nonexistent nodes or + * properties in the base DT + * -FDT_ERR_BADPHANDLE, + * -FDT_ERR_BADOVERLAY, + * -FDT_ERR_NOPHANDLES, + * -FDT_ERR_INTERNAL, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADOFFSET, + * -FDT_ERR_BADPATH, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_overlay_apply(void *fdt, void *fdto); + +/** + * fdt_overlay_target_offset - retrieves the offset of a fragment's target + * @fdt: Base device tree blob + * @fdto: Device tree overlay blob + * @fragment_offset: node offset of the fragment in the overlay + * @pathp: pointer which receives the path of the target (or NULL) + * + * fdt_overlay_target_offset() retrieves the target offset in the base + * device tree of a fragment, no matter how the actual targeting is + * done (through a phandle or a path) + * + * returns: + * the targeted node offset in the base device tree + * Negative error code on error + */ +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp); + +/**********************************************************************/ +/* Debugging / informational functions */ +/**********************************************************************/ + +const char *fdt_strerror(int errval); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBFDT_H */ diff --git a/clib/lib/libfdt/libfdt_env.h b/clib/lib/libfdt/libfdt_env.h new file mode 100644 index 0000000..73b6d40 --- /dev/null +++ b/clib/lib/libfdt/libfdt_env.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_ENV_H +#define LIBFDT_ENV_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef __CHECKER__ +#define FDT_FORCE __attribute__((force)) +#define FDT_BITWISE __attribute__((bitwise)) +#else +#define FDT_FORCE +#define FDT_BITWISE +#endif + +typedef uint16_t FDT_BITWISE fdt16_t; +typedef uint32_t FDT_BITWISE fdt32_t; +typedef uint64_t FDT_BITWISE fdt64_t; + +#define EXTRACT_BYTE(x, n) ((unsigned long long)((uint8_t *)&x)[n]) +#define CPU_TO_FDT16(x) ((EXTRACT_BYTE(x, 0) << 8) | EXTRACT_BYTE(x, 1)) +#define CPU_TO_FDT32(x) ((EXTRACT_BYTE(x, 0) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \ + (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3)) +#define CPU_TO_FDT64(x) ((EXTRACT_BYTE(x, 0) << 56) | (EXTRACT_BYTE(x, 1) << 48) | \ + (EXTRACT_BYTE(x, 2) << 40) | (EXTRACT_BYTE(x, 3) << 32) | \ + (EXTRACT_BYTE(x, 4) << 24) | (EXTRACT_BYTE(x, 5) << 16) | \ + (EXTRACT_BYTE(x, 6) << 8) | EXTRACT_BYTE(x, 7)) + +static inline uint16_t fdt16_to_cpu(fdt16_t x) +{ + return (FDT_FORCE uint16_t)CPU_TO_FDT16(x); +} +static inline fdt16_t cpu_to_fdt16(uint16_t x) +{ + return (FDT_FORCE fdt16_t)CPU_TO_FDT16(x); +} + +static inline uint32_t fdt32_to_cpu(fdt32_t x) +{ + return (FDT_FORCE uint32_t)CPU_TO_FDT32(x); +} +static inline fdt32_t cpu_to_fdt32(uint32_t x) +{ + return (FDT_FORCE fdt32_t)CPU_TO_FDT32(x); +} + +static inline uint64_t fdt64_to_cpu(fdt64_t x) +{ + return (FDT_FORCE uint64_t)CPU_TO_FDT64(x); +} +static inline fdt64_t cpu_to_fdt64(uint64_t x) +{ + return (FDT_FORCE fdt64_t)CPU_TO_FDT64(x); +} +#undef CPU_TO_FDT64 +#undef CPU_TO_FDT32 +#undef CPU_TO_FDT16 +#undef EXTRACT_BYTE + +#ifdef __APPLE__ +#include + +/* strnlen() is not available on Mac OS < 10.7 */ +# if !defined(MAC_OS_X_VERSION_10_7) || (MAC_OS_X_VERSION_MAX_ALLOWED < \ + MAC_OS_X_VERSION_10_7) + +#define strnlen fdt_strnlen + +/* + * fdt_strnlen: returns the length of a string or max_count - which ever is + * smallest. + * Input 1 string: the string whose size is to be determined + * Input 2 max_count: the maximum value returned by this function + * Output: length of the string or max_count (the smallest of the two) + */ +static inline size_t fdt_strnlen(const char *string, size_t max_count) +{ + const char *p = memchr(string, 0, max_count); + return p ? p - string : max_count; +} + +#endif /* !defined(MAC_OS_X_VERSION_10_7) || (MAC_OS_X_VERSION_MAX_ALLOWED < + MAC_OS_X_VERSION_10_7) */ + +#endif /* __APPLE__ */ + +#endif /* LIBFDT_ENV_H */ diff --git a/clib/lib/libfdt/libfdt_internal.h b/clib/lib/libfdt/libfdt_internal.h new file mode 100644 index 0000000..16bda19 --- /dev/null +++ b/clib/lib/libfdt/libfdt_internal.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_INTERNAL_H +#define LIBFDT_INTERNAL_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include + +#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) + +int32_t fdt_ro_probe_(const void *fdt); +#define FDT_RO_PROBE(fdt) \ + { \ + int32_t totalsize_; \ + if ((totalsize_ = fdt_ro_probe_(fdt)) < 0) \ + return totalsize_; \ + } + +int fdt_check_node_offset_(const void *fdt, int offset); +int fdt_check_prop_offset_(const void *fdt, int offset); +const char *fdt_find_string_(const char *strtab, int tabsize, const char *s); +int fdt_node_end_offset_(void *fdt, int nodeoffset); + +static inline const void *fdt_offset_ptr_(const void *fdt, int offset) +{ + return (const char *)fdt + fdt_off_dt_struct(fdt) + offset; +} + +static inline void *fdt_offset_ptr_w_(void *fdt, int offset) +{ + return (void *)(uintptr_t)fdt_offset_ptr_(fdt, offset); +} + +static inline const struct fdt_reserve_entry *fdt_mem_rsv_(const void *fdt, int n) +{ + const struct fdt_reserve_entry *rsv_table = + (const struct fdt_reserve_entry *) + ((const char *)fdt + fdt_off_mem_rsvmap(fdt)); + + return rsv_table + n; +} +static inline struct fdt_reserve_entry *fdt_mem_rsv_w_(void *fdt, int n) +{ + return (void *)(uintptr_t)fdt_mem_rsv_(fdt, n); +} + +/* + * Internal helpers to access tructural elements of the device tree + * blob (rather than for exaple reading integers from within property + * values). We assume that we are either given a naturally aligned + * address for the platform or if we are not, we are on a platform + * where unaligned memory reads will be handled in a graceful manner. + * If not the external helpers fdtXX_ld() from libfdt.h can be used + * instead. + */ +static inline uint32_t fdt32_ld_(const fdt32_t *p) +{ + return fdt32_to_cpu(*p); +} + +static inline uint64_t fdt64_ld_(const fdt64_t *p) +{ + return fdt64_to_cpu(*p); +} + +#define FDT_SW_MAGIC (~FDT_MAGIC) + +/**********************************************************************/ +/* Checking controls */ +/**********************************************************************/ + +#ifndef FDT_ASSUME_MASK +#define FDT_ASSUME_MASK 0 +#endif + +/* + * Defines assumptions which can be enabled. Each of these can be enabled + * individually. For maximum safety, don't enable any assumptions! + * + * For minimal code size and no safety, use ASSUME_PERFECT at your own risk. + * You should have another method of validating the device tree, such as a + * signature or hash check before using libfdt. + * + * For situations where security is not a concern it may be safe to enable + * ASSUME_SANE. + */ +enum { + /* + * This does essentially no checks. Only the latest device-tree + * version is correctly handled. Inconsistencies or errors in the device + * tree may cause undefined behaviour or crashes. Invalid parameters + * passed to libfdt may do the same. + * + * If an error occurs when modifying the tree it may leave the tree in + * an intermediate (but valid) state. As an example, adding a property + * where there is insufficient space may result in the property name + * being added to the string table even though the property itself is + * not added to the struct section. + * + * Only use this if you have a fully validated device tree with + * the latest supported version and wish to minimise code size. + */ + ASSUME_PERFECT = 0xff, + + /* + * This assumes that the device tree is sane. i.e. header metadata + * and basic hierarchy are correct. + * + * With this assumption enabled, normal device trees produced by libfdt + * and the compiler should be handled safely. Malicious device trees and + * complete garbage may cause libfdt to behave badly or crash. Truncated + * device trees (e.g. those only partially loaded) can also cause + * problems. + * + * Note: Only checks that relate exclusively to the device tree itself + * (not the parameters passed to libfdt) are disabled by this + * assumption. This includes checking headers, tags and the like. + */ + ASSUME_VALID_DTB = 1 << 0, + + /* + * This builds on ASSUME_VALID_DTB and further assumes that libfdt + * functions are called with valid parameters, i.e. not trigger + * FDT_ERR_BADOFFSET or offsets that are out of bounds. It disables any + * extensive checking of parameters and the device tree, making various + * assumptions about correctness. + * + * It doesn't make sense to enable this assumption unless + * ASSUME_VALID_DTB is also enabled. + */ + ASSUME_VALID_INPUT = 1 << 1, + + /* + * This disables checks for device-tree version and removes all code + * which handles older versions. + * + * Only enable this if you know you have a device tree with the latest + * version. + */ + ASSUME_LATEST = 1 << 2, + + /* + * This assumes that it is OK for a failed addition to the device tree, + * due to lack of space or some other problem, to skip any rollback + * steps (such as dropping the property name from the string table). + * This is safe to enable in most circumstances, even though it may + * leave the tree in a sub-optimal state. + */ + ASSUME_NO_ROLLBACK = 1 << 3, + + /* + * This assumes that the device tree components appear in a 'convenient' + * order, i.e. the memory reservation block first, then the structure + * block and finally the string block. + * + * This order is not specified by the device-tree specification, + * but is expected by libfdt. The device-tree compiler always created + * device trees with this order. + * + * This assumption disables a check in fdt_open_into() and removes the + * ability to fix the problem there. This is safe if you know that the + * device tree is correctly ordered. See fdt_blocks_misordered_(). + */ + ASSUME_LIBFDT_ORDER = 1 << 4, + + /* + * This assumes that libfdt itself does not have any internal bugs. It + * drops certain checks that should never be needed unless libfdt has an + * undiscovered bug. + * + * This can generally be considered safe to enable. + */ + ASSUME_LIBFDT_FLAWLESS = 1 << 5, +}; + +/** + * can_assume_() - check if a particular assumption is enabled + * + * @mask: Mask to check (ASSUME_...) + * @return true if that assumption is enabled, else false + */ +static inline bool can_assume_(int mask) +{ + return FDT_ASSUME_MASK & mask; +} + +/** helper macros for checking assumptions */ +#define can_assume(_assume) can_assume_(ASSUME_ ## _assume) + +#endif /* LIBFDT_INTERNAL_H */ diff --git a/clib/lib/libfdt/meson.build b/clib/lib/libfdt/meson.build new file mode 100644 index 0000000..bf8343f --- /dev/null +++ b/clib/lib/libfdt/meson.build @@ -0,0 +1,66 @@ +version_script = '-Wl,--version-script=@0@'.format(meson.current_source_dir() / 'version.lds') +if not cc.has_link_argument(version_script) + version_script = [] +endif + +sources = files( + 'fdt.c', + 'fdt_addresses.c', + 'fdt_check.c', + 'fdt_empty_tree.c', + 'fdt_overlay.c', + 'fdt_ro.c', + 'fdt_rw.c', + 'fdt_strerror.c', + 'fdt_sw.c', + 'fdt_wip.c', +) + +link_args = [] +if cc.has_link_argument('-Wl,--no-undefined') + link_args += '-Wl,--no-undefined' +else + # -undefined error is the equivalent of --no-undefined for the macOS linker, + # but -undefined would also be understood as a valid argument for GNU ld! + link_args += cc.get_supported_link_arguments('-Wl,-undefined,error') +endif + +link_args += version_script +libfdt = both_libraries( + 'fdt', sources, + version: meson.project_version(), + link_args: link_args, + link_depends: 'version.lds', + install: true, +) + +if static_build + link_with = libfdt.get_static_lib() +else + link_with = libfdt.get_shared_lib() +endif + +libfdt_inc = include_directories('.') + +libfdt_dep = declare_dependency( + include_directories: libfdt_inc, + link_with: link_with, +) + +install_headers( + files( + 'fdt.h', + 'libfdt.h', + 'libfdt_env.h', + ) +) + +pkgconfig = import('pkgconfig') + +pkgconfig.generate( + libraries: libfdt, + version: meson.project_version(), + filebase: 'libfdt', + name: 'libfdt', + description: 'Flat Device Tree manipulation', +) diff --git a/clib/lib/libfdt/version.lds b/clib/lib/libfdt/version.lds new file mode 100644 index 0000000..989cd89 --- /dev/null +++ b/clib/lib/libfdt/version.lds @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +LIBFDT_1.2 { + global: + fdt_next_node; + fdt_check_header; + fdt_move; + fdt_string; + fdt_num_mem_rsv; + fdt_get_mem_rsv; + fdt_subnode_offset_namelen; + fdt_subnode_offset; + fdt_path_offset_namelen; + fdt_path_offset; + fdt_get_name; + fdt_get_property_namelen; + fdt_get_property; + fdt_getprop_namelen; + fdt_getprop; + fdt_get_phandle; + fdt_get_alias_namelen; + fdt_get_alias; + fdt_get_path; + fdt_header_size; + fdt_supernode_atdepth_offset; + fdt_node_depth; + fdt_parent_offset; + fdt_node_offset_by_prop_value; + fdt_node_offset_by_phandle; + fdt_node_check_compatible; + fdt_node_offset_by_compatible; + fdt_setprop_inplace; + fdt_nop_property; + fdt_nop_node; + fdt_create; + fdt_add_reservemap_entry; + fdt_finish_reservemap; + fdt_begin_node; + fdt_property; + fdt_end_node; + fdt_finish; + fdt_open_into; + fdt_pack; + fdt_add_mem_rsv; + fdt_del_mem_rsv; + fdt_set_name; + fdt_setprop; + fdt_delprop; + fdt_add_subnode_namelen; + fdt_add_subnode; + fdt_del_node; + fdt_strerror; + fdt_offset_ptr; + fdt_next_tag; + fdt_appendprop; + fdt_create_empty_tree; + fdt_first_property_offset; + fdt_get_property_by_offset; + fdt_getprop_by_offset; + fdt_next_property_offset; + fdt_first_subnode; + fdt_next_subnode; + fdt_address_cells; + fdt_size_cells; + fdt_stringlist_contains; + fdt_stringlist_count; + fdt_stringlist_search; + fdt_stringlist_get; + fdt_resize; + fdt_overlay_apply; + fdt_get_string; + fdt_find_max_phandle; + fdt_generate_phandle; + fdt_check_full; + fdt_setprop_placeholder; + fdt_property_placeholder; + fdt_header_size_; + fdt_appendprop_addrrange; + fdt_setprop_inplace_namelen_partial; + fdt_create_with_flags; + fdt_overlay_target_offset; + fdt_get_symbol; + fdt_get_symbol_namelen; + local: + *; +}; diff --git a/clib/lib/lwext4/.clang-format b/clib/lib/lwext4/.clang-format new file mode 100644 index 0000000..bb6914d --- /dev/null +++ b/clib/lib/lwext4/.clang-format @@ -0,0 +1,8 @@ +#clang-format-3.7 -style=file -i lwext4/* + +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: Always +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false diff --git a/clib/lib/lwext4/.gitignore b/clib/lib/lwext4/.gitignore new file mode 100644 index 0000000..787a9e5 --- /dev/null +++ b/clib/lib/lwext4/.gitignore @@ -0,0 +1,11 @@ +build_*/ +ext_images/ + +.cproject +.project +.settings/ + +.atom-gdb.json +.build-tools.cson +.gdbinit +tags diff --git a/clib/lib/lwext4/.travis.yml b/clib/lib/lwext4/.travis.yml new file mode 100644 index 0000000..4926786 --- /dev/null +++ b/clib/lib/lwext4/.travis.yml @@ -0,0 +1,35 @@ + +language: c +compiler: gcc +sudo: required +dist: trusty + +install: + - uname -a + - sudo rm /var/lib/apt/lists/* -vfr + - sudo apt-get update -qq + - sudo apt-get install -qq cmake + - wget http://www.freddiechopin.info/en/download/category/11-bleeding-edge-toolchain?download=139%3Ableeding-edge-toolchain-151225-64-bit-linux -O /tmp/gcc-arm-none-eabi-5_3-151225-linux-x64.tar.xz + - tar -xf /tmp/gcc-arm-none-eabi-5_3-151225-linux-x64.tar.xz -C /tmp/ + - export SAVED_PATH=$PATH + +script: + - gcc --version + - make generic + - cd build_generic && make -j`nproc` + - cd .. + - export PATH=/tmp/gcc-arm-none-eabi-5_3-151225/bin:$SAVED_PATH + - arm-none-eabi-gcc --version + - make cortex-m4 + - cd build_cortex-m4 && make -j`nproc` + - cd .. + - make cortex-m3 + - cd build_cortex-m3 && make -j`nproc` + - cd .. + - make cortex-m0 + - cd build_cortex-m0 && make -j`nproc` + - cd .. + +notifications: + on_success: change + on_failure: always diff --git a/clib/lib/lwext4/CHANGELOG b/clib/lib/lwext4/CHANGELOG new file mode 100644 index 0000000..e4da0c0 --- /dev/null +++ b/clib/lib/lwext4/CHANGELOG @@ -0,0 +1,69 @@ +lwext4-1.0.0 +============ +* new extent module implementation (handle unwritten extents correctly) +* xattr support +* journaling transactions & recover support +* improve configurations (with automatic generated config file) +* move stm32disco demo to separate repository +* test suite & tools improvements (more tests on autogenerated images) +* new filesystem tools: lwext4-mkfs, lwext4-mbr +* travis continious integration +* lot of bugfixes and minor improvements... + + +lwext4-0.8.0 +============ +* improve ext4_dir_entry_next +* clang code format based on config file +* ChibiOS demo for some stm32 boards +* Improve includes in lwext4 dir +* Add some const keyword where should be used + + +lwext4-0.7.0 +============ +* features supported: flex_bg, uninit_bg, dir_nlink +* config file improvements, 3 basic build modes: + * feature set ext2 - small footprint (~20KB .text) + * feature set ext3 - htree directory indexing (~25KB .text) + * feature set ext4 - all supported features enabled (~30KB .text) +* IO timing stats in stm32f429_demo +* more advanced cases in test suite +* support for meta_bg feature (unstable) +* crc32c module for meta_csum feature (not supported yet) +* small demo application improvments (readability) + + +lwext4-0.6.0 +============ +* Fixed stm32429demo enumerating issues +* Comb sort for directory indexing +* Cmake toolchain files for msp430 + +lwext4-0.5.0 +============ +* Build system refactoring +* Pedantic warning check for lwext4 files +* New toolchain files for cortex-m0, avexmega7, arm-sim +* Merge bugfixes from HelenOS mainline +* OS locks setup function + +lwext4-0.4.0 +============ +* Client-server based automatic test suite + +lwext4-0.3.0 +============ +* STM32F429 demo + +lwext4-0.2.0 +============ +* Full extents support +* Doxygen comments +* Bug fixing +* Demo app improvments + + +lwext4-0.1.1 +============ +* First version of the lwext4 filesystem library. diff --git a/clib/lib/lwext4/CMakeLists.txt b/clib/lib/lwext4/CMakeLists.txt new file mode 100644 index 0000000..d93bfe4 --- /dev/null +++ b/clib/lib/lwext4/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(lwext4 OBJECT) + +target_include_directories(lwext4 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +file(GLOB_RECURSE SRC + ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c +) +target_sources(lwext4 PRIVATE ${SRC}) diff --git a/clib/lib/lwext4/CMakeLists.txt.old b/clib/lib/lwext4/CMakeLists.txt.old new file mode 100644 index 0000000..bec7da6 --- /dev/null +++ b/clib/lib/lwext4/CMakeLists.txt.old @@ -0,0 +1,99 @@ +project(lwext4 C) +cmake_minimum_required(VERSION 3.4) + +include_directories(include) +include_directories(${PROJECT_BINARY_DIR}/include) +include_directories(blockdev/filedev) +include_directories(blockdev/filedev_win) + +set(BLOCKDEV_TYPE none) + +add_definitions(-DCONFIG_USE_DEFAULT_CONFIG=0) +add_definitions(-DVERSION="${VERSION}") + +#Examples +if (CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m0) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m3) + add_definitions(-DCONFIG_UNALIGNED_ACCESS=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL arm-sim) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m4) + add_definitions(-DCONFIG_UNALIGNED_ACCESS=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL bf518) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL avrxmega7) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL msp430g2210) + add_definitions(-DCONFIG_DEBUG_PRINTF=0) + add_definitions(-DCONFIG_DEBUG_ASSERT=0) + #... +elseif(LIB_ONLY) + add_definitions(-DCONFIG_DEBUG_PRINTF=0) + add_definitions(-DCONFIG_DEBUG_ASSERT=0) + add_definitions(-DCONFIG_HAVE_OWN_OFLAGS=1) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=0) + add_definitions(-DCONFIG_BLOCK_DEV_CACHE_SIZE=16) +else() + #Generic example target + if (WIN32) + set(BLOCKDEV_TYPE windows) + else() + set(BLOCKDEV_TYPE linux) + endif() + set (INSTALL_LIB 1) + add_definitions(-DCONFIG_HAVE_OWN_OFLAGS=0) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=0) + add_definitions(-DCONFIG_HAVE_OWN_ASSERT=0) + add_definitions(-DCONFIG_BLOCK_DEV_CACHE_SIZE=16) + add_subdirectory(fs_test) +endif() + +macro(output_configure) + get_property( + definitions + DIRECTORY + PROPERTY COMPILE_DEFINITIONS + ) + file(WRITE + ${PROJECT_BINARY_DIR}/include/generated/ext4_config.h + "") + foreach(item ${definitions}) + string(REGEX MATCH "^CONFIG_" match_res ${item}) + if(match_res) + string(REGEX REPLACE "=(.+)$" "" replace_res ${item}) + string(CONFIGURE + "#define ${replace_res} ${CMAKE_MATCH_1}" + output_str) + file(APPEND + ${PROJECT_BINARY_DIR}/include/generated/ext4_config.h + "${output_str}\n") + endif() + endforeach() +endmacro() +output_configure() + +add_subdirectory(blockdev) + +#Library build +add_subdirectory(src) +#Detect all possible warnings for lwext4 target +if (NOT CMAKE_COMPILER_IS_GNUCC) + set_target_properties(lwext4 PROPERTIES COMPILE_FLAGS "") +else() + set_target_properties(lwext4 PROPERTIES COMPILE_FLAGS "-Wall -Wextra -pedantic") +endif() + +#DISTRIBUTION +set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}") +set(CPACK_SOURCE_GENERATOR "TBZ2") +set(CPACK_SOURCE_PACKAGE_FILE_NAME + "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set(CPACK_SOURCE_IGNORE_FILES +"/build" ".git") +include(CPack) + + +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) diff --git a/clib/lib/lwext4/LICENSE b/clib/lib/lwext4/LICENSE new file mode 100644 index 0000000..ca992f8 --- /dev/null +++ b/clib/lib/lwext4/LICENSE @@ -0,0 +1,345 @@ + +Some files in lwext4 contain a different license statement. Those +files are licensed under the license contained in the file itself. + +----------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/clib/lib/lwext4/Makefile b/clib/lib/lwext4/Makefile new file mode 100644 index 0000000..12ee5c7 --- /dev/null +++ b/clib/lib/lwext4/Makefile @@ -0,0 +1,84 @@ + +#Release +#Debug +BUILD_TYPE = Release + +ifneq ($(shell test -d .git), 0) +GIT_SHORT_HASH:= $(shell git rev-parse --short HEAD) +endif + +VERSION_MAJOR = 1 +VERSION_MINOR = 0 +VERSION_PATCH = 0 + +VERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)-$(GIT_SHORT_HASH) + +COMMON_DEFINITIONS = \ + -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ + -DVERSION_MAJOR=$(VERSION_MAJOR) \ + -DVERSION_MINOR=$(VERSION_MINOR) \ + -DVERSION_PATCH=$(VERSION_PATCH) \ + -DVERSION=$(VERSION) \ + +define generate_common + rm -R -f build_$(1) + mkdir build_$(1) + cd build_$(1) && cmake -G"Unix Makefiles" \ + $(COMMON_DEFINITIONS) \ + $(2) \ + -DCMAKE_TOOLCHAIN_FILE=../toolchain/$(1).cmake .. +endef + +generic: + $(call generate_common,$@) + +cortex-m0: + $(call generate_common,$@) + +cortex-m0+: + $(call generate_common,$@) + +cortex-m3: + $(call generate_common,$@) + +cortex-m4: + $(call generate_common,$@) + +cortex-m4f: + $(call generate_common,$@) + +cortex-m7: + $(call generate_common,$@) + +arm-sim: + $(call generate_common,$@) + +avrxmega7: + $(call generate_common,$@) + +msp430: + $(call generate_common,$@) + +mingw: + $(call generate_common,$@,-DWIN32=1) + +lib_only: + rm -R -f build_lib_only + mkdir build_lib_only + cd build_lib_only && cmake $(COMMON_DEFINITIONS) -DLIB_ONLY=TRUE .. + +all: + generic + +clean: + rm -R -f build_* + rm -R -f ext_images + + +include fs_test.mk + + + + + + diff --git a/clib/lib/lwext4/README.md b/clib/lib/lwext4/README.md new file mode 100644 index 0000000..3abd860 --- /dev/null +++ b/clib/lib/lwext4/README.md @@ -0,0 +1,244 @@ +[![Join the chat at https://gitter.im/gkostka/lwext4](https://badges.gitter.im/gkostka/lwext4.svg)](https://gitter.im/gkostka/lwext4?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![License (GPL v2.0)](https://img.shields.io/badge/license-GPL%20(v2.0)-blue.svg?style=flat-square)](http://opensource.org/licenses/GPL-2.0) +[![Build Status](https://travis-ci.org/gkostka/lwext4.svg)](https://travis-ci.org/gkostka/lwext4) +[![](http://img.shields.io/gratipay/user/gkostka.svg)](https://gratipay.com/gkostka/) + +![lwext4](https://cloud.githubusercontent.com/assets/8606098/11697327/68306d88-9eb9-11e5-8807-81a2887f077e.png) + +About +===== + + +The main goal of the lwext4 project is to provide ext2/3/4 filesystem for microcontrollers. It may be an interesting alternative for traditional MCU filesystem libraries (mostly based on FAT32). Library has some cool and unique features in microcontrollers world: + - directory indexing - fast file find and list operations + - extents - fast big file truncate + - journaling transactions & recovery - power loss resistance + +Lwext4 is an excellent choice for SD/MMC card, USB flash drive or any other wear +leveled memory types. However it is not good for raw flash devices. + +Feel free to contact me: +kostka.grzegorz@gmail.com + +Credits +===== + +The most of the source code of lwext4 was taken from HelenOS: +* http://helenos.org/ + +Some features are based on FreeBSD and Linux implementations. + +KaHo Ng (https://github.com/ngkaho1234): +* advanced extents implementation +* xattr support +* metadata checksum support +* journal recovery & transactions +* many bugfixes & improvements + +Lwext4 could be used also as fuse internals. Here is a nice project which uses lwext4 as a filesystem base: +* https://github.com/ngkaho1234/fuse-lwext4 + +Some of the source files are licensed under GPLv2. It makes whole +lwext4 GPLv2 licensed. To use library as a BSD3, GPLv2 licensed source +files must be removed first. At this point there are two files +licensed under GPLv2: +* ext4_xattr.c +* ext4_extents.c + +All other modules and headers are BSD-3-Clause licensed code. + + +Features +===== + +* filetypes: regular, directories, softlinks +* support for hardlinks +* multiple blocksize supported: 1KB, 2KB, 4KB ... 64KB +* little/big endian architectures supported +* multiple configurations (ext2/ext3/ext4) +* only C standard library dependency +* various CPU architectures supported (x86/64, cortex-mX, msp430 ...) +* small memory footprint +* flexible configurations + +Memory footprint +------------ + +Advanced ext4 filesystem features, like extents or journaling require some memory. +However most of the memory expensive features could be disabled at compile time. +Here is a brief summary for cortex-m4 processor: + +* .text: 20KB - only ext2 fs support , 50KB - full ext4 fs feature set +* .data: 8KB - minimum 8 x 1KB block cache, 12KB - when journaling and extents are enabled +* .stack: 2KB - is enough (not measured precisely) + +Blocks are allocated dynamically. Previous versions of library could work without +malloc but from 1.0.0 dynamic memory allocation is required. However, block cache +should not allocate more than CONFIG_BLOCK_DEV CACHE_SIZE. + +Supported ext2/3/4 features +===== +incompatible: +------------ +* filetype, recover, meta_bg, extents, 64bit, flex_bg: **yes** +* compression, journal_dev, mmp, ea_inode, dirdata, bg_meta_csum, largedir, inline_data: **no** + +compatible: +------------ +* has_journal, ext_attr, dir_index: **yes** +* dir_prealloc, imagic_inodes, resize_inode: **no** + +read-only: +------------ +* sparse_super, large_file, huge_file, gdt_csum, dir_nlink, extra_isize, metadata_csum: **yes** +* quota, bigalloc, btree_dir: **no** + +Project tree +===== +* blockdev - block devices set, supported blockdev +* fs_test - test suite, mkfs and demo application +* src - source files +* include - header files +* toolchain - cmake toolchain files +* CMakeLists.txt - CMake config file +* ext_images.7z - compressed ext2/3/4 100MB images +* fs_test.mk - automatic tests definitions +* Makefile - helper makefile to generate cmake and run test suite +* README.md - readme file + +Compile +===== +Dependencies +------------ +* Windows + +Download MSYS-2: https://sourceforge.net/projects/msys2/ + +Install required packages is MSYS2 Shell package manager: +```bash + pacman -S make gcc cmake p7zip + ``` + +* Linux + +Package installation (Debian): +```bash + apt-get install make gcc cmake p7zip + ``` + +Compile & install tools +------------ +```bash + make generic + cd build_generic + make + sudo make install + ``` + +lwext4-generic demo application +===== +Simple lwext4 library test application: +* load ext2/3/4 images +* load linux block device with ext2/3/4 part +* load windows volume with ext2/3/4 filesystem +* directory speed test +* file write/read speed test + +How to use for images/blockdevices: +```bash + lwext4-generic -i ext_images/ext2 + lwext4-generic -i ext_images/ext3 + lwext4-generic -i ext_images/ext4 + ``` + +Show full option set: +```bash + lwext4-generic --help + ``` + +Run automatic tests +===== + +Execute tests for 100MB unpacked images: +```bash + make test + ``` +Execute tests for autogenerated 1GB images (only on Linux targets) + fsck: +```bash + make test_all + ``` +Using lwext4-mkfs tool +===== +It is possible to create ext2/3/4 partition by internal library tool. + +Generate empty file (1GB): +```bash + dd if=/dev/zero of=ext_image bs=1M count=1024 + ``` +Create ext2 partition: +```bash + lwext4-mkfs -i ext_image -e 2 + ``` +Create ext3 partition: +```bash + lwext4-mkfs -i ext_image -e 3 + ``` +Create ext4 partition: +```bash + lwext4-mkfs -i ext_image -e 4 + ``` +Show full option set: +```bash + lwext4-mkfs --help + ``` + +Cross compile standalone library +===== +Toolchains needed: +------------ + +Lwext4 could be compiled for many targets. Here are an examples for 8/16/32/64 bit architectures. +* generic for x86 or amd64 +* arm-none-eabi-gcc for ARM cortex-m0/m3/m4 microcontrollers +* avr-gcc for AVR xmega microcontrollers +* bfin-elf-gcc for blackfin processors +* msp430-gcc for msp430 microcontrollers + +Library has been tested only for generic (amd64) & ARM Cortex M architectures. +For other targets compilation passes (with warnings somewhere) but tests are +not done yet. Lwext4 code is written with endianes respect. Big endian +behavior also hasn't been tested yet. + +Build avrxmega7 library: +------------ +```bash + make avrxmega7 + cd build_avrxmega7 + make lwext4 + ``` + +Build cortex-m0 library: +------------ +```bash + make cortex-m0 + cd build_cortex-m0 + make lwext4 + ``` + +Build cortex-m3 library: +------------ +```bash + make cortex-m3 + cd build_cortex-m3 + make lwext4 + ``` + +Build cortex-m4 library: +------------ +```bash + make cortex-m4 + cd build_cortex-m4 + make lwext4 +``` + + diff --git a/clib/lib/lwext4/_config.yml b/clib/lib/lwext4/_config.yml new file mode 100644 index 0000000..fc24e7a --- /dev/null +++ b/clib/lib/lwext4/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-hacker \ No newline at end of file diff --git a/clib/lib/lwext4/blockdev/CMakeLists.txt b/clib/lib/lwext4/blockdev/CMakeLists.txt new file mode 100644 index 0000000..a16e810 --- /dev/null +++ b/clib/lib/lwext4/blockdev/CMakeLists.txt @@ -0,0 +1,13 @@ +#Blockdev library + +if (WIN32) + aux_source_directory(linux BLOCKDEV_SRC) + aux_source_directory(windows BLOCKDEV_SRC) +elseif (BLOCKDEV_TYPE STREQUAL linux) + aux_source_directory(linux BLOCKDEV_SRC) +else() +endif() + +aux_source_directory(. BLOCKDEV_SRC) +add_library(blockdev ${BLOCKDEV_SRC}) + diff --git a/clib/lib/lwext4/blockdev/blockdev.c b/clib/lib/lwext4/blockdev/blockdev.c new file mode 100644 index 0000000..a44a2e1 --- /dev/null +++ b/clib/lib/lwext4/blockdev/blockdev.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include + + +/**********************BLOCKDEV INTERFACE**************************************/ +static int blockdev_open(struct ext4_blockdev *bdev); +static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int blockdev_close(struct ext4_blockdev *bdev); +static int blockdev_lock(struct ext4_blockdev *bdev); +static int blockdev_unlock(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(blockdev, 512, 0, blockdev_open, + blockdev_bread, blockdev_bwrite, blockdev_close, + blockdev_lock, blockdev_unlock); + +/******************************************************************************/ +static int blockdev_open(struct ext4_blockdev *bdev) +{ + /*blockdev_open: skeleton*/ + return EIO; +} + +/******************************************************************************/ + +static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + /*blockdev_bread: skeleton*/ + return EIO; +} + + +/******************************************************************************/ +static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + /*blockdev_bwrite: skeleton*/ + return EIO; +} +/******************************************************************************/ +static int blockdev_close(struct ext4_blockdev *bdev) +{ + /*blockdev_close: skeleton*/ + return EIO; +} + +static int blockdev_lock(struct ext4_blockdev *bdev) +{ + /*blockdev_lock: skeleton*/ + return EIO; +} + +static int blockdev_unlock(struct ext4_blockdev *bdev) +{ + /*blockdev_unlock: skeleton*/ + return EIO; +} + +/******************************************************************************/ +struct ext4_blockdev *ext4_blockdev_get(void) +{ + return &blockdev; +} +/******************************************************************************/ + diff --git a/clib/lib/lwext4/blockdev/blockdev.h b/clib/lib/lwext4/blockdev/blockdev.h new file mode 100644 index 0000000..17f947a --- /dev/null +++ b/clib/lib/lwext4/blockdev/blockdev.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BLOCKDEV_H_ +#define BLOCKDEV_H_ + +#include +#include + +#include +#include + +/**@brief File blockdev get.*/ +struct ext4_blockdev *ext4_blockdev_get(void); + +#endif /* BLOCKDEV_H_ */ diff --git a/clib/lib/lwext4/blockdev/linux/file_dev.c b/clib/lib/lwext4/blockdev/linux/file_dev.c new file mode 100644 index 0000000..dd0743b --- /dev/null +++ b/clib/lib/lwext4/blockdev/linux/file_dev.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _LARGEFILE64_SOURCE +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include + +/**@brief Default filename.*/ +static const char *fname = "ext2"; + +/**@brief Image block size.*/ +#define EXT4_FILEDEV_BSIZE 512 + +/**@brief Image file descriptor.*/ +static FILE *dev_file; + +#define DROP_LINUXCACHE_BUFFERS 0 + +/**********************BLOCKDEV INTERFACE**************************************/ +static int file_dev_open(struct ext4_blockdev *bdev); +static int file_dev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int file_dev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int file_dev_close(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(file_dev, EXT4_FILEDEV_BSIZE, 0, file_dev_open, + file_dev_bread, file_dev_bwrite, file_dev_close, 0, 0); + +/******************************************************************************/ +static int file_dev_open(struct ext4_blockdev *bdev) +{ + dev_file = fopen(fname, "r+b"); + + if (!dev_file) + return EIO; + + /*No buffering at file.*/ + setbuf(dev_file, 0); + + if (fseeko(dev_file, 0, SEEK_END)) + return EFAULT; + + file_dev.part_offset = 0; + file_dev.part_size = ftello(dev_file); + file_dev.bdif->ph_bcnt = file_dev.part_size / file_dev.bdif->ph_bsize; + + return EOK; +} + +/******************************************************************************/ + +static int file_dev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + if (fseeko(dev_file, blk_id * bdev->bdif->ph_bsize, SEEK_SET)) + return EIO; + if (!blk_cnt) + return EOK; + if (!fread(buf, bdev->bdif->ph_bsize * blk_cnt, 1, dev_file)) + return EIO; + + return EOK; +} + +static void drop_cache(void) +{ +#if defined(__linux__) && DROP_LINUXCACHE_BUFFERS + int fd; + char *data = "3"; + + sync(); + fd = open("/proc/sys/vm/drop_caches", O_WRONLY); + write(fd, data, sizeof(char)); + close(fd); +#endif +} + +/******************************************************************************/ +static int file_dev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + if (fseeko(dev_file, blk_id * bdev->bdif->ph_bsize, SEEK_SET)) + return EIO; + if (!blk_cnt) + return EOK; + if (!fwrite(buf, bdev->bdif->ph_bsize * blk_cnt, 1, dev_file)) + return EIO; + + drop_cache(); + return EOK; +} +/******************************************************************************/ +static int file_dev_close(struct ext4_blockdev *bdev) +{ + fclose(dev_file); + return EOK; +} + +/******************************************************************************/ +struct ext4_blockdev *file_dev_get(void) +{ + return &file_dev; +} +/******************************************************************************/ +void file_dev_name_set(const char *n) +{ + fname = n; +} +/******************************************************************************/ diff --git a/clib/lib/lwext4/blockdev/linux/file_dev.h b/clib/lib/lwext4/blockdev/linux/file_dev.h new file mode 100644 index 0000000..ce4690d --- /dev/null +++ b/clib/lib/lwext4/blockdev/linux/file_dev.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef FILE_DEV_H_ +#define FILE_DEV_H_ + +#include +#include + +#include +#include + +/**@brief File blockdev get.*/ +struct ext4_blockdev *file_dev_get(void); + +/**@brief Set filename to open.*/ +void file_dev_name_set(const char *n); + +#endif /* FILE_DEV_H_ */ diff --git a/clib/lib/lwext4/blockdev/windows/file_windows.c b/clib/lib/lwext4/blockdev/windows/file_windows.c new file mode 100644 index 0000000..9142132 --- /dev/null +++ b/clib/lib/lwext4/blockdev/windows/file_windows.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include + +/**@brief Default filename.*/ +static const char *fname = "ext2"; + +/**@brief IO block size.*/ +#define EXT4_IORAW_BSIZE 512 + +/**@brief Image file descriptor.*/ +static HANDLE dev_file; + +/**********************BLOCKDEV INTERFACE**************************************/ +static int file_open(struct ext4_blockdev *bdev); +static int file_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int file_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int file_close(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(_filedev, EXT4_IORAW_BSIZE, 0, file_open, + file_bread, file_bwrite, file_close, 0, 0); + +/******************************************************************************/ +static int file_open(struct ext4_blockdev *bdev) +{ + char path[64]; + DISK_GEOMETRY pdg; + uint64_t disk_size; + BOOL bResult = FALSE; + DWORD junk; + + sprintf(path, "\\\\.\\%s", fname); + + dev_file = + CreateFile(path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, NULL); + + if (dev_file == INVALID_HANDLE_VALUE) { + return EIO; + } + + bResult = + DeviceIoControl(dev_file, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &pdg, sizeof(pdg), &junk, (LPOVERLAPPED)NULL); + + if (bResult == FALSE) { + CloseHandle(dev_file); + return EIO; + } + + disk_size = pdg.Cylinders.QuadPart * (ULONG)pdg.TracksPerCylinder * + (ULONG)pdg.SectorsPerTrack * (ULONG)pdg.BytesPerSector; + + _filedev.bdif->ph_bsize = pdg.BytesPerSector; + _filedev.bdif->ph_bcnt = disk_size / pdg.BytesPerSector; + + _filedev.part_offset = 0; + _filedev.part_size = disk_size; + + return EOK; +} + +/******************************************************************************/ + +static int file_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + long hipart = blk_id >> (32 - 9); + long lopart = blk_id << 9; + long err; + + SetLastError(0); + lopart = SetFilePointer(dev_file, lopart, &hipart, FILE_BEGIN); + + if (lopart == -1 && NO_ERROR != (err = GetLastError())) { + return EIO; + } + + DWORD n; + + if (!ReadFile(dev_file, buf, blk_cnt * 512, &n, NULL)) { + err = GetLastError(); + return EIO; + } + return EOK; +} + +/******************************************************************************/ +static int file_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + long hipart = blk_id >> (32 - 9); + long lopart = blk_id << 9; + long err; + + SetLastError(0); + lopart = SetFilePointer(dev_file, lopart, &hipart, FILE_BEGIN); + + if (lopart == -1 && NO_ERROR != (err = GetLastError())) { + return EIO; + } + + DWORD n; + + if (!WriteFile(dev_file, buf, blk_cnt * 512, &n, NULL)) { + err = GetLastError(); + return EIO; + } + return EOK; +} + +/******************************************************************************/ +static int file_close(struct ext4_blockdev *bdev) +{ + CloseHandle(dev_file); + return EOK; +} + +/******************************************************************************/ +struct ext4_blockdev *file_windows_dev_get(void) +{ + return &_filedev; +} +/******************************************************************************/ +void file_windows_name_set(const char *n) +{ + fname = n; +} + +/******************************************************************************/ +#endif diff --git a/clib/lib/lwext4/blockdev/windows/file_windows.h b/clib/lib/lwext4/blockdev/windows/file_windows.h new file mode 100644 index 0000000..013d1f3 --- /dev/null +++ b/clib/lib/lwext4/blockdev/windows/file_windows.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef FILE_WINDOWS_H_ +#define FILE_WINDOWS_H_ + +#include +#include + +#include +#include + +/**@brief IO raw blockdev get.*/ +struct ext4_blockdev *file_windows_dev_get(void); + +/**@brief Set filrname to open.*/ +void file_windows_name_set(const char *n); + + +#endif /* FILE_WINDOWS_H_ */ diff --git a/clib/lib/lwext4/fs_test.mk b/clib/lib/lwext4/fs_test.mk new file mode 100644 index 0000000..3ea1ac2 --- /dev/null +++ b/clib/lib/lwext4/fs_test.mk @@ -0,0 +1,664 @@ + +ifeq ($(OS),Windows_NT) +LWEXT4_CLIENT = @build_generic\\fs_test\\lwext4-client +LWEXT4_SERVER = @build_generic\\fs_test\\lwext4-server +else +LWEXT4_CLIENT = @build_generic/fs_test/lwext4-client +LWEXT4_SERVER = @build_generic/fs_test/lwext4-server +endif + +TEST_DIR = /test + +t0: + @echo "T0: Device register test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + +t1: + @echo "T1: Single mount-umount test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + +t2: + @echo "T2: Multiple mount-umount test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + +t3: + @echo "T3: Test dir create/remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 0" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t4: + @echo "T4: 10 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 10" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t5: + @echo "T5: 100 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 100" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 100" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t6: + @echo "T6: 1000 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 1000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 1000" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t7: + @echo "T7: 10 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 10" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t8: + @echo "T8: 100 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 100" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 100" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t9: + @echo "T9: 1000 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 1000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 1000" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t10: + @echo "T10: 10 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t11: + @echo "T11: 100 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 100" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t12: + @echo "T12: 1000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 1000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t13: + @echo "T13: 10 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t14: + @echo "T14: 100 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 100" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t15: + @echo "T15: 1000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 1000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +t16: + @echo "T16: 8kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 8192 0" + + $(LWEXT4_CLIENT) -c "ftell 0 8192" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fread 0 0 8192 0" + + $(LWEXT4_CLIENT) -c "ftell 0 8192" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t17: + @echo "T17: 64kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 65536 0" + + $(LWEXT4_CLIENT) -c "ftell 0 65536" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fread 0 0 65536 0" + + $(LWEXT4_CLIENT) -c "ftell 0 65536" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t18: + @echo "T18: 512kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 524288 0" + + $(LWEXT4_CLIENT) -c "ftell 0 524288" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fread 0 0 524288 0" + + $(LWEXT4_CLIENT) -c "ftell 0 524288" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t19: + @echo "T19: 4MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 4194304 0" + + $(LWEXT4_CLIENT) -c "ftell 0 4194304" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fread 0 0 4194304 0" + + $(LWEXT4_CLIENT) -c "ftell 0 4194304" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t20: + @echo "T20: 32MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 33554432 0" + + $(LWEXT4_CLIENT) -c "ftell 0 33554432" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fread 0 0 33554432 0" + + $(LWEXT4_CLIENT) -c "ftell 0 33554432" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t21: + @echo "T21: 128MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 134217728 0" + + $(LWEXT4_CLIENT) -c "ftell 0 134217728" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fread 0 0 134217728 0" + + $(LWEXT4_CLIENT) -c "ftell 0 134217728" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fclose 0" + + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t22: + @echo "T22: 512MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 536870912 0" + + $(LWEXT4_CLIENT) -c "ftell 0 536870912" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fread 0 0 536870912 0" + + $(LWEXT4_CLIENT) -c "ftell 0 536870912" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fclose 0" + + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t23: + @echo "T23: 10000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t24: + @echo "T24: 50000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 50000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 50000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 50000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +t25: + @echo "T25: 10000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t26: + @echo "T26: 50000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 50000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +ct: + @echo "Clean test directory" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "umount /" + +server_ext2: + $(LWEXT4_SERVER) -i ext_images/ext2 + +server_ext3: + $(LWEXT4_SERVER) -i ext_images/ext3 + +server_ext4: + $(LWEXT4_SERVER) -i ext_images/ext4 + +server_kill: + -killall lwext4-server + +fsck_images: + sudo fsck.ext2 ext_images/ext2 -v -f + sudo fsck.ext3 ext_images/ext3 -v -f + sudo fsck.ext4 ext_images/ext4 -v -f + +images_small: + rm -rf ext_images + mkdir ext_images + dd if=/dev/zero of=ext_images/ext2 bs=1M count=128 + dd if=/dev/zero of=ext_images/ext3 bs=1M count=128 + dd if=/dev/zero of=ext_images/ext4 bs=1M count=128 + sudo mkfs.ext2 ext_images/ext2 + sudo mkfs.ext3 ext_images/ext3 + sudo mkfs.ext4 ext_images/ext4 + +images_big: + rm -rf ext_images + mkdir ext_images + dd if=/dev/zero of=ext_images/ext2 bs=1M count=1024 + dd if=/dev/zero of=ext_images/ext3 bs=1M count=1024 + dd if=/dev/zero of=ext_images/ext4 bs=1M count=1024 + sudo mkfs.ext2 ext_images/ext2 + sudo mkfs.ext3 ext_images/ext3 + sudo mkfs.ext4 ext_images/ext4 + +test_set_small: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 +test_set_full: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 t21 t22 t23 t24 t25 t26 + +test_ext2_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext2 & + sleep 1 + make test_set_full + make server_kill + + +test_ext3_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext3 & + sleep 1 + make test_set_full + make server_kill + +test_ext4_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext4 & + sleep 1 + make test_set_full + make server_kill + +test_all: images_big test_ext2_full test_ext3_full test_ext4_full fsck_images + + +test_ext2_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext2 & + sleep 1 + make test_set_small + make server_kill + + +test_ext3_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext3 & + sleep 1 + make test_set_small + make server_kill + +test_ext4_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext4 & + sleep 1 + make test_set_small + make server_kill + +test: images_small test_ext2_small test_ext3_small test_ext4_small + + + + + + + diff --git a/clib/lib/lwext4/fs_test/CMakeLists.txt b/clib/lib/lwext4/fs_test/CMakeLists.txt new file mode 100644 index 0000000..16befb3 --- /dev/null +++ b/clib/lib/lwext4/fs_test/CMakeLists.txt @@ -0,0 +1,32 @@ +#fs_test executables +add_executable(lwext4-server lwext4_server.c) +target_link_libraries(lwext4-server lwext4) +target_link_libraries(lwext4-server blockdev) +if(WIN32) +target_link_libraries(lwext4-server ws2_32) +endif(WIN32) +add_executable(lwext4-client lwext4_client.c) +target_link_libraries(lwext4-client lwext4) +if(WIN32) +target_link_libraries(lwext4-client ws2_32) +endif(WIN32) + +aux_source_directory(common COMMON_SRC) +add_executable(lwext4-generic lwext4_generic.c ${COMMON_SRC}) +target_link_libraries(lwext4-generic blockdev) +target_link_libraries(lwext4-generic lwext4) + +add_executable(lwext4-mkfs lwext4_mkfs.c) +target_link_libraries(lwext4-mkfs blockdev) +target_link_libraries(lwext4-mkfs lwext4) + +add_executable(lwext4-mbr lwext4_mbr.c) +target_link_libraries(lwext4-mbr blockdev) +target_link_libraries(lwext4-mbr lwext4) + +install (TARGETS lwext4-server DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-client DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-generic DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-mkfs DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-mbr DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + diff --git a/clib/lib/lwext4/fs_test/common/test_lwext4.c b/clib/lib/lwext4/fs_test/common/test_lwext4.c new file mode 100644 index 0000000..78ec626 --- /dev/null +++ b/clib/lib/lwext4/fs_test/common/test_lwext4.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "../common/test_lwext4.h" + +#include + +#include +#include +#include + + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Block cache handle.*/ +static struct ext4_bcache *bc; + +static char *entry_to_str(uint8_t type) +{ + switch (type) { + case EXT4_DE_UNKNOWN: + return "[unk] "; + case EXT4_DE_REG_FILE: + return "[fil] "; + case EXT4_DE_DIR: + return "[dir] "; + case EXT4_DE_CHRDEV: + return "[cha] "; + case EXT4_DE_BLKDEV: + return "[blk] "; + case EXT4_DE_FIFO: + return "[fif] "; + case EXT4_DE_SOCK: + return "[soc] "; + case EXT4_DE_SYMLINK: + return "[sym] "; + default: + break; + } + return "[???]"; +} + +static long int get_ms(void) { return tim_get_ms(); } + +static void printf_io_timings(long int diff) +{ + const struct ext4_io_stats *stats = io_timings_get(diff); + if (!stats) + return; + + printf("io_timings:\n"); + printf(" io_read: %.3f%%\n", (double)stats->io_read); + printf(" io_write: %.3f%%\n", (double)stats->io_write); + printf(" io_cpu: %.3f%%\n", (double)stats->cpu); +} + +void test_lwext4_dir_ls(const char *path) +{ + char sss[255]; + ext4_dir d; + const ext4_direntry *de; + + printf("ls %s\n", path); + + ext4_dir_open(&d, path); + de = ext4_dir_entry_next(&d); + + while (de) { + memcpy(sss, de->name, de->name_length); + sss[de->name_length] = 0; + printf(" %s%s\n", entry_to_str(de->inode_type), sss); + de = ext4_dir_entry_next(&d); + } + ext4_dir_close(&d); +} + +void test_lwext4_mp_stats(void) +{ + struct ext4_mount_stats stats; + ext4_mount_point_stats("/mp/", &stats); + + printf("********************\n"); + printf("ext4_mount_point_stats\n"); + printf("inodes_count = %" PRIu32 "\n", stats.inodes_count); + printf("free_inodes_count = %" PRIu32 "\n", stats.free_inodes_count); + printf("blocks_count = %" PRIu32 "\n", (uint32_t)stats.blocks_count); + printf("free_blocks_count = %" PRIu32 "\n", + (uint32_t)stats.free_blocks_count); + printf("block_size = %" PRIu32 "\n", stats.block_size); + printf("block_group_count = %" PRIu32 "\n", stats.block_group_count); + printf("blocks_per_group= %" PRIu32 "\n", stats.blocks_per_group); + printf("inodes_per_group = %" PRIu32 "\n", stats.inodes_per_group); + printf("volume_name = %s\n", stats.volume_name); + printf("********************\n"); +} + +void test_lwext4_block_stats(void) +{ + if (!bd) + return; + + printf("********************\n"); + printf("ext4 blockdev stats\n"); + printf("bdev->bread_ctr = %" PRIu32 "\n", bd->bdif->bread_ctr); + printf("bdev->bwrite_ctr = %" PRIu32 "\n", bd->bdif->bwrite_ctr); + + printf("bcache->ref_blocks = %" PRIu32 "\n", bd->bc->ref_blocks); + printf("bcache->max_ref_blocks = %" PRIu32 "\n", bd->bc->max_ref_blocks); + printf("bcache->lru_ctr = %" PRIu32 "\n", bd->bc->lru_ctr); + + printf("\n"); + + printf("********************\n"); +} + +bool test_lwext4_dir_test(int len) +{ + ext4_file f; + int r; + int i; + char path[64]; + long int diff; + long int stop; + long int start; + + printf("test_lwext4_dir_test: %d\n", len); + io_timings_clear(); + start = get_ms(); + + printf("directory create: /mp/dir1\n"); + r = ext4_dir_mk("/mp/dir1"); + if (r != EOK) { + printf("ext4_dir_mk: rc = %d\n", r); + return false; + } + + printf("add files to: /mp/dir1\n"); + for (i = 0; i < len; ++i) { + sprintf(path, "/mp/dir1/f%d", i); + r = ext4_fopen(&f, path, "wb"); + if (r != EOK) { + printf("ext4_fopen: rc = %d\n", r); + return false; + } + } + + stop = get_ms(); + diff = stop - start; + test_lwext4_dir_ls("/mp/dir1"); + printf("test_lwext4_dir_test: time: %d ms\n", (int)diff); + printf("test_lwext4_dir_test: av: %d ms/entry\n", (int)diff / (len + 1)); + printf_io_timings(diff); + return true; +} + +static int verify_buf(const unsigned char *b, size_t len, unsigned char c) +{ + size_t i; + for (i = 0; i < len; ++i) { + if (b[i] != c) + return c - b[i]; + } + + return 0; +} + +bool test_lwext4_file_test(uint8_t *rw_buff, uint32_t rw_size, uint32_t rw_count) +{ + int r; + size_t size; + uint32_t i; + long int start; + long int stop; + long int diff; + uint32_t kbps; + uint64_t size_bytes; + + ext4_file f; + + printf("file_test:\n"); + printf(" rw size: %" PRIu32 "\n", rw_size); + printf(" rw count: %" PRIu32 "\n", rw_count); + + /*Add hello world file.*/ + r = ext4_fopen(&f, "/mp/hello.txt", "wb"); + r = ext4_fwrite(&f, "Hello World !\n", strlen("Hello World !\n"), 0); + r = ext4_fclose(&f); + + io_timings_clear(); + start = get_ms(); + r = ext4_fopen(&f, "/mp/test1", "wb"); + if (r != EOK) { + printf("ext4_fopen ERROR = %d\n", r); + return false; + } + + printf("ext4_write: %" PRIu32 " * %" PRIu32 " ...\n", rw_size, + rw_count); + for (i = 0; i < rw_count; ++i) { + + memset(rw_buff, i % 10 + '0', rw_size); + + r = ext4_fwrite(&f, rw_buff, rw_size, &size); + + if ((r != EOK) || (size != rw_size)) + break; + } + + if (i != rw_count) { + printf(" file_test: rw_count = %" PRIu32 "\n", i); + return false; + } + + stop = get_ms(); + diff = stop - start; + size_bytes = rw_size * rw_count; + size_bytes = (size_bytes * 1000) / 1024; + kbps = (size_bytes) / (diff + 1); + printf(" write time: %d ms\n", (int)diff); + printf(" write speed: %" PRIu32 " KB/s\n", kbps); + printf_io_timings(diff); + r = ext4_fclose(&f); + + io_timings_clear(); + start = get_ms(); + r = ext4_fopen(&f, "/mp/test1", "r+"); + if (r != EOK) { + printf("ext4_fopen ERROR = %d\n", r); + return false; + } + + printf("ext4_read: %" PRIu32 " * %" PRIu32 " ...\n", rw_size, rw_count); + + for (i = 0; i < rw_count; ++i) { + r = ext4_fread(&f, rw_buff, rw_size, &size); + + if ((r != EOK) || (size != rw_size)) + break; + + if (verify_buf(rw_buff, rw_size, i % 10 + '0')) + break; + } + + if (i != rw_count) { + printf(" file_test: rw_count = %" PRIu32 "\n", i); + return false; + } + + stop = get_ms(); + diff = stop - start; + size_bytes = rw_size * rw_count; + size_bytes = (size_bytes * 1000) / 1024; + kbps = (size_bytes) / (diff + 1); + printf(" read time: %d ms\n", (int)diff); + printf(" read speed: %d KB/s\n", (int)kbps); + printf_io_timings(diff); + + r = ext4_fclose(&f); + return true; +} +void test_lwext4_cleanup(void) +{ + long int start; + long int stop; + long int diff; + int r; + + printf("\ncleanup:\n"); + r = ext4_fremove("/mp/hello.txt"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove error: rc = %d\n", r); + } + + printf("remove /mp/test1\n"); + r = ext4_fremove("/mp/test1"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove error: rc = %d\n", r); + } + + printf("remove /mp/dir1\n"); + io_timings_clear(); + start = get_ms(); + r = ext4_dir_rm("/mp/dir1"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove ext4_dir_rm: rc = %d\n", r); + } + stop = get_ms(); + diff = stop - start; + printf("cleanup: time: %d ms\n", (int)diff); + printf_io_timings(diff); +} + +bool test_lwext4_mount(struct ext4_blockdev *bdev, struct ext4_bcache *bcache) +{ + int r; + + bc = bcache; + bd = bdev; + + if (!bd) { + printf("test_lwext4_mount: no block device\n"); + return false; + } + + ext4_dmask_set(DEBUG_ALL); + + r = ext4_device_register(bd, "ext4_fs"); + if (r != EOK) { + printf("ext4_device_register: rc = %d\n", r); + return false; + } + + r = ext4_mount("ext4_fs", "/mp/", false); + if (r != EOK) { + printf("ext4_mount: rc = %d\n", r); + return false; + } + + r = ext4_recover("/mp/"); + if (r != EOK && r != ENOTSUP) { + printf("ext4_recover: rc = %d\n", r); + return false; + } + + r = ext4_journal_start("/mp/"); + if (r != EOK) { + printf("ext4_journal_start: rc = %d\n", r); + return false; + } + + ext4_cache_write_back("/mp/", 1); + return true; +} + +bool test_lwext4_umount(void) +{ + int r; + + ext4_cache_write_back("/mp/", 0); + + r = ext4_journal_stop("/mp/"); + if (r != EOK) { + printf("ext4_journal_stop: fail %d", r); + return false; + } + + r = ext4_umount("/mp/"); + if (r != EOK) { + printf("ext4_umount: fail %d", r); + return false; + } + return true; +} diff --git a/clib/lib/lwext4/fs_test/common/test_lwext4.h b/clib/lib/lwext4/fs_test/common/test_lwext4.h new file mode 100644 index 0000000..bc0c446 --- /dev/null +++ b/clib/lib/lwext4/fs_test/common/test_lwext4.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_LWEXT4_H_ +#define TEST_LWEXT4_H_ + +#include +#include +#include + +void test_lwext4_dir_ls(const char *path); +void test_lwext4_mp_stats(void); +void test_lwext4_block_stats(void); +bool test_lwext4_dir_test(int len); +bool test_lwext4_file_test(uint8_t *rw_buff, uint32_t rw_size, uint32_t rw_count); +void test_lwext4_cleanup(void); + +bool test_lwext4_mount(struct ext4_blockdev *bdev, struct ext4_bcache *bcache); +bool test_lwext4_umount(void); + +void tim_wait_ms(uint32_t v); + +uint32_t tim_get_ms(void); +uint64_t tim_get_us(void); + +struct ext4_io_stats { + float io_read; + float io_write; + float cpu; +}; + +void io_timings_clear(void); +const struct ext4_io_stats *io_timings_get(uint32_t time_sum_ms); + +#endif /* TEST_LWEXT4_H_ */ diff --git a/clib/lib/lwext4/fs_test/lwext4_client.c b/clib/lib/lwext4/fs_test/lwext4_client.c new file mode 100644 index 0000000..f47ca93 --- /dev/null +++ b/clib/lib/lwext4/fs_test/lwext4_client.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include +static int inet_pton(int af, const char *src, void *dst); + +#else +#include +#include +#include +#include +#endif + +static int winsock_init(void); +static void winsock_fini(void); + +/**@brief Default server addres.*/ +static char *server_addr = "127.0.0.1"; + +/**@brief Default connection port.*/ +static int connection_port = 1234; + +/**@brief Call op*/ +static char *op_code; + +static const char *usage = " \n\ +Welcome in lwext4_client. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ + --call (-c) - call opt \n\ + --port (-p) - server port \n\ + --addr (-a) - server ip address \n\ +\n"; + +static int client_connect(void) +{ + int fd = 0; + struct sockaddr_in serv_addr; + + if (winsock_init() < 0) { + printf("winsock_init error\n"); + exit(-1); + } + + memset(&serv_addr, '0', sizeof(serv_addr)); + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + printf("socket() error: %s\n", strerror(errno)); + exit(-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(connection_port); + + if (!inet_pton(AF_INET, server_addr, &serv_addr.sin_addr)) { + printf("inet_pton() error\n"); + exit(-1); + } + + if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + printf("connect() error: %s\n", strerror(errno)); + exit(-1); + } + + return fd; +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"call", required_argument, 0, 'c'}, + {"port", required_argument, 0, 'p'}, + {"addr", required_argument, 0, 'a'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "c:p:a:x", long_options, + &option_index))) { + + switch (c) { + case 'a': + server_addr = optarg; + break; + case 'p': + connection_port = atoi(optarg); + break; + case 'c': + op_code = optarg; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char *argv[]) +{ + int sockfd; + int n; + int rc; + char recvBuff[1024]; + + if (!parse_opt(argc, argv)) + return -1; + + sockfd = client_connect(); + + n = send(sockfd, op_code, strlen(op_code), 0); + if (n < 0) { + printf("\tWrite error: %s fd = %d\n", strerror(errno), sockfd); + return -1; + } + + n = recv(sockfd, (void *)&rc, sizeof(rc), 0); + if (n < 0) { + printf("\tWrite error: %s fd = %d\n", strerror(errno), sockfd); + return -1; + } + + printf("rc: %d %s\n", rc, strerror(rc)); + if (rc) + printf("\t%s\n", op_code); + + winsock_fini(); + return rc; +} + +static int winsock_init(void) +{ +#if WIN32 + int rc; + static WSADATA wsaData; + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rc != 0) { + return -1; + } +#endif + return 0; +} + +static void winsock_fini(void) +{ +#if WIN32 + WSACleanup(); +#endif +} + +#if WIN32 +static int inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage ss; + int size = sizeof(ss); + char src_copy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory(&ss, sizeof(ss)); + /* stupid non-const API */ + strncpy(src_copy, src, INET6_ADDRSTRLEN + 1); + src_copy[INET6_ADDRSTRLEN] = 0; + + if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, + &size) == 0) { + switch (af) { + case AF_INET: + *(struct in_addr *)dst = + ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = + ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} +#endif diff --git a/clib/lib/lwext4/fs_test/lwext4_generic.c b/clib/lib/lwext4/fs_test/lwext4_generic.c new file mode 100644 index 0000000..a9bfb1b --- /dev/null +++ b/clib/lib/lwext4/fs_test/lwext4_generic.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" +#include "common/test_lwext4.h" + +#ifdef WIN32 +#include +#endif + +/**@brief Input stream name.*/ +char input_name[128] = "ext_images/ext2"; + +/**@brief Read-write size*/ +static int rw_szie = 1024 * 1024; + +/**@brief Read-write size*/ +static int rw_count = 10; + +/**@brief Directory test count*/ +static int dir_cnt = 0; + +/**@brief Cleanup after test.*/ +static bool cleanup_flag = false; + +/**@brief Block device stats.*/ +static bool bstat = false; + +/**@brief Superblock stats.*/ +static bool sbstat = false; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +/**@brief Verbose mode*/ +static bool verbose = 0; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Block cache handle.*/ +static struct ext4_bcache *bc; + +static const char *usage = " \n\ +Welcome in ext4 generic demo. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file (default = ext2) \n\ +[-w] --rw_size - single R/W size (default = 1024 * 1024) \n\ +[-c] --rw_count - R/W count (default = 10) \n\ +[-d] --dirs - directory test count (default = 0) \n\ +[-l] --clean - clean up after test \n\ +[-b] --bstat - block device stats \n\ +[-t] --sbstat - superblock stats \n\ +[-w] --wpart - windows partition mode \n\ +\n"; + +void io_timings_clear(void) +{ +} + +const struct ext4_io_stats *io_timings_get(uint32_t time_sum_ms) +{ + return NULL; +} + +uint32_t tim_get_ms(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +uint64_t tim_get_us(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000000) + (t.tv_usec); +} + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"rw_size", required_argument, 0, 's'}, + {"rw_count", required_argument, 0, 'c'}, + {"dirs", required_argument, 0, 'd'}, + {"clean", no_argument, 0, 'l'}, + {"bstat", no_argument, 0, 'b'}, + {"sbstat", no_argument, 0, 't'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:s:c:q:d:lbtwvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + strcpy(input_name, optarg); + break; + case 's': + rw_szie = atoi(optarg); + break; + case 'c': + rw_count = atoi(optarg); + break; + case 'd': + dir_cnt = atoi(optarg); + break; + case 'l': + cleanup_flag = true; + break; + case 'b': + bstat = true; + break; + case 't': + sbstat = true; + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char **argv) +{ + if (!parse_opt(argc, argv)) + return EXIT_FAILURE; + + printf("ext4_generic\n"); + printf("test conditions:\n"); + printf("\timput name: %s\n", input_name); + printf("\trw size: %d\n", rw_szie); + printf("\trw count: %d\n", rw_count); + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + if (!test_lwext4_mount(bd, bc)) + return EXIT_FAILURE; + + test_lwext4_cleanup(); + + if (sbstat) + test_lwext4_mp_stats(); + + test_lwext4_dir_ls("/mp/"); + fflush(stdout); + if (!test_lwext4_dir_test(dir_cnt)) + return EXIT_FAILURE; + + fflush(stdout); + uint8_t *rw_buff = malloc(rw_szie); + if (!rw_buff) { + free(rw_buff); + return EXIT_FAILURE; + } + if (!test_lwext4_file_test(rw_buff, rw_szie, rw_count)) { + free(rw_buff); + return EXIT_FAILURE; + } + + free(rw_buff); + + fflush(stdout); + test_lwext4_dir_ls("/mp/"); + + if (sbstat) + test_lwext4_mp_stats(); + + if (cleanup_flag) + test_lwext4_cleanup(); + + if (bstat) + test_lwext4_block_stats(); + + if (!test_lwext4_umount()) + return EXIT_FAILURE; + + printf("\ntest finished\n"); + return EXIT_SUCCESS; +} diff --git a/clib/lib/lwext4/fs_test/lwext4_mbr.c b/clib/lib/lwext4/fs_test/lwext4_mbr.c new file mode 100644 index 0000000..9bb19b8 --- /dev/null +++ b/clib/lib/lwext4/fs_test/lwext4_mbr.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + +/**@brief Input stream name.*/ +const char *input_name = NULL; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +static bool verbose = false; + +static const char *usage = " \n\ +Welcome in lwext4_mbr tool. \n\ +Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file name (or blockdevice) \n\ +[-w] --wpart - windows partition mode \n\ +[-v] --verbose - verbose mode \n\ +\n"; + + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:wvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + input_name = optarg; + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + + return true; +} + +int main(int argc, char **argv) +{ + int r; + if (!parse_opt(argc, argv)){ + printf("parse_opt error\n"); + return EXIT_FAILURE; + } + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + printf("ext4_mbr\n"); + struct ext4_mbr_bdevs bdevs; + r = ext4_mbr_scan(bd, &bdevs); + if (r != EOK) { + printf("ext4_mbr_scan error\n"); + return EXIT_FAILURE; + } + + int i; + printf("ext4_mbr_scan:\n"); + for (i = 0; i < 4; i++) { + printf("mbr_entry %d:\n", i); + if (!bdevs.partitions[i].bdif) { + printf("\tempty/unknown\n"); + continue; + } + + printf("\toffeset: 0x%"PRIx64", %"PRIu64"MB\n", + bdevs.partitions[i].part_offset, + bdevs.partitions[i].part_offset / (1024 * 1024)); + printf("\tsize: 0x%"PRIx64", %"PRIu64"MB\n", + bdevs.partitions[i].part_size, + bdevs.partitions[i].part_size / (1024 * 1024)); + } + + + return EXIT_SUCCESS; +} diff --git a/clib/lib/lwext4/fs_test/lwext4_mkfs.c b/clib/lib/lwext4/fs_test/lwext4_mkfs.c new file mode 100644 index 0000000..781e8dc --- /dev/null +++ b/clib/lib/lwext4/fs_test/lwext4_mkfs.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + +/**@brief Input stream name.*/ +const char *input_name = NULL; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +static int fs_type = F_SET_EXT4; + +static struct ext4_fs fs; +static struct ext4_mkfs_info info = { + .block_size = 1024, + .journal = true, +}; + +static bool verbose = false; + +static const char *usage = " \n\ +Welcome in lwext4_mkfs tool . \n\ +Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file name (or blockdevice) \n\ +[-w] --wpart - windows partition mode \n\ +[-v] --verbose - verbose mode \n\ +[-b] --block - block size: 1024, 2048, 4096 (default 1024) \n\ +[-e] --ext - fs type (ext2: 2, ext3: 3 ext4: 4)) \n\ +\n"; + + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"block", required_argument, 0, 'b'}, + {"ext", required_argument, 0, 'e'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:b:e:wvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + input_name = optarg; + break; + case 'b': + info.block_size = atoi(optarg); + break; + case 'e': + fs_type = atoi(optarg); + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + + switch (info.block_size) { + case 1024: + case 2048: + case 4096: + break; + default: + printf("parse_opt: block_size = %"PRIu32" unsupported\n", + info.block_size); + return false; + } + + switch (fs_type) { + case F_SET_EXT2: + case F_SET_EXT3: + case F_SET_EXT4: + break; + default: + printf("parse_opt: fs_type = %"PRIu32" unsupported\n", fs_type); + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + int r; + if (!parse_opt(argc, argv)){ + printf("parse_opt error\n"); + return EXIT_FAILURE; + } + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + printf("ext4_mkfs: ext%d\n", fs_type); + r = ext4_mkfs(&fs, bd, &info, fs_type); + if (r != EOK) { + printf("ext4_mkfs error: %d\n", r); + return EXIT_FAILURE; + } + + memset(&info, 0, sizeof(struct ext4_mkfs_info)); + r = ext4_mkfs_read_info(bd, &info); + if (r != EOK) { + printf("ext4_mkfs_read_info error: %d\n", r); + return EXIT_FAILURE; + } + + printf("Created filesystem with parameters:\n"); + printf("Size: %"PRIu64"\n", info.len); + printf("Block size: %"PRIu32"\n", info.block_size); + printf("Blocks per group: %"PRIu32"\n", info.blocks_per_group); + printf("Inodes per group: %"PRIu32"\n", info.inodes_per_group); + printf("Inode size: %"PRIu32"\n", info.inode_size); + printf("Inodes: %"PRIu32"\n", info.inodes); + printf("Journal blocks: %"PRIu32"\n", info.journal_blocks); + printf("Features ro_compat: 0x%x\n", info.feat_ro_compat); + printf("Features compat: 0x%x\n", info.feat_compat); + printf("Features incompat: 0x%x\n", info.feat_incompat); + printf("BG desc reserve: %"PRIu32"\n", info.bg_desc_reserve_blocks); + printf("Descriptor size: %"PRIu32"\n",info.dsc_size); + printf("Label: %s\n", info.label); + + printf("\nDone ...\n"); + return EXIT_SUCCESS; +} diff --git a/clib/lib/lwext4/fs_test/lwext4_server.c b/clib/lib/lwext4/fs_test/lwext4_server.c new file mode 100644 index 0000000..b65ef3d --- /dev/null +++ b/clib/lib/lwext4/fs_test/lwext4_server.c @@ -0,0 +1,1180 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + + +static int winsock_init(void); +static void winsock_fini(void); +static char *entry_to_str(uint8_t type); + +#define MAX_FILES 64 +#define MAX_DIRS 64 + +#define MAX_RW_BUFFER (1024 * 1024) +#define RW_BUFFER_PATERN ('x') + +/**@brief Default connection port*/ +static int connection_port = 1234; + +/**@brief Default filesystem filename.*/ +static char *ext4_fname = "ext2"; + +/**@brief Verbose mode*/ +static bool verbose = false; + +/**@brief Winpart mode*/ +static bool winpart = false; + +/**@brief Blockdev handle*/ +static struct ext4_blockdev *bd; + +static bool cache_wb = false; + +static char read_buffer[MAX_RW_BUFFER]; +static char write_buffer[MAX_RW_BUFFER]; + +static const char *usage = " \n\ +Welcome in lwext4_server. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ + --image (-i) - ext2/3/4 image file \n\ + --port (-p) - server port \n\ + --verbose (-v) - verbose mode \n\ + --winpart (-w) - windows_partition mode \n\ + --cache_wb (-c) - cache writeback_mode \n\ +\n"; + +/**@brief Open file instance descriptor.*/ +struct lwext4_files { + char name[255]; + ext4_file fd; +}; + +/**@brief Open directory instance descriptor.*/ +struct lwext4_dirs { + char name[255]; + ext4_dir fd; +}; + +/**@brief Library call opcode.*/ +struct lwext4_op_codes { + char *func; +}; + +/**@brief Library call wraper.*/ +struct lwext4_call { + int (*lwext4_call)(const char *p); +}; + +/**@brief */ +static struct lwext4_files file_tab[MAX_FILES]; + +/**@brief */ +static struct lwext4_dirs dir_tab[MAX_DIRS]; + +/**@brief */ +static struct lwext4_op_codes op_codes[] = { + "device_register", + "mount", + "umount", + "mount_point_stats", + "cache_write_back", + "fremove", + "fopen", + "fclose", + "fread", + "fwrite", + "fseek", + "ftell", + "fsize", + "dir_rm", + "dir_mk", + "dir_open", + "dir_close", + "dir_entry_get", + + "multi_fcreate", + "multi_fwrite", + "multi_fread", + "multi_fremove", + "multi_dcreate", + "multi_dremove", + "stats_save", + "stats_check", +}; + +static int device_register(const char *p); +static int mount(const char *p); +static int umount(const char *p); +static int mount_point_stats(const char *p); +static int cache_write_back(const char *p); +static int fremove(const char *p); +static int file_open(const char *p); +static int file_close(const char *p); +static int file_read(const char *p); +static int file_write(const char *p); +static int file_seek(const char *p); +static int file_tell(const char *p); +static int file_size(const char *p); +static int dir_rm(const char *p); +static int dir_mk(const char *p); +static int dir_open(const char *p); +static int dir_close(const char *p); +static int dir_close(const char *p); +static int dir_entry_get(const char *p); + +static int multi_fcreate(const char *p); +static int multi_fwrite(const char *p); +static int multi_fread(const char *p); +static int multi_fremove(const char *p); +static int multi_dcreate(const char *p); +static int multi_dremove(const char *p); +static int stats_save(const char *p); +static int stats_check(const char *p); + +/**@brief */ +static struct lwext4_call op_call[] = { + device_register, /*PARAMS(3): 0 cache_mode dev_name */ + mount, /*PARAMS(2): dev_name mount_point */ + umount, /*PARAMS(1): mount_point */ + mount_point_stats, /*PARAMS(2): mount_point, 0 */ + cache_write_back, /*PARAMS(2): mount_point, en */ + fremove, /*PARAMS(1): path */ + file_open, /*PARAMS(2): fid path flags */ + file_close, /*PARAMS(1): fid */ + file_read, /*PARAMS(4): fid 0 len 0 */ + file_write, /*PARAMS(4): fid 0 len 0 */ + file_seek, /*PARAMS(2): fid off origin */ + file_tell, /*PARAMS(2): fid exp */ + file_size, /*PARAMS(2): fid exp */ + dir_rm, /*PARAMS(1): path */ + dir_mk, /*PARAMS(1): path */ + dir_open, /*PARAMS(2): did, path */ + dir_close, /*PARAMS(1): did */ + dir_entry_get, /*PARAMS(2): did, exp */ + + multi_fcreate, /*PARAMS(3): path prefix cnt */ + multi_fwrite, /*PARAMS(4): path prefix cnt size */ + multi_fread, /*PARAMS(4): path prefix cnt size */ + multi_fremove, /*PARAMS(2): path prefix cnt */ + multi_dcreate, /*PARAMS(3): path prefix cnt */ + multi_dremove, /*PARAMS(2): path prefix */ + stats_save, /*PARAMS(1): path */ + stats_check, /*PARAMS(1): path */ +}; + +static clock_t get_ms(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +/**@brief */ +static int exec_op_code(const char *opcode) +{ + int i; + int r = -1; + + for (i = 0; i < sizeof(op_codes) / sizeof(op_codes[0]); ++i) { + + if (strncmp(op_codes[i].func, opcode, strlen(op_codes[i].func))) + continue; + + if (opcode[strlen(op_codes[i].func)] != ' ') + continue; + + printf("%s\n", opcode); + opcode += strlen(op_codes[i].func); + /*Call*/ + + clock_t t = get_ms(); + r = op_call[i].lwext4_call(opcode); + + printf("rc: %d, time: %ums\n", r, (unsigned int)(get_ms() - t)); + + break; + } + + return r; +} + +static int server_open(void) +{ + int fd = 0; + struct sockaddr_in serv_addr; + + memset(&serv_addr, 0, sizeof(serv_addr)); + + if (winsock_init() < 0) { + printf("winsock_init() error\n"); + exit(-1); + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + printf("socket() error: %s\n", strerror(errno)); + exit(-1); + } + + int yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, + sizeof(int))) { + printf("setsockopt() error: %s\n", strerror(errno)); + exit(-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(connection_port); + + if (bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + printf("bind() error: %s\n", strerror(errno)); + exit(-1); + } + + if (listen(fd, 1)) { + printf("listen() error: %s\n", strerror(errno)); + exit(-1); + } + + return fd; +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"image", required_argument, 0, 'i'}, + {"port", required_argument, 0, 'p'}, + {"verbose", no_argument, 0, 'v'}, + {"winpart", no_argument, 0, 'w'}, + {"cache_wb", no_argument, 0, 'c'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:p:vcwx", long_options, + &option_index))) { + + switch (c) { + case 'i': + ext4_fname = optarg; + break; + case 'p': + connection_port = atoi(optarg); + break; + case 'v': + verbose = true; + break; + case 'c': + cache_wb = true; + break; + case 'w': + winpart = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char *argv[]) +{ + int n; + int listenfd; + int connfd; + char op_code[128]; + + if (!parse_opt(argc, argv)) + return -1; + + listenfd = server_open(); + + printf("lwext4_server: listening on port: %d\n", connection_port); + + memset(write_buffer, RW_BUFFER_PATERN, MAX_RW_BUFFER); + while (1) { + connfd = accept(listenfd, (struct sockaddr *)NULL, NULL); + + n = recv(connfd, op_code, sizeof(op_code), 0); + + if (n < 0) { + printf("recv() error: %s fd = %d\n", strerror(errno), + connfd); + break; + } + + op_code[n] = 0; + + int r = exec_op_code(op_code); + + n = send(connfd, (void *)&r, sizeof(r), 0); + if (n < 0) { + printf("send() error: %s fd = %d\n", strerror(errno), + connfd); + break; + } + + close(connfd); + } + + winsock_fini(); + return 0; +} + +static int device_register(const char *p) +{ + int dev; + int cache_mode; + char dev_name[32]; + + if (sscanf(p, "%d %d %s", &dev, &cache_mode, dev_name) != 3) { + printf("Param list error\n"); + return -1; + } + +#ifdef WIN32 + if (winpart) { + file_windows_name_set(ext4_fname); + bd = file_windows_dev_get(); + + } else +#endif + { + file_dev_name_set(ext4_fname); + bd = file_dev_get(); + } + + ext4_device_unregister_all(); + + return ext4_device_register(bd, dev_name); +} + +static int mount(const char *p) +{ + char dev_name[32]; + char mount_point[32]; + int rc; + + if (sscanf(p, "%s %s", dev_name, mount_point) != 2) { + printf("Param list error\n"); + return -1; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + rc = ext4_mount(dev_name, mount_point, false); + if (rc != EOK) + return rc; + + rc = ext4_recover(mount_point); + if (rc != EOK && rc != ENOTSUP) + return rc; + + rc = ext4_journal_start(mount_point); + if (rc != EOK) + return rc; + + if (cache_wb) + ext4_cache_write_back(mount_point, 1); + return rc; +} + +static int umount(const char *p) +{ + char mount_point[32]; + int rc; + + if (sscanf(p, "%s", mount_point) != 1) { + printf("Param list error\n"); + return -1; + } + + if (cache_wb) + ext4_cache_write_back(mount_point, 0); + + rc = ext4_journal_stop(mount_point); + if (rc != EOK) + return rc; + + rc = ext4_umount(mount_point); + if (rc != EOK) + return rc; + + return rc; +} + +static int mount_point_stats(const char *p) +{ + char mount_point[32]; + int d; + int rc; + struct ext4_mount_stats stats; + + if (sscanf(p, "%s %d", mount_point, &d) != 2) { + printf("Param list error\n"); + return -1; + } + + rc = ext4_mount_point_stats(mount_point, &stats); + + if (rc != EOK) + return rc; + + if (verbose) { + printf("\tinodes_count = %" PRIu32"\n", stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + stats.free_blocks_count); + printf("\tblock_size = %" PRIu32"\n", stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + stats.inodes_per_group); + printf("\tvolume_name = %s\n", stats.volume_name); + } + + return rc; +} + +static int cache_write_back(const char *p) +{ + char mount_point[32]; + int en; + + if (sscanf(p, "%s %d", mount_point, &en) != 2) { + printf("Param list error\n"); + return -1; + } + + return ext4_cache_write_back(mount_point, en); +} + +static int fremove(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_fremove(path); +} + +static int file_open(const char *p) +{ + int fid = MAX_FILES; + char path[256]; + char flags[8]; + int rc; + + if (sscanf(p, "%d %s %s", &fid, path, flags) != 3) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + rc = ext4_fopen(&file_tab[fid].fd, path, flags); + + if (rc == EOK) + strcpy(file_tab[fid].name, path); + + return rc; +} + +static int file_close(const char *p) +{ + int fid = MAX_FILES; + int rc; + + if (sscanf(p, "%d", &fid) != 1) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + rc = ext4_fclose(&file_tab[fid].fd); + + if (rc == EOK) + file_tab[fid].name[0] = 0; + + return rc; +} + +static int file_read(const char *p) +{ + int fid = MAX_FILES; + int len; + int d; + int rc; + size_t rb; + + if (sscanf(p, "%d %d %d %d", &fid, &d, &len, &d) != 4) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + + memset(read_buffer, 0, MAX_RW_BUFFER); + rc = ext4_fread(&file_tab[fid].fd, read_buffer, d, &rb); + + if (rc != EOK) + break; + + if (rb != d) { + printf("Read count error\n"); + return -1; + } + + if (memcmp(read_buffer, write_buffer, d)) { + printf("Read compare error\n"); + return -1; + } + + len -= d; + } + + return rc; +} + +static int file_write(const char *p) +{ + int fid = MAX_FILES; + int d; + int rc; + + int len; + size_t wb; + + if (sscanf(p, "%d %d %d %d", &fid, &d, &len, &d) != 4) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + rc = ext4_fwrite(&file_tab[fid].fd, write_buffer, d, &wb); + + if (rc != EOK) + break; + + if (wb != d) { + printf("Write count error\n"); + return -1; + } + + len -= d; + } + + return rc; +} + +static int file_seek(const char *p) +{ + int fid = MAX_FILES; + int off; + int origin; + + if (sscanf(p, "%d %d %d", &fid, &off, &origin) != 3) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + return ext4_fseek(&file_tab[fid].fd, off, origin); +} + +static int file_tell(const char *p) +{ + int fid = MAX_FILES; + uint32_t exp_pos; + + if (sscanf(p, "%d %u", &fid, &exp_pos) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + if (exp_pos != ext4_ftell(&file_tab[fid].fd)) { + printf("Expected filepos error\n"); + return -1; + } + + return EOK; +} + +static int file_size(const char *p) +{ + int fid = MAX_FILES; + uint32_t exp_size; + + if (sscanf(p, "%d %u", &fid, &exp_size) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + if (exp_size != ext4_fsize(&file_tab[fid].fd)) { + printf("Expected filesize error\n"); + return -1; + } + + return EOK; +} + +static int dir_rm(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_dir_rm(path); +} + +static int dir_mk(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_dir_mk(path); +} + +static int dir_open(const char *p) +{ + int did = MAX_DIRS; + char path[255]; + int rc; + + if (sscanf(p, "%d %s", &did, path) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + rc = ext4_dir_open(&dir_tab[did].fd, path); + + if (rc == EOK) + strcpy(dir_tab[did].name, path); + + return rc; +} + +static int dir_close(const char *p) +{ + int did = MAX_DIRS; + int rc; + + if (sscanf(p, "%d", &did) != 1) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + if (dir_tab[did].name[0] == 0) { + printf("Dir id empty\n"); + return -1; + } + + rc = ext4_dir_close(&dir_tab[did].fd); + + if (rc == EOK) + dir_tab[did].name[0] = 0; + + return rc; +} + +static int dir_entry_get(const char *p) +{ + int did = MAX_DIRS; + int exp; + char name[256]; + + if (sscanf(p, "%d %d", &did, &exp) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + if (dir_tab[did].name[0] == 0) { + printf("Dir id empty\n"); + return -1; + } + + int idx = 0; + const ext4_direntry *d; + + while ((d = ext4_dir_entry_next(&dir_tab[did].fd)) != NULL) { + + idx++; + memcpy(name, d->name, d->name_length); + name[d->name_length] = 0; + if (verbose) { + printf("\t%s %s\n", entry_to_str(d->inode_type), name); + } + } + + if (idx < 2) { + printf("Minumum dir entry error\n"); + return -1; + } + + if ((idx - 2) != exp) { + printf("Expected dir entry error\n"); + return -1; + } + + return EOK; +} + +static int multi_fcreate(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt; + int rc; + int i; + ext4_file fd; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "wb+"); + + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_fwrite(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i; + int len, ll; + int rc; + size_t d, wb; + ext4_file fd; + + if (sscanf(p, "%s %s %d %d", path, prefix, &cnt, &ll) != 4) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "rb+"); + + if (rc != EOK) + break; + + len = ll; + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + rc = ext4_fwrite(&fd, write_buffer, d, &wb); + + if (rc != EOK) + break; + + if (wb != d) { + printf("Write count error\n"); + return -1; + } + + len -= d; + } + } + + return rc; +} + +static int multi_fread(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt; + int len, ll; + int rc ,i, d; + size_t rb; + ext4_file fd; + + if (sscanf(p, "%s %s %d %d", path, prefix, &cnt, &ll) != 4) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "rb+"); + + if (rc != EOK) + break; + + len = ll; + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + + memset(read_buffer, 0, MAX_RW_BUFFER); + rc = ext4_fread(&fd, read_buffer, d, &rb); + + if (rc != EOK) + break; + + if (rb != d) { + printf("Read count error\n"); + return -1; + } + + if (memcmp(read_buffer, write_buffer, d)) { + printf("Read compare error\n"); + return -1; + } + + len -= d; + } + } + + return rc; +} + +static int multi_fremove(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fremove(path1); + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_dcreate(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_dir_mk(path1); + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_dremove(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_dir_rm(path1); + if (rc != EOK) + break; + } + + return rc; +} + +struct ext4_mount_stats saved_stats; + +static int stats_save(const char *p) +{ + char path[256]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_mount_point_stats(path, &saved_stats); +} + +static int stats_check(const char *p) +{ + char path[256]; + int rc; + + struct ext4_mount_stats actual_stats; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + rc = ext4_mount_point_stats(path, &actual_stats); + + if (rc != EOK) + return rc; + + if (memcmp(&saved_stats, &actual_stats, + sizeof(struct ext4_mount_stats))) { + if (verbose) { + printf("\tMount point stats error:\n"); + printf("\tsaved_stats:\n"); + printf("\tinodes_count = %" PRIu32"\n", + saved_stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + saved_stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", + saved_stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + saved_stats.free_blocks_count); + printf("\tblock_size = %" PRIu32"\n", + saved_stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + saved_stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + saved_stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + saved_stats.inodes_per_group); + printf("\tvolume_name = %s\n", saved_stats.volume_name); + printf("\tactual_stats:\n"); + printf("\tinodes_count = %" PRIu32"\n", + actual_stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + actual_stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", + actual_stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + actual_stats.free_blocks_count); + printf("\tblock_size = %d\n", actual_stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + actual_stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + actual_stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + actual_stats.inodes_per_group); + printf("\tvolume_name = %s\n", + actual_stats.volume_name); + } + return -1; + } + + return rc; +} + +static char *entry_to_str(uint8_t type) +{ + switch (type) { + case EXT4_DE_UNKNOWN: + return "[unk] "; + case EXT4_DE_REG_FILE: + return "[fil] "; + case EXT4_DE_DIR: + return "[dir] "; + case EXT4_DE_CHRDEV: + return "[cha] "; + case EXT4_DE_BLKDEV: + return "[blk] "; + case EXT4_DE_FIFO: + return "[fif] "; + case EXT4_DE_SOCK: + return "[soc] "; + case EXT4_DE_SYMLINK: + return "[sym] "; + default: + break; + } + return "[???]"; +} + +static int winsock_init(void) +{ +#if WIN32 + int rc; + static WSADATA wsaData; + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rc != 0) { + return -1; + } +#endif + return 0; +} + +static void winsock_fini(void) +{ +#if WIN32 + WSACleanup(); +#endif +} diff --git a/clib/lib/lwext4/include/ext4.h b/clib/lib/lwext4/include/ext4.h new file mode 100644 index 0000000..18e756e --- /dev/null +++ b/clib/lib/lwext4/include/ext4.h @@ -0,0 +1,628 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4.h + * @brief Ext4 high level operations (files, directories, mount points...). + * Client has to include only this file. + */ + +#ifndef EXT4_H_ +#define EXT4_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include + +/********************************OS LOCK INFERFACE***************************/ + +/**@brief OS dependent lock interface.*/ +struct ext4_lock { + + /**@brief Lock access to mount point.*/ + void (*lock)(void); + + /**@brief Unlock access to mount point.*/ + void (*unlock)(void); +}; + +/********************************FILE DESCRIPTOR*****************************/ + +/**@brief File descriptor. */ +typedef struct ext4_file { + + /**@brief Mount point handle.*/ + struct ext4_mountpoint *mp; + + /**@brief File inode id.*/ + uint32_t inode; + + /**@brief Open flags.*/ + uint32_t flags; + + /**@brief File size.*/ + uint64_t fsize; + + /**@brief Actual file position.*/ + uint64_t fpos; +} ext4_file; + +/*****************************DIRECTORY DESCRIPTOR***************************/ + +/**@brief Directory entry descriptor. */ +typedef struct ext4_direntry { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; + uint8_t name[255]; +} ext4_direntry; + +/**@brief Directory descriptor. */ +typedef struct ext4_dir { + /**@brief File descriptor.*/ + ext4_file f; + /**@brief Current directory entry.*/ + ext4_direntry de; + /**@brief Next entry offset.*/ + uint64_t next_off; +} ext4_dir; + +/********************************MOUNT OPERATIONS****************************/ + +/**@brief Register block device. + * + * @param bd Block device. + * @param dev_name Block device name. + * + * @return Standard error code.*/ +int ext4_device_register(struct ext4_blockdev *bd, + const char *dev_name); + +/**@brief Un-register block device. + * + * @param dev_name Block device name. + * + * @return Standard error code.*/ +int ext4_device_unregister(const char *dev_name); + +/**@brief Un-register all block devices. + * + * @return Standard error code.*/ +int ext4_device_unregister_all(void); + +/**@brief Mount a block device with EXT4 partition to the mount point. + * + * @param dev_name Block device name (@ref ext4_device_register). + * @param mount_point Mount point, for example: + * - / + * - /my_partition/ + * - /my_second_partition/ + * @param read_only mount as read-only mode. + * + * @return Standard error code */ +int ext4_mount(const char *dev_name, + const char *mount_point, + bool read_only); + +/**@brief Umount operation. + * + * @param mount_point Mount point. + * + * @return Standard error code */ +int ext4_umount(const char *mount_point); + +/**@brief Starts journaling. Journaling start/stop functions are transparent + * and might be used on filesystems without journaling support. + * @warning Usage: + * ext4_mount("sda1", "/"); + * ext4_journal_start("/"); + * + * //File operations here... + * + * ext4_journal_stop("/"); + * ext4_umount("/"); + * @param mount_point Mount point. + * + * @return Standard error code. */ +int ext4_journal_start(const char *mount_point); + +/**@brief Stops journaling. Journaling start/stop functions are transparent + * and might be used on filesystems without journaling support. + * + * @param mount_point Mount point name. + * + * @return Standard error code. */ +int ext4_journal_stop(const char *mount_point); + +/**@brief Journal recovery. + * @warning Must be called after @ref ext4_mount. + * + * @param mount_point Mount point. + * + * @return Standard error code. */ +int ext4_recover(const char *mount_point); + +/**@brief Some of the filesystem stats. */ +struct ext4_mount_stats { + uint32_t inodes_count; + uint32_t free_inodes_count; + uint64_t blocks_count; + uint64_t free_blocks_count; + + uint32_t block_size; + uint32_t block_group_count; + uint32_t blocks_per_group; + uint32_t inodes_per_group; + + char volume_name[16]; +}; + +/**@brief Get file mount point stats. + * + * @param mount_point Mount point. + * @param stats Filesystem stats. + * + * @return Standard error code. */ +int ext4_mount_point_stats(const char *mount_point, + struct ext4_mount_stats *stats); + +/**@brief Setup OS lock routines. + * + * @param mount_point Mount point. + * @param locks Lock and unlock functions + * + * @return Standard error code. */ +int ext4_mount_setup_locks(const char *mount_point, + const struct ext4_lock *locks); + +/**@brief Acquire the filesystem superblock pointer of a mp. + * + * @param mount_point Mount point. + * @param sb Superblock handle + * + * @return Standard error code. */ +int ext4_get_sblock(const char *mount_point, struct ext4_sblock **sb); + +/**@brief Enable/disable write back cache mode. + * @warning Default model of cache is write trough. It means that when You do: + * + * ext4_fopen(...); + * ext4_fwrite(...); + * < --- data is flushed to physical drive + * + * When you do: + * ext4_cache_write_back(..., 1); + * ext4_fopen(...); + * ext4_fwrite(...); + * < --- data is NOT flushed to physical drive + * ext4_cache_write_back(..., 0); + * < --- when write back mode is disabled all + * cache data will be flushed + * To enable write back mode permanently just call this function + * once after ext4_mount (and disable before ext4_umount). + * + * Some of the function use write back cache mode internally. + * If you enable write back mode twice you have to disable it twice + * to flush all data: + * + * ext4_cache_write_back(..., 1); + * ext4_cache_write_back(..., 1); + * + * ext4_cache_write_back(..., 0); + * ext4_cache_write_back(..., 0); + * + * Write back mode is useful when you want to create a lot of empty + * files/directories. + * + * @param path Mount point. + * @param on Enable/disable cache writeback mode. + * + * @return Standard error code. */ +int ext4_cache_write_back(const char *path, bool on); + + +/**@brief Force cache flush. + * + * @param path Mount point. + * + * @return Standard error code. */ +int ext4_cache_flush(const char *path); + +/********************************FILE OPERATIONS*****************************/ + +/**@brief Remove file by path. + * + * @param path Path to file. + * + * @return Standard error code. */ +int ext4_fremove(const char *path); + +/**@brief Create a hardlink for a file. + * + * @param path Path to file. + * @param hardlink_path Path of hardlink. + * + * @return Standard error code. */ +int ext4_flink(const char *path, const char *hardlink_path); + +/**@brief Rename file. + * @param path Source. + * @param new_path Destination. + * @return Standard error code. */ +int ext4_frename(const char *path, const char *new_path); + +/**@brief File open function. + * + * @param file File handle. + * @param path File path, has to start from mount point:/my_partition/file. + * @param flags File open flags. + * |---------------------------------------------------------------| + * | r or rb O_RDONLY | + * |---------------------------------------------------------------| + * | w or wb O_WRONLY|O_CREAT|O_TRUNC | + * |---------------------------------------------------------------| + * | a or ab O_WRONLY|O_CREAT|O_APPEND | + * |---------------------------------------------------------------| + * | r+ or rb+ or r+b O_RDWR | + * |---------------------------------------------------------------| + * | w+ or wb+ or w+b O_RDWR|O_CREAT|O_TRUNC | + * |---------------------------------------------------------------| + * | a+ or ab+ or a+b O_RDWR|O_CREAT|O_APPEND | + * |---------------------------------------------------------------| + * + * @return Standard error code.*/ +int ext4_fopen(ext4_file *file, const char *path, const char *flags); + +/**@brief Alternate file open function. + * + * @param file File handle. + * @param path File path, has to start from mount point:/my_partition/file. + * @param flags File open flags. + * + * @return Standard error code.*/ +int ext4_fopen2(ext4_file *file, const char *path, int flags); + +/**@brief File close function. + * + * @param file File handle. + * + * @return Standard error code.*/ +int ext4_fclose(ext4_file *file); + + +/**@brief File truncate function. + * + * @param file File handle. + * @param size New file size. + * + * @return Standard error code.*/ +int ext4_ftruncate(ext4_file *file, uint64_t size); + +/**@brief Read data from file. + * + * @param file File handle. + * @param buf Output buffer. + * @param size Bytes to read. + * @param rcnt Bytes read (NULL allowed). + * + * @return Standard error code.*/ +int ext4_fread(ext4_file *file, void *buf, size_t size, size_t *rcnt); + +/**@brief Write data to file. + * + * @param file File handle. + * @param buf Data to write + * @param size Write length.. + * @param wcnt Bytes written (NULL allowed). + * + * @return Standard error code.*/ +int ext4_fwrite(ext4_file *file, const void *buf, size_t size, size_t *wcnt); + +/**@brief File seek operation. + * + * @param file File handle. + * @param offset Offset to seek. + * @param origin Seek type: + * @ref SEEK_SET + * @ref SEEK_CUR + * @ref SEEK_END + * + * @return Standard error code.*/ +int ext4_fseek(ext4_file *file, int64_t offset, uint32_t origin); + +/**@brief Get file position. + * + * @param file File handle. + * + * @return Actual file position */ +uint64_t ext4_ftell(ext4_file *file); + +/**@brief Get file size. + * + * @param file File handle. + * + * @return File size. */ +uint64_t ext4_fsize(ext4_file *file); + + +/**@brief Get inode of file/directory/link. + * + * @param path Parh to file/dir/link. + * @param ret_ino Inode number. + * @param inode Inode internals. + * + * @return Standard error code.*/ +int ext4_raw_inode_fill(const char *path, uint32_t *ret_ino, + struct ext4_inode *inode); + +/**@brief Check if inode exists. + * + * @param path Parh to file/dir/link. + * @param type Inode type. + * @ref EXT4_DIRENTRY_UNKNOWN + * @ref EXT4_DE_REG_FILE + * @ref EXT4_DE_DIR + * @ref EXT4_DE_CHRDEV + * @ref EXT4_DE_BLKDEV + * @ref EXT4_DE_FIFO + * @ref EXT4_DE_SOCK + * @ref EXT4_DE_SYMLINK + * + * @return Standard error code.*/ +int ext4_inode_exist(const char *path, int type); + +/**@brief Change file/directory/link mode bits. + * + * @param path Path to file/dir/link. + * @param mode New mode bits (for example 0777). + * + * @return Standard error code.*/ +int ext4_mode_set(const char *path, uint32_t mode); + + +/**@brief Get file/directory/link mode bits. + * + * @param path Path to file/dir/link. + * @param mode New mode bits (for example 0777). + * + * @return Standard error code.*/ +int ext4_mode_get(const char *path, uint32_t *mode); + +/**@brief Change file owner and group. + * + * @param path Path to file/dir/link. + * @param uid User id. + * @param gid Group id. + * + * @return Standard error code.*/ +int ext4_owner_set(const char *path, uint32_t uid, uint32_t gid); + +/**@brief Get file/directory/link owner and group. + * + * @param path Path to file/dir/link. + * @param uid User id. + * @param gid Group id. + * + * @return Standard error code.*/ +int ext4_owner_get(const char *path, uint32_t *uid, uint32_t *gid); + +/**@brief Set file/directory/link access time. + * + * @param path Path to file/dir/link. + * @param atime Access timestamp. + * + * @return Standard error code.*/ +int ext4_atime_set(const char *path, uint32_t atime); + +/**@brief Set file/directory/link modify time. + * + * @param path Path to file/dir/link. + * @param mtime Modify timestamp. + * + * @return Standard error code.*/ +int ext4_mtime_set(const char *path, uint32_t mtime); + +/**@brief Set file/directory/link change time. + * + * @param path Path to file/dir/link. + * @param ctime Change timestamp. + * + * @return Standard error code.*/ +int ext4_ctime_set(const char *path, uint32_t ctime); + +/**@brief Get file/directory/link access time. + * + * @param path Path to file/dir/link. + * @param atime Access timestamp. + * + * @return Standard error code.*/ +int ext4_atime_get(const char *path, uint32_t *atime); + +/**@brief Get file/directory/link modify time. + * + * @param path Path to file/dir/link. + * @param mtime Modify timestamp. + * + * @return Standard error code.*/ +int ext4_mtime_get(const char *path, uint32_t *mtime); + +/**@brief Get file/directory/link change time. + * + * @param path Pathto file/dir/link. + * @param ctime Change timestamp. + * + * @return standard error code*/ +int ext4_ctime_get(const char *path, uint32_t *ctime); + +/**@brief Create symbolic link. + * + * @param target Destination entry path. + * @param path Source entry path. + * + * @return Standard error code.*/ +int ext4_fsymlink(const char *target, const char *path); + +/**@brief Create special file. + * @param path Path to new special file. + * @param filetype Filetype of the new special file. + * (that must not be regular file, directory, or unknown type) + * @param dev If filetype is char device or block device, + * the device number will become the payload in the inode. + * @return Standard error code.*/ +int ext4_mknod(const char *path, int filetype, uint32_t dev); + +/**@brief Read symbolic link payload. + * + * @param path Path to symlink. + * @param buf Output buffer. + * @param bufsize Output buffer max size. + * @param rcnt Bytes read. + * + * @return Standard error code.*/ +int ext4_readlink(const char *path, char *buf, size_t bufsize, size_t *rcnt); + +/**@brief Set extended attribute. + * + * @param path Path to file/directory + * @param name Name of the entry to add. + * @param name_len Length of @name in bytes. + * @param data Data of the entry to add. + * @param data_size Size of data to add. + * + * @return Standard error code.*/ +int ext4_setxattr(const char *path, const char *name, size_t name_len, + const void *data, size_t data_size); + +/**@brief Get extended attribute. + * + * @param path Path to file/directory. + * @param name Name of the entry to get. + * @param name_len Length of @name in bytes. + * @param buf Data of the entry to get. + * @param buf_size Size of data to get. + * + * @return Standard error code.*/ +int ext4_getxattr(const char *path, const char *name, size_t name_len, + void *buf, size_t buf_size, size_t *data_size); + +/**@brief List extended attributes. + * + * @param path Path to file/directory. + * @param list List to hold the name of entries. + * @param size Size of @list in bytes. + * @param ret_size Used bytes of @list. + * + * @return Standard error code.*/ +int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size); + +/**@brief Remove extended attribute. + * + * @param path Path to file/directory. + * @param name Name of the entry to remove. + * @param name_len Length of @name in bytes. + * + * @return Standard error code.*/ +int ext4_removexattr(const char *path, const char *name, size_t name_len); + + +/*********************************DIRECTORY OPERATION***********************/ + +/**@brief Recursive directory remove. + * + * @param path Directory path to remove + * + * @return Standard error code.*/ +int ext4_dir_rm(const char *path); + +/**@brief Rename/move directory. + * + * @param path Source path. + * @param new_path Destination path. + * + * @return Standard error code. */ +int ext4_dir_mv(const char *path, const char *new_path); + +/**@brief Create new directory. + * + * @param path Directory name. + * + * @return Standard error code.*/ +int ext4_dir_mk(const char *path); + +/**@brief Directory open. + * + * @param dir Directory handle. + * @param path Directory path. + * + * @return Standard error code.*/ +int ext4_dir_open(ext4_dir *dir, const char *path); + +/**@brief Directory close. + * + * @param dir directory handle. + * + * @return Standard error code.*/ +int ext4_dir_close(ext4_dir *dir); + +/**@brief Return next directory entry. + * + * @param dir Directory handle. + * + * @return Directory entry id (NULL if no entry)*/ +const ext4_direntry *ext4_dir_entry_next(ext4_dir *dir); + +/**@brief Rewine directory entry offset. + * + * @param dir Directory handle.*/ +void ext4_dir_entry_rewind(ext4_dir *dir); + + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_balloc.h b/clib/lib/lwext4/include/ext4_balloc.h new file mode 100644 index 0000000..5f163d5 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_balloc.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_balloc.h + * @brief Physical block allocator. + */ + +#ifndef EXT4_BALLOC_H_ +#define EXT4_BALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +#include +#include + +/**@brief Compute number of block group from block address. + * @param s superblock pointer. + * @param baddr Absolute address of block. + * @return Block group index + */ +uint32_t ext4_balloc_get_bgid_of_block(struct ext4_sblock *s, + ext4_fsblk_t baddr); + +/**@brief Compute the starting block address of a block group + * @param s superblock pointer. + * @param bgid block group index + * @return Block address + */ +ext4_fsblk_t ext4_balloc_get_block_of_bgid(struct ext4_sblock *s, + uint32_t bgid); + +/**@brief Calculate and set checksum of block bitmap. + * @param sb superblock pointer. + * @param bg block group + * @param bitmap bitmap buffer + */ +void ext4_balloc_set_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap); + +/**@brief Free block from inode. + * @param inode_ref inode reference + * @param baddr block address + * @return standard error code*/ +int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr); + +/**@brief Free blocks from inode. + * @param inode_ref inode reference + * @param first block address + * @param count block count + * @return standard error code*/ +int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t first, uint32_t count); + +/**@brief Allocate block procedure. + * @param inode_ref inode reference + * @param baddr allocated block address + * @return standard error code*/ +int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *baddr); + +/**@brief Try allocate selected block. + * @param inode_ref inode reference + * @param baddr block address to allocate + * @param free if baddr is not allocated + * @return standard error code*/ +int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr, bool *free); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BALLOC_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_bcache.h b/clib/lib/lwext4/include/ext4_bcache.h new file mode 100644 index 0000000..de12bd5 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_bcache.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bcache.h + * @brief Block cache allocator. + */ + +#ifndef EXT4_BCACHE_H_ +#define EXT4_BCACHE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include +#include + +#define EXT4_BLOCK_ZERO() \ + {.lb_id = 0, .data = 0} + +/**@brief Single block descriptor*/ +struct ext4_block { + /**@brief Logical block ID*/ + uint64_t lb_id; + + /**@brief Buffer */ + struct ext4_buf *buf; + + /**@brief Data buffer.*/ + uint8_t *data; +}; + +struct ext4_bcache; + +/**@brief Single block descriptor*/ +struct ext4_buf { + /**@brief Flags*/ + int flags; + + /**@brief Logical block address*/ + uint64_t lba; + + /**@brief Data buffer.*/ + uint8_t *data; + + /**@brief LRU priority. (unused) */ + uint32_t lru_prio; + + /**@brief LRU id.*/ + uint32_t lru_id; + + /**@brief Reference count table*/ + uint32_t refctr; + + /**@brief The block cache this buffer belongs to. */ + struct ext4_bcache *bc; + + /**@brief Whether or not buffer is on dirty list.*/ + bool on_dirty_list; + + /**@brief LBA tree node*/ + RB_ENTRY(ext4_buf) lba_node; + + /**@brief LRU tree node*/ + RB_ENTRY(ext4_buf) lru_node; + + /**@brief Dirty list node*/ + SLIST_ENTRY(ext4_buf) dirty_node; + + /**@brief Callback routine after a disk-write operation. + * @param bc block cache descriptor + * @param buf buffer descriptor + * @param standard error code returned by bdev->bwrite() + * @param arg argument passed to this routine*/ + void (*end_write)(struct ext4_bcache *bc, + struct ext4_buf *buf, + int res, + void *arg); + + /**@brief argument passed to end_write() callback.*/ + void *end_write_arg; +}; + +/**@brief Block cache descriptor*/ +struct ext4_bcache { + + /**@brief Item count in block cache*/ + uint32_t cnt; + + /**@brief Item size in block cache*/ + uint32_t itemsize; + + /**@brief Last recently used counter*/ + uint32_t lru_ctr; + + /**@brief Currently referenced datablocks*/ + uint32_t ref_blocks; + + /**@brief Maximum referenced datablocks*/ + uint32_t max_ref_blocks; + + /**@brief The blockdev binded to this block cache*/ + struct ext4_blockdev *bdev; + + /**@brief The cache should not be shaked */ + bool dont_shake; + + /**@brief A tree holding all bufs*/ + RB_HEAD(ext4_buf_lba, ext4_buf) lba_root; + + /**@brief A tree holding unreferenced bufs*/ + RB_HEAD(ext4_buf_lru, ext4_buf) lru_root; + + /**@brief A singly-linked list holding dirty buffers*/ + SLIST_HEAD(ext4_buf_dirty, ext4_buf) dirty_list; +}; + +/**@brief buffer state bits + * + * - BC♡UPTODATE: Buffer contains valid data. + * - BC_DIRTY: Buffer is dirty. + * - BC_FLUSH: Buffer will be immediately flushed, + * when no one references it. + * - BC_TMP: Buffer will be dropped once its refctr + * reaches zero. + */ +enum bcache_state_bits { + BC_UPTODATE, + BC_DIRTY, + BC_FLUSH, + BC_TMP +}; + +#define ext4_bcache_set_flag(buf, b) \ + (buf)->flags |= 1 << (b) + +#define ext4_bcache_clear_flag(buf, b) \ + (buf)->flags &= ~(1 << (b)) + +#define ext4_bcache_test_flag(buf, b) \ + (((buf)->flags & (1 << (b))) >> (b)) + +static inline void ext4_bcache_set_dirty(struct ext4_buf *buf) { + ext4_bcache_set_flag(buf, BC_UPTODATE); + ext4_bcache_set_flag(buf, BC_DIRTY); +} + +static inline void ext4_bcache_clear_dirty(struct ext4_buf *buf) { + ext4_bcache_clear_flag(buf, BC_UPTODATE); + ext4_bcache_clear_flag(buf, BC_DIRTY); +} + +/**@brief Increment reference counter of buf by 1.*/ +#define ext4_bcache_inc_ref(buf) ((buf)->refctr++) + +/**@brief Decrement reference counter of buf by 1.*/ +#define ext4_bcache_dec_ref(buf) ((buf)->refctr--) + +/**@brief Insert buffer to dirty cache list + * @param bc block cache descriptor + * @param buf buffer descriptor */ +static inline void +ext4_bcache_insert_dirty_node(struct ext4_bcache *bc, struct ext4_buf *buf) { + if (!buf->on_dirty_list) { + SLIST_INSERT_HEAD(&bc->dirty_list, buf, dirty_node); + buf->on_dirty_list = true; + } +} + +/**@brief Remove buffer to dirty cache list + * @param bc block cache descriptor + * @param buf buffer descriptor */ +static inline void +ext4_bcache_remove_dirty_node(struct ext4_bcache *bc, struct ext4_buf *buf) { + if (buf->on_dirty_list) { + SLIST_REMOVE(&bc->dirty_list, buf, ext4_buf, dirty_node); + buf->on_dirty_list = false; + } +} + + +/**@brief Dynamic initialization of block cache. + * @param bc block cache descriptor + * @param cnt items count in block cache + * @param itemsize single item size (in bytes) + * @return standard error code*/ +int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt, + uint32_t itemsize); + +/**@brief Do cleanup works on block cache. + * @param bc block cache descriptor.*/ +void ext4_bcache_cleanup(struct ext4_bcache *bc); + +/**@brief Dynamic de-initialization of block cache. + * @param bc block cache descriptor + * @return standard error code*/ +int ext4_bcache_fini_dynamic(struct ext4_bcache *bc); + +/**@brief Get a buffer with the lowest LRU counter in bcache. + * @param bc block cache descriptor + * @return buffer with the lowest LRU counter*/ +struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc); + +/**@brief Drop unreferenced buffer from bcache. + * @param bc block cache descriptor + * @param buf buffer*/ +void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf); + +/**@brief Invalidate a buffer. + * @param bc block cache descriptor + * @param buf buffer*/ +void ext4_bcache_invalidate_buf(struct ext4_bcache *bc, + struct ext4_buf *buf); + +/**@brief Invalidate a range of buffers. + * @param bc block cache descriptor + * @param from starting lba + * @param cnt block counts*/ +void ext4_bcache_invalidate_lba(struct ext4_bcache *bc, + uint64_t from, + uint32_t cnt); + +/**@brief Find existing buffer from block cache memory. + * Unreferenced block allocation is based on LRU + * (Last Recently Used) algorithm. + * @param bc block cache descriptor + * @param b block to alloc + * @param lba logical block address + * @return block cache buffer */ +struct ext4_buf * +ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b, + uint64_t lba); + +/**@brief Allocate block from block cache memory. + * Unreferenced block allocation is based on LRU + * (Last Recently Used) algorithm. + * @param bc block cache descriptor + * @param b block to alloc + * @param is_new block is new (needs to be read) + * @return standard error code*/ +int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b, + bool *is_new); + +/**@brief Free block from cache memory (decrement reference counter). + * @param bc block cache descriptor + * @param b block to free + * @return standard error code*/ +int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b); + +/**@brief Return a full status of block cache. + * @param bc block cache descriptor + * @return full status*/ +bool ext4_bcache_is_full(struct ext4_bcache *bc); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BCACHE_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_bitmap.h b/clib/lib/lwext4/include/ext4_bitmap.h new file mode 100644 index 0000000..6bcb100 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_bitmap.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bitmap.h + * @brief Block/inode bitmap allocator. + */ + +#ifndef EXT4_BITMAP_H_ +#define EXT4_BITMAP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/**@brief Set bitmap bit. + * @param bmap bitmap + * @param bit bit to set*/ +static inline void ext4_bmap_bit_set(uint8_t *bmap, uint32_t bit) +{ + *(bmap + (bit >> 3)) |= (1 << (bit & 7)); +} + +/**@brief Clear bitmap bit. + * @param bmap bitmap buffer + * @param bit bit to clear*/ +static inline void ext4_bmap_bit_clr(uint8_t *bmap, uint32_t bit) +{ + *(bmap + (bit >> 3)) &= ~(1 << (bit & 7)); +} + +/**@brief Check if the bitmap bit is set. + * @param bmap bitmap buffer + * @param bit bit to check*/ +static inline bool ext4_bmap_is_bit_set(uint8_t *bmap, uint32_t bit) +{ + return (*(bmap + (bit >> 3)) & (1 << (bit & 7))); +} + +/**@brief Check if the bitmap bit is clear. + * @param bmap bitmap buffer + * @param bit bit to check*/ +static inline bool ext4_bmap_is_bit_clr(uint8_t *bmap, uint32_t bit) +{ + return !ext4_bmap_is_bit_set(bmap, bit); +} + +/**@brief Free range of bits in bitmap. + * @param bmap bitmap buffer + * @param sbit start bit + * @param bcnt bit count*/ +void ext4_bmap_bits_free(uint8_t *bmap, uint32_t sbit, uint32_t bcnt); + +/**@brief Find first clear bit in bitmap. + * @param sbit start bit of search + * @param ebit end bit of search + * @param bit_id output parameter (first free bit) + * @return standard error code*/ +int ext4_bmap_bit_find_clr(uint8_t *bmap, uint32_t sbit, uint32_t ebit, + uint32_t *bit_id); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BITMAP_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_block_group.h b/clib/lib/lwext4/include/ext4_block_group.h new file mode 100644 index 0000000..a31d3e7 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_block_group.h @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_block_group.h + * @brief Block group function set. + */ + +#ifndef EXT4_BLOCK_GROUP_H_ +#define EXT4_BLOCK_GROUP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +/**@brief Get address of block with data block bitmap. + * @param bg pointer to block group + * @param s pointer to superblock + * @return Address of block with block bitmap + */ +static inline uint64_t ext4_bg_get_block_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint64_t v = to_le32(bg->block_bitmap_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->block_bitmap_hi) << 32; + + return v; +} + +/**@brief Set address of block with data block bitmap. + * @param bg pointer to block group + * @param s pointer to superblock + * @param blk block to set + */ +static inline void ext4_bg_set_block_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + + bg->block_bitmap_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->block_bitmap_hi = to_le32(blk >> 32); + +} + +/**@brief Get address of block with i-node bitmap. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Address of block with i-node bitmap + */ +static inline uint64_t ext4_bg_get_inode_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + + uint64_t v = to_le32(bg->inode_bitmap_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->inode_bitmap_hi) << 32; + + return v; +} + +/**@brief Set address of block with i-node bitmap. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param blk block to set + */ +static inline void ext4_bg_set_inode_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + bg->inode_bitmap_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_bitmap_hi = to_le32(blk >> 32); + +} + + +/**@brief Get address of the first block of the i-node table. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Address of first block of i-node table + */ +static inline uint64_t +ext4_bg_get_inode_table_first_block(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint64_t v = to_le32(bg->inode_table_first_block_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->inode_table_first_block_hi) << 32; + + return v; +} + +/**@brief Set address of the first block of the i-node table. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param blk block to set + */ +static inline void +ext4_bg_set_inode_table_first_block(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + bg->inode_table_first_block_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_table_first_block_hi = to_le32(blk >> 32); +} + +/**@brief Get number of free blocks in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of free blocks in block group + */ +static inline uint32_t ext4_bg_get_free_blocks_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->free_blocks_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->free_blocks_count_hi) << 16; + + return v; +} + +/**@brief Set number of free blocks in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of free blocks in block group + */ +static inline void ext4_bg_set_free_blocks_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->free_blocks_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->free_blocks_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of free i-nodes in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of free i-nodes in block group + */ +static inline uint32_t ext4_bg_get_free_inodes_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->free_inodes_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->free_inodes_count_hi) << 16; + + return v; +} + +/**@brief Set number of free i-nodes in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of free i-nodes in block group + */ +static inline void ext4_bg_set_free_inodes_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->free_inodes_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->free_inodes_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of used directories in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of used directories in block group + */ +static inline uint32_t ext4_bg_get_used_dirs_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->used_dirs_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->used_dirs_count_hi) << 16; + + return v; +} + +/**@brief Set number of used directories in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of used directories in block group + */ +static inline void ext4_bg_set_used_dirs_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->used_dirs_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->used_dirs_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of unused i-nodes. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of unused i-nodes + */ +static inline uint32_t ext4_bg_get_itable_unused(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + + uint32_t v = to_le16(bg->itable_unused_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->itable_unused_hi) << 16; + + return v; +} + +/**@brief Set number of unused i-nodes. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of unused i-nodes + */ +static inline void ext4_bg_set_itable_unused(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->itable_unused_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->itable_unused_hi = to_le16(cnt >> 16); +} + +/**@brief Set checksum of block group. + * @param bg Pointer to block group + * @param crc Cheksum of block group + */ +static inline void ext4_bg_set_checksum(struct ext4_bgroup *bg, uint16_t crc) +{ + bg->checksum = to_le16(crc); +} + +/**@brief Check if block group has a flag. + * @param bg Pointer to block group + * @param f Flag to be checked + * @return True if flag is set to 1 + */ +static inline bool ext4_bg_has_flag(struct ext4_bgroup *bg, uint32_t f) +{ + return to_le16(bg->flags) & f; +} + +/**@brief Set flag of block group. + * @param bg Pointer to block group + * @param f Flag to be set + */ +static inline void ext4_bg_set_flag(struct ext4_bgroup *bg, uint32_t f) +{ + uint16_t flags = to_le16(bg->flags); + flags |= f; + bg->flags = to_le16(flags); +} + +/**@brief Clear flag of block group. + * @param bg Pointer to block group + * @param f Flag to be cleared + */ +static inline void ext4_bg_clear_flag(struct ext4_bgroup *bg, uint32_t f) +{ + uint16_t flags = to_le16(bg->flags); + flags &= ~f; + bg->flags = to_le16(flags); +} + +/**@brief Calculate CRC16 of the block group. + * @param crc Init value + * @param buffer Input buffer + * @param len Sizeof input buffer + * @return Computed CRC16*/ +uint16_t ext4_bg_crc16(uint16_t crc, const uint8_t *buffer, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BLOCK_GROUP_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_blockdev.h b/clib/lib/lwext4/include/ext4_blockdev.h new file mode 100644 index 0000000..668fb05 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_blockdev.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_blockdev.h + * @brief Block device module. + */ + +#ifndef EXT4_BLOCKDEV_H_ +#define EXT4_BLOCKDEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct ext4_blockdev_iface { + /**@brief Open device function + * @param bdev block device.*/ + int (*open)(struct ext4_blockdev *bdev); + + /**@brief Block read function. + * @param bdev block device + * @param buf output buffer + * @param blk_id block id + * @param blk_cnt block count*/ + int (*bread)(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); + + /**@brief Block write function. + * @param buf input buffer + * @param blk_id block id + * @param blk_cnt block count*/ + int (*bwrite)(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); + + /**@brief Close device function. + * @param bdev block device.*/ + int (*close)(struct ext4_blockdev *bdev); + + /**@brief Lock block device. Required in multi partition mode + * operations. Not mandatory field. + * @param bdev block device.*/ + int (*lock)(struct ext4_blockdev *bdev); + + /**@brief Unlock block device. Required in multi partition mode + * operations. Not mandatory field. + * @param bdev block device.*/ + int (*unlock)(struct ext4_blockdev *bdev); + + /**@brief Block size (bytes): physical*/ + uint32_t ph_bsize; + + /**@brief Block count: physical*/ + uint64_t ph_bcnt; + + /**@brief Block size buffer: physical*/ + uint8_t *ph_bbuf; + + /**@brief Reference counter to block device interface*/ + uint32_t ph_refctr; + + /**@brief Physical read counter*/ + uint32_t bread_ctr; + + /**@brief Physical write counter*/ + uint32_t bwrite_ctr; + + /**@brief User data pointer*/ + void* p_user; +}; + +/**@brief Definition of the simple block device.*/ +struct ext4_blockdev { + /**@brief Block device interface*/ + struct ext4_blockdev_iface *bdif; + + /**@brief Offset in bdif. For multi partition mode.*/ + uint64_t part_offset; + + /**@brief Part size in bdif. For multi partition mode.*/ + uint64_t part_size; + + /**@brief Block cache.*/ + struct ext4_bcache *bc; + + /**@brief Block size (bytes) logical*/ + uint32_t lg_bsize; + + /**@brief Block count: logical*/ + uint64_t lg_bcnt; + + /**@brief Cache write back mode reference counter*/ + uint32_t cache_write_back; + + /**@brief The filesystem this block device belongs to. */ + struct ext4_fs *fs; + + void *journal; +}; + +/**@brief Static initialization of the block device.*/ +#define EXT4_BLOCKDEV_STATIC_INSTANCE(__name, __bsize, __bcnt, __open, __bread,\ + __bwrite, __close, __lock, __unlock) \ + static uint8_t __name##_ph_bbuf[(__bsize)]; \ + static struct ext4_blockdev_iface __name##_iface = { \ + .open = __open, \ + .bread = __bread, \ + .bwrite = __bwrite, \ + .close = __close, \ + .lock = __lock, \ + .unlock = __unlock, \ + .ph_bsize = __bsize, \ + .ph_bcnt = __bcnt, \ + .ph_bbuf = __name##_ph_bbuf, \ + }; \ + static struct ext4_blockdev __name = { \ + .bdif = &__name##_iface, \ + .part_offset = 0, \ + .part_size = (__bcnt) * (__bsize), \ + } + +/**@brief Block device initialization. + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_init(struct ext4_blockdev *bdev); + +/**@brief Binds a bcache to block device. + * @param bdev block device descriptor + * @param bc block cache descriptor + * @return standard error code*/ +int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc); + +/**@brief Close block device + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_fini(struct ext4_blockdev *bdev); + +/**@brief Flush data in given buffer to disk. + * @param bdev block device descriptor + * @param buf buffer + * @return standard error code*/ +int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf); + +/**@brief Flush data in buffer of given lba to disk, + * if that buffer exists in block cache. + * @param bdev block device descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba); + +/**@brief Set logical block size in block device. + * @param bdev block device descriptor + * @param lb_bsize logical block size (in bytes)*/ +void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint32_t lb_bsize); + +/**@brief Block get function (through cache, don't read). + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba); + +/**@brief Block get function (through cache). + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba); + +/**@brief Block set procedure (through cache). + * @param bdev block device descriptor + * @param b block descriptor + * @return standard error code*/ +int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b); + +/**@brief Block read procedure (without cache) + * @param bdev block device descriptor + * @param buf output buffer + * @param lba logical block address + * @return standard error code*/ +int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba, + uint32_t cnt); + +/**@brief Block write procedure (without cache) + * @param bdev block device descriptor + * @param buf output buffer + * @param lba logical block address + * @return standard error code*/ +int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf, + uint64_t lba, uint32_t cnt); + +/**@brief Write to block device (by direct address). + * @param bdev block device descriptor + * @param off byte offset in block device + * @param buf input buffer + * @param len length of the write buffer + * @return standard error code*/ +int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off, + const void *buf, uint32_t len); + +/**@brief Read freom block device (by direct address). + * @param bdev block device descriptor + * @param off byte offset in block device + * @param buf input buffer + * @param len length of the write buffer + * @return standard error code*/ +int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf, + uint32_t len); + +/**@brief Flush all dirty buffers to disk + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_cache_flush(struct ext4_blockdev *bdev); + +/**@brief Enable/disable write back cache mode + * @param bdev block device descriptor + * @param on_off + * !0 - ENABLE + * 0 - DISABLE (all delayed cache buffers will be flushed) + * @return standard error code*/ +int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BLOCKDEV_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_config.h b/clib/lib/lwext4/include/ext4_config.h new file mode 100644 index 0000000..b70b551 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_config.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_config.h + * @brief Configuration file. + */ + +#ifndef EXT4_CONFIG_H_ +#define EXT4_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if !CONFIG_USE_DEFAULT_CFG +#define CONFIG_USE_DEFAULT_CONFIG 0 +#define CONFIG_DEBUG_PRINTF 0 +#define CONFIG_DEBUG_ASSERT 0 +#define CONFIG_HAVE_OWN_OFLAGS 1 +#define CONFIG_HAVE_OWN_ERRNO 1 +#define CONFIG_BLOCK_DEV_CACHE_SIZE 16 +#endif + +/*****************************************************************************/ + +#define F_SET_EXT2 2 +#define F_SET_EXT3 3 +#define F_SET_EXT4 4 + +#ifndef CONFIG_EXT_FEATURE_SET_LVL +#define CONFIG_EXT_FEATURE_SET_LVL F_SET_EXT4 +#endif + +/*****************************************************************************/ + +#if CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT2 +/*Superblock features flag EXT2*/ +#define CONFIG_SUPPORTED_FCOM EXT2_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT2_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT2_SUPPORTED_FRO_COM + +#elif CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT3 +/*Superblock features flag EXT3*/ +#define CONFIG_SUPPORTED_FCOM EXT3_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT3_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT3_SUPPORTED_FRO_COM +#elif CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT4 +/*Superblock features flag EXT4*/ +#define CONFIG_SUPPORTED_FCOM EXT4_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT4_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT4_SUPPORTED_FRO_COM +#else +#define "Unsupported CONFIG_EXT_FEATURE_SET_LVL" +#endif + +#define CONFIG_DIR_INDEX_ENABLE (CONFIG_SUPPORTED_FCOM & EXT4_FCOM_DIR_INDEX) +#define CONFIG_EXTENT_ENABLE (CONFIG_SUPPORTED_FINCOM & EXT4_FINCOM_EXTENTS) +#define CONFIG_META_CSUM_ENABLE (CONFIG_SUPPORTED_FRO_COM & EXT4_FRO_COM_METADATA_CSUM) + +/*****************************************************************************/ + +/**@brief Enable/disable journaling*/ +#ifndef CONFIG_JOURNALING_ENABLE +#define CONFIG_JOURNALING_ENABLE 1 +#endif + +/**@brief Enable/disable xattr*/ +#ifndef CONFIG_XATTR_ENABLE +#define CONFIG_XATTR_ENABLE 1 +#endif + +/**@brief Enable/disable extents*/ +#ifndef CONFIG_EXTENTS_ENABLE +#define CONFIG_EXTENTS_ENABLE 1 +#endif + +/**@brief Include error codes from ext4_errno or standard library.*/ +#ifndef CONFIG_HAVE_OWN_ERRNO +#define CONFIG_HAVE_OWN_ERRNO 0 +#endif + +/**@brief Debug printf enable (stdout)*/ +#ifndef CONFIG_DEBUG_PRINTF +#define CONFIG_DEBUG_PRINTF 1 +#endif + +/**@brief Assert printf enable (stdout)*/ +#ifndef CONFIG_DEBUG_ASSERT +#define CONFIG_DEBUG_ASSERT 1 +#endif + +/**@brief Include assert codes from ext4_debug or standard library.*/ +#ifndef CONFIG_HAVE_OWN_ASSERT +#define CONFIG_HAVE_OWN_ASSERT 1 +#endif + +/**@brief Statistics of block device*/ +#ifndef CONFIG_BLOCK_DEV_ENABLE_STATS +#define CONFIG_BLOCK_DEV_ENABLE_STATS 1 +#endif + +/**@brief Cache size of block device.*/ +#ifndef CONFIG_BLOCK_DEV_CACHE_SIZE +#define CONFIG_BLOCK_DEV_CACHE_SIZE 8 +#endif + + +/**@brief Maximum block device name*/ +#ifndef CONFIG_EXT4_MAX_BLOCKDEV_NAME +#define CONFIG_EXT4_MAX_BLOCKDEV_NAME 32 +#endif + + +/**@brief Maximum block device count*/ +#ifndef CONFIG_EXT4_BLOCKDEVS_COUNT +#define CONFIG_EXT4_BLOCKDEVS_COUNT 2 +#endif + +/**@brief Maximum mountpoint name*/ +#ifndef CONFIG_EXT4_MAX_MP_NAME +#define CONFIG_EXT4_MAX_MP_NAME 32 +#endif + +/**@brief Maximum mountpoint count*/ +#ifndef CONFIG_EXT4_MOUNTPOINTS_COUNT +#define CONFIG_EXT4_MOUNTPOINTS_COUNT 2 +#endif + +/**@brief Include open flags from ext4_errno or standard library.*/ +#ifndef CONFIG_HAVE_OWN_OFLAGS +#define CONFIG_HAVE_OWN_OFLAGS 1 +#endif + +/**@brief Maximum single truncate size. Transactions must be limited to reduce + * number of allocetions for single transaction*/ +#ifndef CONFIG_MAX_TRUNCATE_SIZE +#define CONFIG_MAX_TRUNCATE_SIZE (16ul * 1024ul * 1024ul) +#endif + + +/**@brief Unaligned access switch on/off*/ +#ifndef CONFIG_UNALIGNED_ACCESS +#define CONFIG_UNALIGNED_ACCESS 0 +#endif + +/**@brief Switches use of malloc/free functions family + * from standard library to user provided*/ +#ifndef CONFIG_USE_USER_MALLOC +#define CONFIG_USE_USER_MALLOC 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_CONFIG_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_crc32.h b/clib/lib/lwext4/include/ext4_crc32.h new file mode 100644 index 0000000..5f701fb --- /dev/null +++ b/clib/lib/lwext4/include/ext4_crc32.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Based on FreeBSD. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_crc32.h + * @brief Crc32c routine. Taken from FreeBSD kernel. + */ + +#ifndef LWEXT4_EXT4_CRC32C_H_ +#define LWEXT4_EXT4_CRC32C_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +/**@brief CRC32 algorithm. + * @param crc input feed + * @param buf input buffer + * @param size input buffer length (bytes) + * @return updated crc32 value*/ +uint32_t ext4_crc32(uint32_t crc, const void *buf, uint32_t size); + +/**@brief CRC32C algorithm. + * @param crc input feed + * @param buf input buffer + * @param size input buffer length (bytes) + * @return updated crc32c value*/ +uint32_t ext4_crc32c(uint32_t crc, const void *buf, uint32_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* LWEXT4_EXT4_CRC32C_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_debug.h b/clib/lib/lwext4/include/ext4_debug.h new file mode 100644 index 0000000..c558e20 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_debug.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_debug.c + * @brief Debug printf and assert macros. + */ + +#ifndef EXT4_DEBUG_H_ +#define EXT4_DEBUG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if !CONFIG_HAVE_OWN_ASSERT +#include +#endif + +#include +#include + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif + +#ifndef PRId64 +#define PRId64 "lld" +#endif + + +#define DEBUG_BALLOC (1ul << 0) +#define DEBUG_BCACHE (1ul << 1) +#define DEBUG_BITMAP (1ul << 2) +#define DEBUG_BLOCK_GROUP (1ul << 3) +#define DEBUG_BLOCKDEV (1ul << 4) +#define DEBUG_DIR_IDX (1ul << 5) +#define DEBUG_DIR (1ul << 6) +#define DEBUG_EXTENT (1ul << 7) +#define DEBUG_FS (1ul << 8) +#define DEBUG_HASH (1ul << 9) +#define DEBUG_IALLOC (1ul << 10) +#define DEBUG_INODE (1ul << 11) +#define DEBUG_SUPER (1ul << 12) +#define DEBUG_XATTR (1ul << 13) +#define DEBUG_MKFS (1ul << 14) +#define DEBUG_EXT4 (1ul << 15) +#define DEBUG_JBD (1ul << 16) +#define DEBUG_MBR (1ul << 17) + +#define DEBUG_NOPREFIX (1ul << 31) +#define DEBUG_ALL (0xFFFFFFFF) + +static inline const char *ext4_dmask_id2str(uint32_t m) +{ + switch(m) { + case DEBUG_BALLOC: + return "ext4_balloc: "; + case DEBUG_BCACHE: + return "ext4_bcache: "; + case DEBUG_BITMAP: + return "ext4_bitmap: "; + case DEBUG_BLOCK_GROUP: + return "ext4_block_group: "; + case DEBUG_BLOCKDEV: + return "ext4_blockdev: "; + case DEBUG_DIR_IDX: + return "ext4_dir_idx: "; + case DEBUG_DIR: + return "ext4_dir: "; + case DEBUG_EXTENT: + return "ext4_extent: "; + case DEBUG_FS: + return "ext4_fs: "; + case DEBUG_HASH: + return "ext4_hash: "; + case DEBUG_IALLOC: + return "ext4_ialloc: "; + case DEBUG_INODE: + return "ext4_inode: "; + case DEBUG_SUPER: + return "ext4_super: "; + case DEBUG_XATTR: + return "ext4_xattr: "; + case DEBUG_MKFS: + return "ext4_mkfs: "; + case DEBUG_JBD: + return "ext4_jbd: "; + case DEBUG_MBR: + return "ext4_mbr: "; + case DEBUG_EXT4: + return "ext4: "; + } + return ""; +} +#define DBG_NONE "" +#define DBG_INFO "[info] " +#define DBG_WARN "[warn] " +#define DBG_ERROR "[error] " + +/**@brief Global mask debug set. + * @param m new debug mask.*/ +void ext4_dmask_set(uint32_t m); + +/**@brief Global mask debug clear. + * @param m new debug mask.*/ +void ext4_dmask_clr(uint32_t m); + +/**@brief Global debug mask get. + * @return debug mask*/ +uint32_t ext4_dmask_get(void); + +#if CONFIG_DEBUG_PRINTF +#include + +/**@brief Debug printf.*/ +#define ext4_dbg(m, ...) \ + do { \ + if ((m) & ext4_dmask_get()) { \ + if (!((m) & DEBUG_NOPREFIX)) { \ + printf("%s", ext4_dmask_id2str(m)); \ + printf("l: %d ", __LINE__); \ + } \ + printf(__VA_ARGS__); \ + fflush(stdout); \ + } \ + } while (0) +#else +#define ext4_dbg(m, ...) do { } while (0) +#endif + +#if CONFIG_DEBUG_ASSERT +/**@brief Debug assertion.*/ +#if CONFIG_HAVE_OWN_ASSERT +#include + +#define ext4_assert(_v) \ + do { \ + if (!(_v)) { \ + printf("assertion failed:\nfile: %s\nline: %d\n", \ + __FILE__, __LINE__); \ + while (1) \ + ; \ + } \ + } while (0) +#else +#define ext4_assert(_v) assert(_v) +#endif +#else +#define ext4_assert(_v) ((void)(_v)) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DEBUG_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_dir.h b/clib/lib/lwext4/include/ext4_dir.h new file mode 100644 index 0000000..d0d13a2 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_dir.h @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir.h + * @brief Directory handle procedures. + */ + +#ifndef EXT4_DIR_H_ +#define EXT4_DIR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + +#include + +struct ext4_dir_iter { + struct ext4_inode_ref *inode_ref; + struct ext4_block curr_blk; + uint64_t curr_off; + struct ext4_dir_en *curr; +}; + +struct ext4_dir_search_result { + struct ext4_block block; + struct ext4_dir_en *dentry; +}; + + +/**@brief Get i-node number from directory entry. + * @param de Directory entry + * @return I-node number + */ +static inline uint32_t +ext4_dir_en_get_inode(struct ext4_dir_en *de) +{ + return to_le32(de->inode); +} + +/**@brief Set i-node number to directory entry. + * @param de Directory entry + * @param inode I-node number + */ +static inline void +ext4_dir_en_set_inode(struct ext4_dir_en *de, uint32_t inode) +{ + de->inode = to_le32(inode); +} + +/**@brief Set i-node number to directory entry. (For HTree root) + * @param de Directory entry + * @param inode I-node number + */ +static inline void +ext4_dx_dot_en_set_inode(struct ext4_dir_idx_dot_en *de, uint32_t inode) +{ + de->inode = to_le32(inode); +} + +/**@brief Get directory entry length. + * @param de Directory entry + * @return Entry length + */ +static inline uint16_t ext4_dir_en_get_entry_len(struct ext4_dir_en *de) +{ + return to_le16(de->entry_len); +} + +/**@brief Set directory entry length. + * @param de Directory entry + * @param l Entry length + */ +static inline void ext4_dir_en_set_entry_len(struct ext4_dir_en *de, uint16_t l) +{ + de->entry_len = to_le16(l); +} + +/**@brief Get directory entry name length. + * @param sb Superblock + * @param de Directory entry + * @return Entry name length + */ +static inline uint16_t ext4_dir_en_get_name_len(struct ext4_sblock *sb, + struct ext4_dir_en *de) +{ + uint16_t v = de->name_len; + + if ((ext4_get32(sb, rev_level) == 0) && + (ext4_get32(sb, minor_rev_level) < 5)) + v |= ((uint16_t)de->in.name_length_high) << 8; + + return v; +} + +/**@brief Set directory entry name length. + * @param sb Superblock + * @param de Directory entry + * @param len Entry name length + */ +static inline void ext4_dir_en_set_name_len(struct ext4_sblock *sb, + struct ext4_dir_en *de, + uint16_t len) +{ + de->name_len = (len << 8) >> 8; + + if ((ext4_get32(sb, rev_level) == 0) && + (ext4_get32(sb, minor_rev_level) < 5)) + de->in.name_length_high = len >> 8; +} + +/**@brief Get i-node type of directory entry. + * @param sb Superblock + * @param de Directory entry + * @return I-node type (file, dir, etc.) + */ +static inline uint8_t ext4_dir_en_get_inode_type(struct ext4_sblock *sb, + struct ext4_dir_en *de) +{ + if ((ext4_get32(sb, rev_level) > 0) || + (ext4_get32(sb, minor_rev_level) >= 5)) + return de->in.inode_type; + + return EXT4_DE_UNKNOWN; +} +/**@brief Set i-node type of directory entry. + * @param sb Superblock + * @param de Directory entry + * @param t I-node type (file, dir, etc.) + */ + +static inline void ext4_dir_en_set_inode_type(struct ext4_sblock *sb, + struct ext4_dir_en *de, uint8_t t) +{ + if ((ext4_get32(sb, rev_level) > 0) || + (ext4_get32(sb, minor_rev_level) >= 5)) + de->in.inode_type = t; +} + +/**@brief Verify checksum of a linear directory leaf block + * @param inode_ref Directory i-node + * @param dirent Linear directory leaf block + * @return true means the block passed checksum verification + */ +bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent); + +/**@brief Initialize directory iterator. + * Set position to the first valid entry from the required position. + * @param it Pointer to iterator to be initialized + * @param inode_ref Directory i-node + * @param pos Position to start reading entries from + * @return Error code + */ +int ext4_dir_iterator_init(struct ext4_dir_iter *it, + struct ext4_inode_ref *inode_ref, uint64_t pos); + +/**@brief Jump to the next valid entry + * @param it Initialized iterator + * @return Error code + */ +int ext4_dir_iterator_next(struct ext4_dir_iter *it); + +/**@brief Uninitialize directory iterator. + * Release all allocated structures. + * @param it Iterator to be finished + * @return Error code + */ +int ext4_dir_iterator_fini(struct ext4_dir_iter *it); + +/**@brief Write directory entry to concrete data block. + * @param sb Superblock + * @param en Pointer to entry to be written + * @param entry_len Length of new entry + * @param child Child i-node to be written to new entry + * @param name Name of the new entry + * @param name_len Length of entry name + */ +void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, + uint16_t entry_len, struct ext4_inode_ref *child, + const char *name, size_t name_len); + +/**@brief Add new entry to the directory. + * @param parent Directory i-node + * @param name Name of new entry + * @param child I-node to be referenced from new entry + * @return Error code + */ +int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len, struct ext4_inode_ref *child); + +/**@brief Find directory entry with passed name. + * @param result Result structure to be returned if entry found + * @param parent Directory i-node + * @param name Name of entry to be found + * @param name_len Name length + * @return Error code + */ +int ext4_dir_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *parent, const char *name, + uint32_t name_len); + +/**@brief Remove directory entry. + * @param parent Directory i-node + * @param name Name of the entry to be removed + * @param name_len Name length + * @return Error code + */ +int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len); + +/**@brief Try to insert entry to concrete data block. + * @param sb Superblock + * @param inode_ref Directory i-node + * @param dst_blk Block to try to insert entry to + * @param child Child i-node to be inserted by new entry + * @param name Name of the new entry + * @param name_len Length of the new entry name + * @return Error code + */ +int ext4_dir_try_insert_entry(struct ext4_sblock *sb, + struct ext4_inode_ref *inode_ref, + struct ext4_block *dst_blk, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len); + +/**@brief Try to find entry in block by name. + * @param block Block containing entries + * @param sb Superblock + * @param name_len Length of entry name + * @param name Name of entry to be found + * @param res_entry Output pointer to found entry, NULL if not found + * @return Error code + */ +int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, + size_t name_len, const char *name, + struct ext4_dir_en **res_entry); + +/**@brief Simple function to release allocated data from result. + * @param parent Parent inode + * @param result Search result to destroy + * @return Error code + * + */ +int ext4_dir_destroy_result(struct ext4_inode_ref *parent, + struct ext4_dir_search_result *result); + +void ext4_dir_set_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent); + + +void ext4_dir_init_entry_tail(struct ext4_dir_entry_tail *t); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DIR_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_dir_idx.h b/clib/lib/lwext4/include/ext4_dir_idx.h new file mode 100644 index 0000000..e4a7abe --- /dev/null +++ b/clib/lib/lwext4/include/ext4_dir_idx.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir_idx.h + * @brief Directory indexing procedures. + */ + +#ifndef EXT4_DIR_IDX_H_ +#define EXT4_DIR_IDX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +#include +#include + +struct ext4_dir_idx_block { + struct ext4_block b; + struct ext4_dir_idx_entry *entries; + struct ext4_dir_idx_entry *position; +}; + +#define EXT4_DIR_DX_INIT_BCNT 2 + + +/**@brief Initialize index structure of new directory. + * @param dir Pointer to directory i-node + * @param parent Pointer to parent directory i-node + * @return Error code + */ +int ext4_dir_dx_init(struct ext4_inode_ref *dir, + struct ext4_inode_ref *parent); + +/**@brief Try to find directory entry using directory index. + * @param result Output value - if entry will be found, + * than will be passed through this parameter + * @param inode_ref Directory i-node + * @param name_len Length of name to be found + * @param name Name to be found + * @return Error code + */ +int ext4_dir_dx_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *inode_ref, size_t name_len, + const char *name); + +/**@brief Add new entry to indexed directory + * @param parent Directory i-node + * @param child I-node to be referenced from directory entry + * @param name Name of new directory entry + * @return Error code + */ +int ext4_dir_dx_add_entry(struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, uint32_t name_len); + +/**@brief Add new entry to indexed directory + * @param dir Directory i-node + * @param parent_inode parent inode index + * @return Error code + */ +int ext4_dir_dx_reset_parent_inode(struct ext4_inode_ref *dir, + uint32_t parent_inode); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DIR_IDX_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_errno.h b/clib/lib/lwext4/include/ext4_errno.h new file mode 100644 index 0000000..2d92280 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_errno.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_errno.h + * @brief Error codes. + */ +#ifndef EXT4_ERRNO_H_ +#define EXT4_ERRNO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if !CONFIG_HAVE_OWN_ERRNO +#include +#else +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define EEXIST 17 /* File exists */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define ERANGE 34 /* Math result not representable */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ENODATA 61 /* No data available */ +#define ENOTSUP 95 /* Not supported */ +#endif + +#ifndef ENODATA + #ifdef ENOATTR + #define ENODATA ENOATTR + #else + #define ENODATA 61 + #endif +#endif + +#ifndef ENOTSUP +#define ENOTSUP 95 +#endif + +#ifndef EOK +#define EOK 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_ERRNO_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_extent.h b/clib/lib/lwext4/include/ext4_extent.h new file mode 100644 index 0000000..fee0926 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_extent.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_extent.h + * @brief More complex filesystem functions. + */ +#ifndef EXT4_EXTENT_H_ +#define EXT4_EXTENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +void ext4_extent_tree_init(struct ext4_inode_ref *inode_ref); + + +int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_lblk_t iblock, + uint32_t max_blocks, ext4_fsblk_t *result, bool create, + uint32_t *blocks_count); + + +/**@brief Release all data blocks starting from specified logical block. + * @param inode_ref I-node to release blocks from + * @param iblock_from First logical block to release + * @return Error code */ +int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from, + ext4_lblk_t to); + + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_EXTENT_H_ */ +/** +* @} +*/ diff --git a/clib/lib/lwext4/include/ext4_fs.h b/clib/lib/lwext4/include/ext4_fs.h new file mode 100644 index 0000000..b76be6c --- /dev/null +++ b/clib/lib/lwext4/include/ext4_fs.h @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_fs.c + * @brief More complex filesystem functions. + */ + +#ifndef EXT4_FS_H_ +#define EXT4_FS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +struct ext4_fs { + bool read_only; + + struct ext4_blockdev *bdev; + struct ext4_sblock sb; + + uint64_t inode_block_limits[4]; + uint64_t inode_blocks_per_level[4]; + + uint32_t last_inode_bg_id; + + struct jbd_fs *jbd_fs; + struct jbd_journal *jbd_journal; + struct jbd_trans *curr_trans; +}; + +struct ext4_block_group_ref { + struct ext4_block block; + struct ext4_bgroup *block_group; + struct ext4_fs *fs; + uint32_t index; + bool dirty; +}; + +struct ext4_inode_ref { + struct ext4_block block; + struct ext4_inode *inode; + struct ext4_fs *fs; + uint32_t index; + bool dirty; +}; + + +/**@brief Convert block address to relative index in block group. + * @param s Superblock pointer + * @param baddr Block number to convert + * @return Relative number of block + */ +static inline uint32_t ext4_fs_addr_to_idx_bg(struct ext4_sblock *s, + ext4_fsblk_t baddr) +{ + if (ext4_get32(s, first_data_block) && baddr) + baddr--; + + return baddr % ext4_get32(s, blocks_per_group); +} + +/**@brief Convert relative block address in group to absolute address. + * @param s Superblock pointer + * @param index Relative block address + * @param bgid Block group + * @return Absolute block address + */ +static inline ext4_fsblk_t ext4_fs_bg_idx_to_addr(struct ext4_sblock *s, + uint32_t index, + uint32_t bgid) +{ + if (ext4_get32(s, first_data_block)) + index++; + + return ext4_get32(s, blocks_per_group) * bgid + index; +} + +/**@brief TODO: */ +static inline ext4_fsblk_t ext4_fs_first_bg_block_no(struct ext4_sblock *s, + uint32_t bgid) +{ + return (uint64_t)bgid * ext4_get32(s, blocks_per_group) + + ext4_get32(s, first_data_block); +} + +/**@brief Initialize filesystem and read all needed data. + * @param fs Filesystem instance to be initialized + * @param bdev Identifier if device with the filesystem + * @param read_only Mark the filesystem as read-only. + * @return Error code + */ +int ext4_fs_init(struct ext4_fs *fs, struct ext4_blockdev *bdev, + bool read_only); + +/**@brief Destroy filesystem instance (used by unmount operation). + * @param fs Filesystem to be destroyed + * @return Error code + */ +int ext4_fs_fini(struct ext4_fs *fs); + +/**@brief Check filesystem's features, if supported by this driver + * Function can return EOK and set read_only flag. It mean's that + * there are some not-supported features, that can cause problems + * during some write operations. + * @param fs Filesystem to be checked + * @param read_only Flag if filesystem should be mounted only for reading + * @return Error code + */ +int ext4_fs_check_features(struct ext4_fs *fs, bool *read_only); + +/**@brief Get reference to block group specified by index. + * @param fs Filesystem to find block group on + * @param bgid Index of block group to load + * @param ref Output pointer for reference + * @return Error code + */ +int ext4_fs_get_block_group_ref(struct ext4_fs *fs, uint32_t bgid, + struct ext4_block_group_ref *ref); + +/**@brief Put reference to block group. + * @param ref Pointer for reference to be put back + * @return Error code + */ +int ext4_fs_put_block_group_ref(struct ext4_block_group_ref *ref); + +/**@brief Get reference to i-node specified by index. + * @param fs Filesystem to find i-node on + * @param index Index of i-node to load + * @param ref Output pointer for reference + * @return Error code + */ +int ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref); + +/**@brief Reset blocks field of i-node. + * @param fs Filesystem to reset blocks field of i-inode on + * @param inode_ref ref Pointer for inode to be operated on + */ +void ext4_fs_inode_blocks_init(struct ext4_fs *fs, + struct ext4_inode_ref *inode_ref); + +/**@brief Put reference to i-node. + * @param ref Pointer for reference to be put back + * @return Error code + */ +int ext4_fs_put_inode_ref(struct ext4_inode_ref *ref); + +/**@brief Convert filetype to inode mode. + * @param filetype File type + * @return inode mode + */ +uint32_t ext4_fs_correspond_inode_mode(int filetype); + +/**@brief Allocate new i-node in the filesystem. + * @param fs Filesystem to allocated i-node on + * @param inode_ref Output pointer to return reference to allocated i-node + * @param filetype File type of newly created i-node + * @return Error code + */ +int ext4_fs_alloc_inode(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + int filetype); + +/**@brief Release i-node and mark it as free. + * @param inode_ref I-node to be released + * @return Error code + */ +int ext4_fs_free_inode(struct ext4_inode_ref *inode_ref); + +/**@brief Truncate i-node data blocks. + * @param inode_ref I-node to be truncated + * @param new_size New size of inode (must be < current size) + * @return Error code + */ +int ext4_fs_truncate_inode(struct ext4_inode_ref *inode_ref, uint64_t new_size); + +/**@brief Compute 'goal' for inode index + * @param inode_ref Reference to inode, to allocate block for + * @return goal + */ +ext4_fsblk_t ext4_fs_inode_to_goal_block(struct ext4_inode_ref *inode_ref); + +/**@brief Compute 'goal' for allocation algorithm (For blockmap). + * @param inode_ref Reference to inode, to allocate block for + * @return error code + */ +int ext4_fs_indirect_find_goal(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *goal); + +/**@brief Get physical block address by logical index of the block. + * @param inode_ref I-node to read block address from + * @param iblock Logical index of block + * @param fblock Output pointer for return physical + * block address + * @param support_unwritten Indicate whether unwritten block range + * is supported under the current context + * @return Error code + */ +int ext4_fs_get_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool support_unwritten); + +/**@brief Initialize a part of unwritten range of the inode. + * @param inode_ref I-node to proceed on. + * @param iblock Logical index of block + * @param fblock Output pointer for return physical block address + * @return Error code + */ +int ext4_fs_init_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock); + +/**@brief Append following logical block to the i-node. + * @param inode_ref I-node to append block to + * @param fblock Output physical block address of newly allocated block + * @param iblock Output logical number of newly allocated block + * @return Error code + */ +int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *fblock, ext4_lblk_t *iblock); + +/**@brief Increment inode link count. + * @param inode_ref none handle + */ +void ext4_fs_inode_links_count_inc(struct ext4_inode_ref *inode_ref); + +/**@brief Decrement inode link count. + * @param inode_ref none handle + */ +void ext4_fs_inode_links_count_dec(struct ext4_inode_ref *inode_ref); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_FS_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_hash.h b/clib/lib/lwext4/include/ext4_hash.h new file mode 100644 index 0000000..15c2b94 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_hash.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_hash.h + * @brief Directory indexing hash functions. + */ + +#ifndef EXT4_HASH_H_ +#define EXT4_HASH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +struct ext4_hash_info { + uint32_t hash; + uint32_t minor_hash; + uint32_t hash_version; + const uint32_t *seed; +}; + + +/**@brief Directory entry name hash function. + * @param name entry name + * @param len entry name length + * @param hash_seed (from superblock) + * @param hash_version version (from superblock) + * @param hash_minor output value + * @param hash_major output value + * @return standard error code*/ +int ext2_htree_hash(const char *name, int len, const uint32_t *hash_seed, + int hash_version, uint32_t *hash_major, + uint32_t *hash_minor); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_HASH_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_ialloc.h b/clib/lib/lwext4/include/ext4_ialloc.h new file mode 100644 index 0000000..e845c79 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_ialloc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_ialloc.c + * @brief Inode allocation procedures. + */ + +#ifndef EXT4_IALLOC_H_ +#define EXT4_IALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/**@brief Calculate and set checksum of inode bitmap. + * @param sb superblock pointer. + * @param bg block group + * @param bitmap bitmap buffer + */ +void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap); + +/**@brief Free i-node number and modify filesystem data structers. + * @param fs Filesystem, where the i-node is located + * @param index Index of i-node to be release + * @param is_dir Flag us for information whether i-node is directory or not + */ +int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir); + +/**@brief I-node allocation algorithm. + * This is more simple algorithm, than Orlov allocator used + * in the Linux kernel. + * @param fs Filesystem to allocate i-node on + * @param index Output value - allocated i-node number + * @param is_dir Flag if allocated i-node will be file or directory + * @return Error code + */ +int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *index, bool is_dir); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_IALLOC_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_inode.h b/clib/lib/lwext4/include/ext4_inode.h new file mode 100644 index 0000000..11fd1d9 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_inode.h @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_inode.h + * @brief Inode handle functions + */ + +#ifndef EXT4_INODE_H_ +#define EXT4_INODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +/**@brief Get mode of the i-node. + * @param sb Superblock + * @param inode I-node to load mode from + * @return Mode of the i-node + */ +uint32_t ext4_inode_get_mode(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Set mode of the i-node. + * @param sb Superblock + * @param inode I-node to set mode to + * @param mode Mode to set to i-node + */ +void ext4_inode_set_mode(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t mode); + +/**@brief Get ID of the i-node owner (user id). + * @param inode I-node to load uid from + * @return User ID of the i-node owner + */ +uint32_t ext4_inode_get_uid(struct ext4_inode *inode); + +/**@brief Set ID of the i-node owner. + * @param inode I-node to set uid to + * @param uid ID of the i-node owner + */ +void ext4_inode_set_uid(struct ext4_inode *inode, uint32_t uid); + +/**@brief Get real i-node size. + * @param sb Superblock + * @param inode I-node to load size from + * @return Real size of i-node + */ +uint64_t ext4_inode_get_size(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Set real i-node size. + * @param inode I-node to set size to + * @param size Size of the i-node + */ +void ext4_inode_set_size(struct ext4_inode *inode, uint64_t size); + +/**@brief Get time, when i-node was last accessed. + * @param inode I-node + * @return Time of the last access (POSIX) + */ +uint32_t ext4_inode_get_access_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was last accessed. + * @param inode I-node + * @param time Time of the last access (POSIX) + */ +void ext4_inode_set_access_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node was last changed. + * @param inode I-node + * @return Time of the last change (POSIX) + */ +uint32_t ext4_inode_get_change_inode_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was last changed. + * @param inode I-node + * @param time Time of the last change (POSIX) + */ +void ext4_inode_set_change_inode_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node content was last modified. + * @param inode I-node + * @return Time of the last content modification (POSIX) + */ +uint32_t ext4_inode_get_modif_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node content was last modified. + * @param inode I-node + * @param time Time of the last content modification (POSIX) + */ +void ext4_inode_set_modif_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node was deleted. + * @param inode I-node + * @return Time of the delete action (POSIX) + */ +uint32_t ext4_inode_get_del_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was deleted. + * @param inode I-node + * @param time Time of the delete action (POSIX) + */ +void ext4_inode_set_del_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get ID of the i-node owner's group. + * @param inode I-node to load gid from + * @return Group ID of the i-node owner + */ +uint32_t ext4_inode_get_gid(struct ext4_inode *inode); + +/**@brief Set ID to the i-node owner's group. + * @param inode I-node to set gid to + * @param gid Group ID of the i-node owner + */ +void ext4_inode_set_gid(struct ext4_inode *inode, uint32_t gid); + +/**@brief Get number of links to i-node. + * @param inode I-node to load number of links from + * @return Number of links to i-node + */ +uint16_t ext4_inode_get_links_cnt(struct ext4_inode *inode); + +/**@brief Set number of links to i-node. + * @param inode I-node to set number of links to + * @param cnt Number of links to i-node + */ +void ext4_inode_set_links_cnt(struct ext4_inode *inode, uint16_t cnt); + +/**@brief Get number of 512-bytes blocks used for i-node. + * @param sb Superblock + * @param inode I-node + * @return Number of 512-bytes blocks + */ +uint64_t ext4_inode_get_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode); + +/**@brief Set number of 512-bytes blocks used for i-node. + * @param sb Superblock + * @param inode I-node + * @param cnt Number of 512-bytes blocks + * @return Error code + */ +int ext4_inode_set_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode, uint64_t cnt); + +/**@brief Get flags (features) of i-node. + * @param inode I-node to get flags from + * @return Flags (bitmap) + */ +uint32_t ext4_inode_get_flags(struct ext4_inode *inode); + +/**@brief Set flags (features) of i-node. + * @param inode I-node to set flags to + * @param flags Flags to set to i-node + */ +void ext4_inode_set_flags(struct ext4_inode *inode, uint32_t flags); + +/**@brief Get file generation (used by NFS). + * @param inode I-node + * @return File generation + */ +uint32_t ext4_inode_get_generation(struct ext4_inode *inode); + +/**@brief Set file generation (used by NFS). + * @param inode I-node + * @param gen File generation + */ +void ext4_inode_set_generation(struct ext4_inode *inode, uint32_t gen); + +/**@brief Get extra I-node size field. + * @param sb Superblock + * @param inode I-node + * @return extra I-node size + */ +uint16_t ext4_inode_get_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode); + +/**@brief Set extra I-node size field. + * @param sb Superblock + * @param inode I-node + * @param size extra I-node size + */ +void ext4_inode_set_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode, + uint16_t size); + +/**@brief Get address of block, where are extended attributes located. + * @param inode I-node + * @param sb Superblock + * @return Block address + */ +uint64_t ext4_inode_get_file_acl(struct ext4_inode *inode, + struct ext4_sblock *sb); + +/**@brief Set address of block, where are extended attributes located. + * @param inode I-node + * @param sb Superblock + * @param acl Block address + */ +void ext4_inode_set_file_acl(struct ext4_inode *inode, struct ext4_sblock *sb, + uint64_t acl); + +/**@brief Get block address of specified direct block. + * @param inode I-node to load block from + * @param idx Index of logical block + * @return Physical block address + */ +uint32_t ext4_inode_get_direct_block(struct ext4_inode *inode, uint32_t idx); + +/**@brief Set block address of specified direct block. + * @param inode I-node to set block address to + * @param idx Index of logical block + * @param block Physical block address + */ +void ext4_inode_set_direct_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block); + +/**@brief Get block address of specified indirect block. + * @param inode I-node to get block address from + * @param idx Index of indirect block + * @return Physical block address + */ +uint32_t ext4_inode_get_indirect_block(struct ext4_inode *inode, uint32_t idx); + +/**@brief Set block address of specified indirect block. + * @param inode I-node to set block address to + * @param idx Index of indirect block + * @param block Physical block address + */ +void ext4_inode_set_indirect_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block); + +/**@brief Get device number + * @param inode I-node to get device number from + * @return Device number + */ +uint32_t ext4_inode_get_dev(struct ext4_inode *inode); + +/**@brief Set device number + * @param inode I-node to set device number to + * @param dev Device number + */ +void ext4_inode_set_dev(struct ext4_inode *inode, uint32_t dev); + +/**@brief return the type of i-node + * @param sb Superblock + * @param inode I-node to return the type of + * @return Result of check operation + */ +uint32_t ext4_inode_type(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Check if i-node has specified type. + * @param sb Superblock + * @param inode I-node to check type of + * @param type Type to check + * @return Result of check operation + */ +bool ext4_inode_is_type(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t type); + +/**@brief Check if i-node has specified flag. + * @param inode I-node to check flags of + * @param f Flag to check + * @return Result of check operation + */ +bool ext4_inode_has_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Remove specified flag from i-node. + * @param inode I-node to clear flag on + * @param f Flag to be cleared + */ +void ext4_inode_clear_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Set specified flag to i-node. + * @param inode I-node to set flag on + * @param f Flag to be set + */ +void ext4_inode_set_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Get inode checksum(crc32) + * @param sb Superblock + * @param inode I-node to get checksum value from + */ +uint32_t +ext4_inode_get_csum(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Get inode checksum(crc32) + * @param sb Superblock + * @param inode I-node to get checksum value from + */ +void +ext4_inode_set_csum(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t checksum); + +/**@brief Check if i-node can be truncated. + * @param sb Superblock + * @param inode I-node to check + * @return Result of the check operation + */ +bool ext4_inode_can_truncate(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Get extent header from the root of the extent tree. + * @param inode I-node to get extent header from + * @return Pointer to extent header of the root node + */ +struct ext4_extent_header * +ext4_inode_get_extent_header(struct ext4_inode *inode); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_INODE_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_journal.h b/clib/lib/lwext4/include/ext4_journal.h new file mode 100644 index 0000000..415618b --- /dev/null +++ b/clib/lib/lwext4/include/ext4_journal.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_journal.h + * @brief Journal handle functions + */ + +#ifndef EXT4_JOURNAL_H_ +#define EXT4_JOURNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +struct jbd_fs { + struct ext4_blockdev *bdev; + struct ext4_inode_ref inode_ref; + struct jbd_sb sb; + + bool dirty; +}; + +struct jbd_buf { + uint32_t jbd_lba; + struct ext4_block block; + struct jbd_trans *trans; + struct jbd_block_rec *block_rec; + TAILQ_ENTRY(jbd_buf) buf_node; + TAILQ_ENTRY(jbd_buf) dirty_buf_node; +}; + +struct jbd_revoke_rec { + ext4_fsblk_t lba; + RB_ENTRY(jbd_revoke_rec) revoke_node; +}; + +struct jbd_block_rec { + ext4_fsblk_t lba; + struct jbd_trans *trans; + RB_ENTRY(jbd_block_rec) block_rec_node; + LIST_ENTRY(jbd_block_rec) tbrec_node; + TAILQ_HEAD(jbd_buf_dirty, jbd_buf) dirty_buf_queue; +}; + +struct jbd_trans { + uint32_t trans_id; + + uint32_t start_iblock; + int alloc_blocks; + int data_cnt; + uint32_t data_csum; + int written_cnt; + int error; + + struct jbd_journal *journal; + + TAILQ_HEAD(jbd_trans_buf, jbd_buf) buf_queue; + RB_HEAD(jbd_revoke_tree, jbd_revoke_rec) revoke_root; + LIST_HEAD(jbd_trans_block_rec, jbd_block_rec) tbrec_list; + TAILQ_ENTRY(jbd_trans) trans_node; +}; + +struct jbd_journal { + uint32_t first; + uint32_t start; + uint32_t last; + uint32_t trans_id; + uint32_t alloc_trans_id; + + uint32_t block_size; + + TAILQ_HEAD(jbd_cp_queue, jbd_trans) cp_queue; + RB_HEAD(jbd_block, jbd_block_rec) block_rec_root; + + struct jbd_fs *jbd_fs; +}; + +int jbd_get_fs(struct ext4_fs *fs, + struct jbd_fs *jbd_fs); +int jbd_put_fs(struct jbd_fs *jbd_fs); +int jbd_inode_bmap(struct jbd_fs *jbd_fs, + ext4_lblk_t iblock, + ext4_fsblk_t *fblock); +int jbd_recover(struct jbd_fs *jbd_fs); +int jbd_journal_start(struct jbd_fs *jbd_fs, + struct jbd_journal *journal); +int jbd_journal_stop(struct jbd_journal *journal); +struct jbd_trans * +jbd_journal_new_trans(struct jbd_journal *journal); +int jbd_trans_set_block_dirty(struct jbd_trans *trans, + struct ext4_block *block); +int jbd_trans_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba); +int jbd_trans_try_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba); +void jbd_journal_free_trans(struct jbd_journal *journal, + struct jbd_trans *trans, + bool abort); +int jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans); +void +jbd_journal_purge_cp_trans(struct jbd_journal *journal, + bool flush, + bool once); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_JOURNAL_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_mbr.h b/clib/lib/lwext4/include/ext4_mbr.h new file mode 100644 index 0000000..97a4459 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_mbr.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mbr.h + * @brief Master boot record parser + */ + +#ifndef EXT4_MBR_H_ +#define EXT4_MBR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/**@brief Master boot record block devices descriptor*/ +struct ext4_mbr_bdevs { + struct ext4_blockdev partitions[4]; +}; + +int ext4_mbr_scan(struct ext4_blockdev *parent, struct ext4_mbr_bdevs *bdevs); + +/**@brief Master boot record partitions*/ +struct ext4_mbr_parts { + + /**@brief Percentage division tab: + * - {50, 20, 10, 20} + * Sum of all 4 elements must be <= 100*/ + uint8_t division[4]; +}; + +int ext4_mbr_write(struct ext4_blockdev *parent, struct ext4_mbr_parts *parts, uint32_t disk_id); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MBR_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_misc.h b/clib/lib/lwext4/include/ext4_misc.h new file mode 100644 index 0000000..3067d4d --- /dev/null +++ b/clib/lib/lwext4/include/ext4_misc.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_misc.h + * @brief Miscellaneous helpers. + */ + +#ifndef EXT4_MISC_H_ +#define EXT4_MISC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/**************************************************************/ + +#define EXT4_DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y)) +#define EXT4_ALIGN(x, y) ((y) * EXT4_DIV_ROUND_UP((x), (y))) + +/****************************Endian conversion*****************/ + +static inline uint64_t reorder64(uint64_t n) +{ + return ((n & 0xff) << 56) | + ((n & 0xff00) << 40) | + ((n & 0xff0000) << 24) | + ((n & 0xff000000LL) << 8) | + ((n & 0xff00000000LL) >> 8) | + ((n & 0xff0000000000LL) >> 24) | + ((n & 0xff000000000000LL) >> 40) | + ((n & 0xff00000000000000LL) >> 56); +} + +static inline uint32_t reorder32(uint32_t n) +{ + return ((n & 0xff) << 24) | + ((n & 0xff00) << 8) | + ((n & 0xff0000) >> 8) | + ((n & 0xff000000) >> 24); +} + +static inline uint16_t reorder16(uint16_t n) +{ + return ((n & 0xff) << 8) | + ((n & 0xff00) >> 8); +} + +#ifdef CONFIG_BIG_ENDIAN +#define to_le64(_n) reorder64(_n) +#define to_le32(_n) reorder32(_n) +#define to_le16(_n) reorder16(_n) + +#define to_be64(_n) _n +#define to_be32(_n) _n +#define to_be16(_n) _n + +#else +#define to_le64(_n) _n +#define to_le32(_n) _n +#define to_le16(_n) _n + +#define to_be64(_n) reorder64(_n) +#define to_be32(_n) reorder32(_n) +#define to_be16(_n) reorder16(_n) +#endif + +/****************************Access macros to ext4 structures*****************/ + +#define ext4_get32(s, f) to_le32((s)->f) +#define ext4_get16(s, f) to_le16((s)->f) +#define ext4_get8(s, f) (s)->f + +#define ext4_set32(s, f, v) \ + do { \ + (s)->f = to_le32(v); \ + } while (0) +#define ext4_set16(s, f, v) \ + do { \ + (s)->f = to_le16(v); \ + } while (0) +#define ext4_set8 \ + (s, f, v) do { (s)->f = (v); } \ + while (0) + +/****************************Access macros to jbd2 structures*****************/ + +#define jbd_get32(s, f) to_be32((s)->f) +#define jbd_get16(s, f) to_be16((s)->f) +#define jbd_get8(s, f) (s)->f + +#define jbd_set32(s, f, v) \ + do { \ + (s)->f = to_be32(v); \ + } while (0) +#define jbd_set16(s, f, v) \ + do { \ + (s)->f = to_be16(v); \ + } while (0) +#define jbd_set8 \ + (s, f, v) do { (s)->f = (v); } \ + while (0) + +#ifdef __GNUC__ + #ifndef __unused + #define __unused __attribute__ ((__unused__)) + #endif +#else + #define __unused +#endif + +#ifndef offsetof +#define offsetof(type, field) \ + ((size_t)(&(((type *)0)->field))) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MISC_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_mkfs.h b/clib/lib/lwext4/include/ext4_mkfs.h new file mode 100644 index 0000000..aadedb0 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_mkfs.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mkfs.h + * @brief + */ + +#ifndef EXT4_MKFS_H_ +#define EXT4_MKFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +#include +#include + +struct ext4_mkfs_info { + uint64_t len; + uint32_t block_size; + uint32_t blocks_per_group; + uint32_t inodes_per_group; + uint32_t inode_size; + uint32_t inodes; + uint32_t journal_blocks; + uint32_t feat_ro_compat; + uint32_t feat_compat; + uint32_t feat_incompat; + uint32_t bg_desc_reserve_blocks; + uint16_t dsc_size; + uint8_t uuid[UUID_SIZE]; + bool journal; + const char *label; +}; + + +int ext4_mkfs_read_info(struct ext4_blockdev *bd, struct ext4_mkfs_info *info); + +int ext4_mkfs(struct ext4_fs *fs, struct ext4_blockdev *bd, + struct ext4_mkfs_info *info, int fs_type); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MKFS_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_oflags.h b/clib/lib/lwext4/include/ext4_oflags.h new file mode 100644 index 0000000..7f7be7e --- /dev/null +++ b/clib/lib/lwext4/include/ext4_oflags.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_oflags.h + * @brief File opening & seeking flags. + */ +#ifndef EXT4_OFLAGS_H_ +#define EXT4_OFLAGS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/********************************FILE OPEN FLAGS*****************************/ + +#if CONFIG_HAVE_OWN_OFLAGS + + #ifndef O_RDONLY + #define O_RDONLY 00 + #endif + + #ifndef O_WRONLY + #define O_WRONLY 01 + #endif + + #ifndef O_RDWR + #define O_RDWR 02 + #endif + + #ifndef O_CREAT + #define O_CREAT 0100 + #endif + + #ifndef O_EXCL + #define O_EXCL 0200 + #endif + + #ifndef O_TRUNC + #define O_TRUNC 01000 + #endif + + #ifndef O_APPEND + #define O_APPEND 02000 + #endif + +/********************************FILE SEEK FLAGS*****************************/ + + #ifndef SEEK_SET + #define SEEK_SET 0 + #endif + + #ifndef SEEK_CUR + #define SEEK_CUR 1 + #endif + + #ifndef SEEK_END + #define SEEK_END 2 + #endif + +#else + #include + #include +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_OFLAGS_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_super.h b/clib/lib/lwext4/include/ext4_super.h new file mode 100644 index 0000000..1b563da --- /dev/null +++ b/clib/lib/lwext4/include/ext4_super.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_super.c + * @brief Superblock operations. + */ + +#ifndef EXT4_SUPER_H_ +#define EXT4_SUPER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/**@brief Blocks count get stored in superblock. + * @param s superblock descriptor + * @return count of blocks*/ +static inline uint64_t ext4_sb_get_blocks_cnt(struct ext4_sblock *s) +{ + return ((uint64_t)to_le32(s->blocks_count_hi) << 32) | + to_le32(s->blocks_count_lo); +} + +/**@brief Blocks count set in superblock. + * @param s superblock descriptor + * @param cnt count of blocks*/ +static inline void ext4_sb_set_blocks_cnt(struct ext4_sblock *s, uint64_t cnt) +{ + s->blocks_count_lo = to_le32((cnt << 32) >> 32); + s->blocks_count_hi = to_le32(cnt >> 32); +} + +/**@brief Free blocks count get stored in superblock. + * @param s superblock descriptor + * @return free blocks*/ +static inline uint64_t ext4_sb_get_free_blocks_cnt(struct ext4_sblock *s) +{ + return ((uint64_t)to_le32(s->free_blocks_count_hi) << 32) | + to_le32(s->free_blocks_count_lo); +} + +/**@brief Free blocks count set. + * @param s superblock descriptor + * @param cnt new value of free blocks*/ +static inline void ext4_sb_set_free_blocks_cnt(struct ext4_sblock *s, + uint64_t cnt) +{ + s->free_blocks_count_lo = to_le32((cnt << 32) >> 32); + s->free_blocks_count_hi = to_le32(cnt >> 32); +} + +/**@brief Block size get from superblock. + * @param s superblock descriptor + * @return block size in bytes*/ +static inline uint32_t ext4_sb_get_block_size(struct ext4_sblock *s) +{ + return 1024 << to_le32(s->log_block_size); +} + +/**@brief Block group descriptor size. + * @param s superblock descriptor + * @return block group descriptor size in bytes*/ +static inline uint16_t ext4_sb_get_desc_size(struct ext4_sblock *s) +{ + uint16_t size = to_le16(s->desc_size); + + return size < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + ? EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + : size; +} + +/*************************Flags and features*********************************/ + +/**@brief Support check of flag. + * @param s superblock descriptor + * @param v flag to check + * @return true if flag is supported*/ +static inline bool ext4_sb_check_flag(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->flags) & v; +} + +/**@brief Support check of feature compatible. + * @param s superblock descriptor + * @param v feature to check + * @return true if feature is supported*/ +static inline bool ext4_sb_feature_com(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_compatible) & v; +} + +/**@brief Support check of feature incompatible. + * @param s superblock descriptor + * @param v feature to check + * @return true if feature is supported*/ +static inline bool ext4_sb_feature_incom(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_incompatible) & v; +} + +/**@brief Support check of read only flag. + * @param s superblock descriptor + * @param v flag to check + * @return true if flag is supported*/ +static inline bool ext4_sb_feature_ro_com(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_read_only) & v; +} + +/**@brief Block group to flex group. + * @param s superblock descriptor + * @param block_group block group + * @return flex group id*/ +static inline uint32_t ext4_sb_bg_to_flex(struct ext4_sblock *s, + uint32_t block_group) +{ + return block_group >> to_le32(s->log_groups_per_flex); +} + +/**@brief Flex block group size. + * @param s superblock descriptor + * @return flex bg size*/ +static inline uint32_t ext4_sb_flex_bg_size(struct ext4_sblock *s) +{ + return 1 << to_le32(s->log_groups_per_flex); +} + +/**@brief Return first meta block group id. + * @param s superblock descriptor + * @return first meta_bg id */ +static inline uint32_t ext4_sb_first_meta_bg(struct ext4_sblock *s) +{ + return to_le32(s->first_meta_bg); +} + +/**************************More complex functions****************************/ + +/**@brief Returns a block group count. + * @param s superblock descriptor + * @return count of block groups*/ +uint32_t ext4_block_group_cnt(struct ext4_sblock *s); + +/**@brief Returns block count in block group + * (last block group may have less blocks) + * @param s superblock descriptor + * @param bgid block group id + * @return blocks count*/ +uint32_t ext4_blocks_in_group_cnt(struct ext4_sblock *s, uint32_t bgid); + +/**@brief Returns inodes count in block group + * (last block group may have less inodes) + * @param s superblock descriptor + * @param bgid block group id + * @return inodes count*/ +uint32_t ext4_inodes_in_group_cnt(struct ext4_sblock *s, uint32_t bgid); + +/***************************Read/write/check superblock**********************/ + +/**@brief Superblock write. + * @param bdev block device descriptor. + * @param s superblock descriptor + * @return Standard error code */ +int ext4_sb_write(struct ext4_blockdev *bdev, struct ext4_sblock *s); + +/**@brief Superblock read. + * @param bdev block device descriptor. + * @param s superblock descriptor + * @return Standard error code */ +int ext4_sb_read(struct ext4_blockdev *bdev, struct ext4_sblock *s); + +/**@brief Superblock simple validation. + * @param s superblock descriptor + * @return true if OK*/ +bool ext4_sb_check(struct ext4_sblock *s); + +/**@brief Superblock presence in block group. + * @param s superblock descriptor + * @param block_group block group id + * @return true if block group has superblock*/ +bool ext4_sb_is_super_in_bg(struct ext4_sblock *s, uint32_t block_group); + +/**@brief TODO:*/ +bool ext4_sb_sparse(uint32_t group); + +/**@brief TODO:*/ +uint32_t ext4_bg_num_gdb(struct ext4_sblock *s, uint32_t group); + +/**@brief TODO:*/ +uint32_t ext4_num_base_meta_clusters(struct ext4_sblock *s, + uint32_t block_group); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_SUPER_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_trans.h b/clib/lib/lwext4/include/ext4_trans.h new file mode 100644 index 0000000..b17373c --- /dev/null +++ b/clib/lib/lwext4/include/ext4_trans.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_trans.h + * @brief Transaction handle functions + */ + +#ifndef EXT4_TRANS_H +#define EXT4_TRANS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + +/**@brief Mark a buffer dirty and add it to the current transaction. + * @param buf buffer + * @return standard error code*/ +int ext4_trans_set_block_dirty(struct ext4_buf *buf); + +/**@brief Block get function (through cache, don't read). + * jbd_trans_get_access would be called in order to + * get write access to the buffer. + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_block_get_noread(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba); + +/**@brief Block get function (through cache). + * jbd_trans_get_access would be called in order to + * get write access to the buffer. + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_block_get(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba); + +/**@brief Try to add block to be revoked to the current transaction. + * @param bdev block device descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_try_revoke_block(struct ext4_blockdev *bdev, + uint64_t lba); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_TRANS_H */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_types.h b/clib/lib/lwext4/include/ext4_types.h new file mode 100644 index 0000000..c9cdd34 --- /dev/null +++ b/clib/lib/lwext4/include/ext4_types.h @@ -0,0 +1,844 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_types.h + * @brief Ext4 data structure definitions. + */ + +#ifndef EXT4_TYPES_H_ +#define EXT4_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +/* + * Types of blocks. + */ +typedef uint32_t ext4_lblk_t; +typedef uint64_t ext4_fsblk_t; + + +#define EXT4_CHECKSUM_CRC32C 1 + +#define UUID_SIZE 16 + +#pragma pack(push, 1) + +/* + * Structure of the super block + */ +struct ext4_sblock { + uint32_t inodes_count; /* I-nodes count */ + uint32_t blocks_count_lo; /* Blocks count */ + uint32_t reserved_blocks_count_lo; /* Reserved blocks count */ + uint32_t free_blocks_count_lo; /* Free blocks count */ + uint32_t free_inodes_count; /* Free inodes count */ + uint32_t first_data_block; /* First Data Block */ + uint32_t log_block_size; /* Block size */ + uint32_t log_cluster_size; /* Obsoleted fragment size */ + uint32_t blocks_per_group; /* Number of blocks per group */ + uint32_t frags_per_group; /* Obsoleted fragments per group */ + uint32_t inodes_per_group; /* Number of inodes per group */ + uint32_t mount_time; /* Mount time */ + uint32_t write_time; /* Write time */ + uint16_t mount_count; /* Mount count */ + uint16_t max_mount_count; /* Maximal mount count */ + uint16_t magic; /* Magic signature */ + uint16_t state; /* File system state */ + uint16_t errors; /* Behavior when detecting errors */ + uint16_t minor_rev_level; /* Minor revision level */ + uint32_t last_check_time; /* Time of last check */ + uint32_t check_interval; /* Maximum time between checks */ + uint32_t creator_os; /* Creator OS */ + uint32_t rev_level; /* Revision level */ + uint16_t def_resuid; /* Default uid for reserved blocks */ + uint16_t def_resgid; /* Default gid for reserved blocks */ + + /* Fields for EXT4_DYNAMIC_REV superblocks only. */ + uint32_t first_inode; /* First non-reserved inode */ + uint16_t inode_size; /* Size of inode structure */ + uint16_t block_group_index; /* Block group index of this superblock */ + uint32_t features_compatible; /* Compatible feature set */ + uint32_t features_incompatible; /* Incompatible feature set */ + uint32_t features_read_only; /* Readonly-compatible feature set */ + uint8_t uuid[UUID_SIZE]; /* 128-bit uuid for volume */ + char volume_name[16]; /* Volume name */ + char last_mounted[64]; /* Directory where last mounted */ + uint32_t algorithm_usage_bitmap; /* For compression */ + + /* + * Performance hints. Directory preallocation should only + * happen if the EXT4_FEATURE_COMPAT_DIR_PREALLOC flag is on. + */ + uint8_t s_prealloc_blocks; /* Number of blocks to try to preallocate */ + uint8_t s_prealloc_dir_blocks; /* Number to preallocate for dirs */ + uint16_t s_reserved_gdt_blocks; /* Per group desc for online growth */ + + /* + * Journaling support valid if EXT4_FEATURE_COMPAT_HAS_JOURNAL set. + */ + uint8_t journal_uuid[UUID_SIZE]; /* UUID of journal superblock */ + uint32_t journal_inode_number; /* Inode number of journal file */ + uint32_t journal_dev; /* Device number of journal file */ + uint32_t last_orphan; /* Head of list of inodes to delete */ + uint32_t hash_seed[4]; /* HTREE hash seed */ + uint8_t default_hash_version; /* Default hash version to use */ + uint8_t journal_backup_type; + uint16_t desc_size; /* Size of group descriptor */ + uint32_t default_mount_opts; /* Default mount options */ + uint32_t first_meta_bg; /* First metablock block group */ + uint32_t mkfs_time; /* When the filesystem was created */ + uint32_t journal_blocks[17]; /* Backup of the journal inode */ + + /* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */ + uint32_t blocks_count_hi; /* Blocks count */ + uint32_t reserved_blocks_count_hi; /* Reserved blocks count */ + uint32_t free_blocks_count_hi; /* Free blocks count */ + uint16_t min_extra_isize; /* All inodes have at least # bytes */ + uint16_t want_extra_isize; /* New inodes should reserve # bytes */ + uint32_t flags; /* Miscellaneous flags */ + uint16_t raid_stride; /* RAID stride */ + uint16_t mmp_interval; /* # seconds to wait in MMP checking */ + uint64_t mmp_block; /* Block for multi-mount protection */ + uint32_t raid_stripe_width; /* Blocks on all data disks (N * stride) */ + uint8_t log_groups_per_flex; /* FLEX_BG group size */ + uint8_t checksum_type; + uint16_t reserved_pad; + uint64_t kbytes_written; /* Number of lifetime kilobytes written */ + uint32_t snapshot_inum; /* I-node number of active snapshot */ + uint32_t snapshot_id; /* Sequential ID of active snapshot */ + uint64_t + snapshot_r_blocks_count; /* Reserved blocks for active snapshot's + future use */ + uint32_t + snapshot_list; /* I-node number of the head of the on-disk snapshot + list */ + uint32_t error_count; /* Number of file system errors */ + uint32_t first_error_time; /* First time an error happened */ + uint32_t first_error_ino; /* I-node involved in first error */ + uint64_t first_error_block; /* Block involved of first error */ + uint8_t first_error_func[32]; /* Function where the error happened */ + uint32_t first_error_line; /* Line number where error happened */ + uint32_t last_error_time; /* Most recent time of an error */ + uint32_t last_error_ino; /* I-node involved in last error */ + uint32_t last_error_line; /* Line number where error happened */ + uint64_t last_error_block; /* Block involved of last error */ + uint8_t last_error_func[32]; /* Function where the error happened */ + uint8_t mount_opts[64]; + uint32_t usr_quota_inum; /* inode for tracking user quota */ + uint32_t grp_quota_inum; /* inode for tracking group quota */ + uint32_t overhead_clusters; /* overhead blocks/clusters in fs */ + uint32_t backup_bgs[2]; /* groups with sparse_super2 SBs */ + uint8_t encrypt_algos[4]; /* Encryption algorithms in use */ + uint8_t encrypt_pw_salt[16]; /* Salt used for string2key algorithm */ + uint32_t lpf_ino; /* Location of the lost+found inode */ + uint32_t padding[100]; /* Padding to the end of the block */ + uint32_t checksum; /* crc32c(superblock) */ +}; + +#pragma pack(pop) + +#define EXT4_SUPERBLOCK_MAGIC 0xEF53 +#define EXT4_SUPERBLOCK_SIZE 1024 +#define EXT4_SUPERBLOCK_OFFSET 1024 + +#define EXT4_SUPERBLOCK_OS_LINUX 0 +#define EXT4_SUPERBLOCK_OS_HURD 1 + +/* + * Misc. filesystem flags + */ +#define EXT4_SUPERBLOCK_FLAGS_SIGNED_HASH 0x0001 +#define EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH 0x0002 +#define EXT4_SUPERBLOCK_FLAGS_TEST_FILESYS 0x0004 +/* + * Filesystem states + */ +#define EXT4_SUPERBLOCK_STATE_VALID_FS 0x0001 /* Unmounted cleanly */ +#define EXT4_SUPERBLOCK_STATE_ERROR_FS 0x0002 /* Errors detected */ +#define EXT4_SUPERBLOCK_STATE_ORPHAN_FS 0x0004 /* Orphans being recovered */ + +/* + * Behaviour when errors detected + */ +#define EXT4_SUPERBLOCK_ERRORS_CONTINUE 1 /* Continue execution */ +#define EXT4_SUPERBLOCK_ERRORS_RO 2 /* Remount fs read-only */ +#define EXT4_SUPERBLOCK_ERRORS_PANIC 3 /* Panic */ +#define EXT4_SUPERBLOCK_ERRORS_DEFAULT EXT4_ERRORS_CONTINUE + +/* + * Compatible features + */ +#define EXT4_FCOM_DIR_PREALLOC 0x0001 +#define EXT4_FCOM_IMAGIC_INODES 0x0002 +#define EXT4_FCOM_HAS_JOURNAL 0x0004 +#define EXT4_FCOM_EXT_ATTR 0x0008 +#define EXT4_FCOM_RESIZE_INODE 0x0010 +#define EXT4_FCOM_DIR_INDEX 0x0020 + +/* + * Read-only compatible features + */ +#define EXT4_FRO_COM_SPARSE_SUPER 0x0001 +#define EXT4_FRO_COM_LARGE_FILE 0x0002 +#define EXT4_FRO_COM_BTREE_DIR 0x0004 +#define EXT4_FRO_COM_HUGE_FILE 0x0008 +#define EXT4_FRO_COM_GDT_CSUM 0x0010 +#define EXT4_FRO_COM_DIR_NLINK 0x0020 +#define EXT4_FRO_COM_EXTRA_ISIZE 0x0040 +#define EXT4_FRO_COM_QUOTA 0x0100 +#define EXT4_FRO_COM_BIGALLOC 0x0200 +#define EXT4_FRO_COM_METADATA_CSUM 0x0400 + +/* + * Incompatible features + */ +#define EXT4_FINCOM_COMPRESSION 0x0001 +#define EXT4_FINCOM_FILETYPE 0x0002 +#define EXT4_FINCOM_RECOVER 0x0004 /* Needs recovery */ +#define EXT4_FINCOM_JOURNAL_DEV 0x0008 /* Journal device */ +#define EXT4_FINCOM_META_BG 0x0010 +#define EXT4_FINCOM_EXTENTS 0x0040 /* extents support */ +#define EXT4_FINCOM_64BIT 0x0080 +#define EXT4_FINCOM_MMP 0x0100 +#define EXT4_FINCOM_FLEX_BG 0x0200 +#define EXT4_FINCOM_EA_INODE 0x0400 /* EA in inode */ +#define EXT4_FINCOM_DIRDATA 0x1000 /* data in dirent */ +#define EXT4_FINCOM_BG_USE_META_CSUM 0x2000 /* use crc32c for bg */ +#define EXT4_FINCOM_LARGEDIR 0x4000 /* >2GB or 3-lvl htree */ +#define EXT4_FINCOM_INLINE_DATA 0x8000 /* data in inode */ + +/* + * EXT2 supported feature set + */ +#define EXT2_SUPPORTED_FCOM 0x0000 + +#define EXT2_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG) + +#define EXT2_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | \ + EXT4_FRO_COM_LARGE_FILE) + +/* + * EXT3 supported feature set + */ +#define EXT3_SUPPORTED_FCOM (EXT4_FCOM_DIR_INDEX) + +#define EXT3_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG) + +#define EXT3_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | EXT4_FRO_COM_LARGE_FILE) + +/* + * EXT4 supported feature set + */ +#define EXT4_SUPPORTED_FCOM (EXT4_FCOM_DIR_INDEX) + +#define EXT4_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG | \ + EXT4_FINCOM_EXTENTS | EXT4_FINCOM_FLEX_BG | \ + EXT4_FINCOM_64BIT) + +#define EXT4_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | \ + EXT4_FRO_COM_METADATA_CSUM | \ + EXT4_FRO_COM_LARGE_FILE | EXT4_FRO_COM_GDT_CSUM | \ + EXT4_FRO_COM_DIR_NLINK | \ + EXT4_FRO_COM_EXTRA_ISIZE | EXT4_FRO_COM_HUGE_FILE) + +/*Ignored features: + * RECOVER - journaling in lwext4 is not supported + * (probably won't be ever...) + * MMP - multi-mout protection (impossible scenario) + * */ +#define EXT_FINCOM_IGNORED \ + EXT4_FINCOM_RECOVER | EXT4_FINCOM_MMP + +#if 0 +/*TODO: Features incompatible to implement*/ +#define EXT4_SUPPORTED_FINCOM + (EXT4_FINCOM_INLINE_DATA) + +/*TODO: Features read only to implement*/ +#define EXT4_SUPPORTED_FRO_COM + EXT4_FRO_COM_BIGALLOC |\ + EXT4_FRO_COM_QUOTA) +#endif + + +/* Inode table/bitmap not in use */ +#define EXT4_BLOCK_GROUP_INODE_UNINIT 0x0001 +/* Block bitmap not in use */ +#define EXT4_BLOCK_GROUP_BLOCK_UNINIT 0x0002 +/* On-disk itable initialized to zero */ +#define EXT4_BLOCK_GROUP_ITABLE_ZEROED 0x0004 + +/* + * Structure of a blocks group descriptor + */ +struct ext4_bgroup { + uint32_t block_bitmap_lo; /* Blocks bitmap block */ + uint32_t inode_bitmap_lo; /* Inodes bitmap block */ + uint32_t inode_table_first_block_lo; /* Inodes table block */ + uint16_t free_blocks_count_lo; /* Free blocks count */ + uint16_t free_inodes_count_lo; /* Free inodes count */ + uint16_t used_dirs_count_lo; /* Directories count */ + uint16_t flags; /* EXT4_BG_flags (INODE_UNINIT, etc) */ + uint32_t exclude_bitmap_lo; /* Exclude bitmap for snapshots */ + uint16_t block_bitmap_csum_lo; /* crc32c(s_uuid+grp_num+bbitmap) LE */ + uint16_t inode_bitmap_csum_lo; /* crc32c(s_uuid+grp_num+ibitmap) LE */ + uint16_t itable_unused_lo; /* Unused inodes count */ + uint16_t checksum; /* crc16(sb_uuid+group+desc) */ + + uint32_t block_bitmap_hi; /* Blocks bitmap block MSB */ + uint32_t inode_bitmap_hi; /* I-nodes bitmap block MSB */ + uint32_t inode_table_first_block_hi; /* I-nodes table block MSB */ + uint16_t free_blocks_count_hi; /* Free blocks count MSB */ + uint16_t free_inodes_count_hi; /* Free i-nodes count MSB */ + uint16_t used_dirs_count_hi; /* Directories count MSB */ + uint16_t itable_unused_hi; /* Unused inodes count MSB */ + uint32_t exclude_bitmap_hi; /* Exclude bitmap block MSB */ + uint16_t block_bitmap_csum_hi; /* crc32c(s_uuid+grp_num+bbitmap) BE */ + uint16_t inode_bitmap_csum_hi; /* crc32c(s_uuid+grp_num+ibitmap) BE */ + uint32_t reserved; /* Padding */ +}; + + +#define EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE 32 +#define EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE 64 + +#define EXT4_MIN_BLOCK_SIZE 1024 /* 1 KiB */ +#define EXT4_MAX_BLOCK_SIZE 65536 /* 64 KiB */ +#define EXT4_REV0_INODE_SIZE 128 + +#define EXT4_INODE_BLOCK_SIZE 512 + +#define EXT4_INODE_DIRECT_BLOCK_COUNT 12 +#define EXT4_INODE_INDIRECT_BLOCK EXT4_INODE_DIRECT_BLOCK_COUNT +#define EXT4_INODE_DOUBLE_INDIRECT_BLOCK (EXT4_INODE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_TRIPPLE_INDIRECT_BLOCK (EXT4_INODE_DOUBLE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_BLOCKS (EXT4_INODE_TRIPPLE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_INDIRECT_BLOCK_COUNT \ + (EXT4_INODE_BLOCKS - EXT4_INODE_DIRECT_BLOCK_COUNT) + +#pragma pack(push, 1) + +/* + * Structure of an inode on the disk + */ +struct ext4_inode { + uint16_t mode; /* File mode */ + uint16_t uid; /* Low 16 bits of owner uid */ + uint32_t size_lo; /* Size in bytes */ + uint32_t access_time; /* Access time */ + uint32_t change_inode_time; /* I-node change time */ + uint32_t modification_time; /* Modification time */ + uint32_t deletion_time; /* Deletion time */ + uint16_t gid; /* Low 16 bits of group id */ + uint16_t links_count; /* Links count */ + uint32_t blocks_count_lo; /* Blocks count */ + uint32_t flags; /* File flags */ + uint32_t unused_osd1; /* OS dependent - not used in HelenOS */ + uint32_t blocks[EXT4_INODE_BLOCKS]; /* Pointers to blocks */ + uint32_t generation; /* File version (for NFS) */ + uint32_t file_acl_lo; /* File ACL */ + uint32_t size_hi; + uint32_t obso_faddr; /* Obsoleted fragment address */ + + union { + struct { + uint16_t blocks_high; + uint16_t file_acl_high; + uint16_t uid_high; + uint16_t gid_high; + uint16_t checksum_lo; /* crc32c(uuid+inum+inode) LE */ + uint16_t reserved2; + } linux2; + struct { + uint16_t reserved1; + uint16_t mode_high; + uint16_t uid_high; + uint16_t gid_high; + uint32_t author; + } hurd2; + } osd2; + + uint16_t extra_isize; + uint16_t checksum_hi; /* crc32c(uuid+inum+inode) BE */ + uint32_t ctime_extra; /* Extra change time (nsec << 2 | epoch) */ + uint32_t mtime_extra; /* Extra Modification time (nsec << 2 | epoch) */ + uint32_t atime_extra; /* Extra Access time (nsec << 2 | epoch) */ + uint32_t crtime; /* File creation time */ + uint32_t + crtime_extra; /* Extra file creation time (nsec << 2 | epoch) */ + uint32_t version_hi; /* High 32 bits for 64-bit version */ +}; + +#pragma pack(pop) + +#define EXT4_INODE_MODE_FIFO 0x1000 +#define EXT4_INODE_MODE_CHARDEV 0x2000 +#define EXT4_INODE_MODE_DIRECTORY 0x4000 +#define EXT4_INODE_MODE_BLOCKDEV 0x6000 +#define EXT4_INODE_MODE_FILE 0x8000 +#define EXT4_INODE_MODE_SOFTLINK 0xA000 +#define EXT4_INODE_MODE_SOCKET 0xC000 +#define EXT4_INODE_MODE_TYPE_MASK 0xF000 + +/* + * Inode flags + */ +#define EXT4_INODE_FLAG_SECRM 0x00000001 /* Secure deletion */ +#define EXT4_INODE_FLAG_UNRM 0x00000002 /* Undelete */ +#define EXT4_INODE_FLAG_COMPR 0x00000004 /* Compress file */ +#define EXT4_INODE_FLAG_SYNC 0x00000008 /* Synchronous updates */ +#define EXT4_INODE_FLAG_IMMUTABLE 0x00000010 /* Immutable file */ +#define EXT4_INODE_FLAG_APPEND 0x00000020 /* writes to file may only append */ +#define EXT4_INODE_FLAG_NODUMP 0x00000040 /* do not dump file */ +#define EXT4_INODE_FLAG_NOATIME 0x00000080 /* do not update atime */ + +/* Compression flags */ +#define EXT4_INODE_FLAG_DIRTY 0x00000100 +#define EXT4_INODE_FLAG_COMPRBLK \ + 0x00000200 /* One or more compressed clusters */ +#define EXT4_INODE_FLAG_NOCOMPR 0x00000400 /* Don't compress */ +#define EXT4_INODE_FLAG_ECOMPR 0x00000800 /* Compression error */ + +#define EXT4_INODE_FLAG_INDEX 0x00001000 /* hash-indexed directory */ +#define EXT4_INODE_FLAG_IMAGIC 0x00002000 /* AFS directory */ +#define EXT4_INODE_FLAG_JOURNAL_DATA \ + 0x00004000 /* File data should be journaled */ +#define EXT4_INODE_FLAG_NOTAIL 0x00008000 /* File tail should not be merged */ +#define EXT4_INODE_FLAG_DIRSYNC \ + 0x00010000 /* Dirsync behaviour (directories only) */ +#define EXT4_INODE_FLAG_TOPDIR 0x00020000 /* Top of directory hierarchies */ +#define EXT4_INODE_FLAG_HUGE_FILE 0x00040000 /* Set to each huge file */ +#define EXT4_INODE_FLAG_EXTENTS 0x00080000 /* Inode uses extents */ +#define EXT4_INODE_FLAG_EA_INODE 0x00200000 /* Inode used for large EA */ +#define EXT4_INODE_FLAG_EOFBLOCKS 0x00400000 /* Blocks allocated beyond EOF */ +#define EXT4_INODE_FLAG_RESERVED 0x80000000 /* reserved for ext4 lib */ + +#define EXT4_INODE_ROOT_INDEX 2 + + +#define EXT4_DIRECTORY_FILENAME_LEN 255 + +/**@brief Directory entry types. */ +enum { EXT4_DE_UNKNOWN = 0, + EXT4_DE_REG_FILE, + EXT4_DE_DIR, + EXT4_DE_CHRDEV, + EXT4_DE_BLKDEV, + EXT4_DE_FIFO, + EXT4_DE_SOCK, + EXT4_DE_SYMLINK }; + +#define EXT4_DIRENTRY_DIR_CSUM 0xDE + +#pragma pack(push, 1) + +union ext4_dir_en_internal { + uint8_t name_length_high; /* Higher 8 bits of name length */ + uint8_t inode_type; /* Type of referenced inode (in rev >= 0.5) */ +}; + +/** + * Linked list directory entry structure + */ +struct ext4_dir_en { + uint32_t inode; /* I-node for the entry */ + uint16_t entry_len; /* Distance to the next directory entry */ + uint8_t name_len; /* Lower 8 bits of name length */ + + union ext4_dir_en_internal in; + uint8_t name[]; /* Entry name */ +}; + +/* Structures for indexed directory */ + +struct ext4_dir_idx_climit { + uint16_t limit; + uint16_t count; +}; + +struct ext4_dir_idx_dot_en { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; + uint8_t name[4]; +}; + +struct ext4_dir_idx_rinfo { + uint32_t reserved_zero; + uint8_t hash_version; + uint8_t info_length; + uint8_t indirect_levels; + uint8_t unused_flags; +}; + +struct ext4_dir_idx_entry { + uint32_t hash; + uint32_t block; +}; + +struct ext4_dir_idx_root { + struct ext4_dir_idx_dot_en dots[2]; + struct ext4_dir_idx_rinfo info; + struct ext4_dir_idx_entry en[]; +}; + +struct ext4_fake_dir_entry { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; +}; + +struct ext4_dir_idx_node { + struct ext4_fake_dir_entry fake; + struct ext4_dir_idx_entry entries[]; +}; + +/* + * This goes at the end of each htree block. + */ +struct ext4_dir_idx_tail { + uint32_t reserved; + uint32_t checksum; /* crc32c(uuid+inum+dirblock) */ +}; + +/* + * This is a bogus directory entry at the end of each leaf block that + * records checksums. + */ +struct ext4_dir_entry_tail { + uint32_t reserved_zero1; /* Pretend to be unused */ + uint16_t rec_len; /* 12 */ + uint8_t reserved_zero2; /* Zero name length */ + uint8_t reserved_ft; /* 0xDE, fake file type */ + uint32_t checksum; /* crc32c(uuid+inum+dirblock) */ +}; + +#pragma pack(pop) + +#define EXT4_DIRENT_TAIL(block, blocksize) \ + ((struct ext4_dir_entry_tail *)(((char *)(block)) + ((blocksize) - \ + sizeof(struct ext4_dir_entry_tail)))) + +#define EXT4_ERR_BAD_DX_DIR (-25000) + +#define EXT4_LINK_MAX 65000 + +#define EXT4_BAD_INO 1 +#define EXT4_ROOT_INO 2 +#define EXT4_BOOT_LOADER_INO 5 +#define EXT4_UNDEL_DIR_INO 6 +#define EXT4_RESIZE_INO 7 +#define EXT4_JOURNAL_INO 8 + +#define EXT4_GOOD_OLD_FIRST_INO 11 +#define EXT_MAX_BLOCKS (ext4_lblk_t) (-1) +#define IN_RANGE(b, first, len) ((b) >= (first) && (b) <= (first) + (len) - 1) + + +/******************************************************************************/ + +/* EXT3 HTree directory indexing */ +#define EXT2_HTREE_LEGACY 0 +#define EXT2_HTREE_HALF_MD4 1 +#define EXT2_HTREE_TEA 2 +#define EXT2_HTREE_LEGACY_UNSIGNED 3 +#define EXT2_HTREE_HALF_MD4_UNSIGNED 4 +#define EXT2_HTREE_TEA_UNSIGNED 5 + +#define EXT2_HTREE_EOF 0x7FFFFFFFUL + +#define EXT4_GOOD_OLD_INODE_SIZE 128 + +/*****************************************************************************/ + +/* + * JBD stores integers in big endian. + */ + +#define JBD_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * Descriptor block types: + */ + +#define JBD_DESCRIPTOR_BLOCK 1 +#define JBD_COMMIT_BLOCK 2 +#define JBD_SUPERBLOCK 3 +#define JBD_SUPERBLOCK_V2 4 +#define JBD_REVOKE_BLOCK 5 + +#pragma pack(push, 1) + +/* + * Standard header for all descriptor blocks: + */ +struct jbd_bhdr { + uint32_t magic; + uint32_t blocktype; + uint32_t sequence; +}; + +#pragma pack(pop) + +/* + * Checksum types. + */ +#define JBD_CRC32_CHKSUM 1 +#define JBD_MD5_CHKSUM 2 +#define JBD_SHA1_CHKSUM 3 +#define JBD_CRC32C_CHKSUM 4 + +#define JBD_CRC32_CHKSUM_SIZE 4 + +#define JBD_CHECKSUM_BYTES (32 / sizeof(uint32_t)) + +#pragma pack(push, 1) + +/* + * Commit block header for storing transactional checksums: + * + * NOTE: If FEATURE_COMPAT_CHECKSUM (checksum v1) is set, the h_chksum* + * fields are used to store a checksum of the descriptor and data blocks. + * + * If FEATURE_INCOMPAT_CSUM_V2 (checksum v2) is set, then the h_chksum + * field is used to store crc32c(uuid+commit_block). Each journal metadata + * block gets its own checksum, and data block checksums are stored in + * journal_block_tag (in the descriptor). The other h_chksum* fields are + * not used. + * + * If FEATURE_INCOMPAT_CSUM_V3 is set, the descriptor block uses + * journal_block_tag3_t to store a full 32-bit checksum. Everything else + * is the same as v2. + * + * Checksum v1, v2, and v3 are mutually exclusive features. + */ + +struct jbd_commit_header { + struct jbd_bhdr header; + uint8_t chksum_type; + uint8_t chksum_size; + uint8_t padding[2]; + uint32_t chksum[JBD_CHECKSUM_BYTES]; + uint64_t commit_sec; + uint32_t commit_nsec; +}; + +/* + * The block tag: used to describe a single buffer in the journal + */ +struct jbd_block_tag3 { + uint32_t blocknr; /* The on-disk block number */ + uint32_t flags; /* See below */ + uint32_t blocknr_high; /* most-significant high 32bits. */ + uint32_t checksum; /* crc32c(uuid+seq+block) */ +}; + +struct jbd_block_tag { + uint32_t blocknr; /* The on-disk block number */ + uint16_t checksum; /* truncated crc32c(uuid+seq+block) */ + uint16_t flags; /* See below */ + uint32_t blocknr_high; /* most-significant high 32bits. */ +}; + +#pragma pack(pop) + +/* Definitions for the journal tag flags word: */ +#define JBD_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JBD_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JBD_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JBD_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + +#pragma pack(push, 1) + +/* Tail of descriptor block, for checksumming */ +struct jbd_block_tail { + uint32_t checksum; +}; + +/* + * The revoke descriptor: used on disk to describe a series of blocks to + * be revoked from the log + */ +struct jbd_revoke_header { + struct jbd_bhdr header; + uint32_t count; /* Count of bytes used in the block */ +}; + +/* Tail of revoke block, for checksumming */ +struct jbd_revoke_tail { + uint32_t checksum; +}; + +#pragma pack(pop) + +#define JBD_USERS_MAX 48 +#define JBD_USERS_SIZE (UUID_SIZE * JBD_USERS_MAX) + +#pragma pack(push, 1) + +/* + * The journal superblock. All fields are in big-endian byte order. + */ +struct jbd_sb { +/* 0x0000 */ + struct jbd_bhdr header; + +/* 0x000C */ + /* Static information describing the journal */ + uint32_t blocksize; /* journal device blocksize */ + uint32_t maxlen; /* total blocks in journal file */ + uint32_t first; /* first block of log information */ + +/* 0x0018 */ + /* Dynamic information describing the current state of the log */ + uint32_t sequence; /* first commit ID expected in log */ + uint32_t start; /* blocknr of start of log */ + +/* 0x0020 */ + /* Error value, as set by journal_abort(). */ + int32_t error_val; + +/* 0x0024 */ + /* Remaining fields are only valid in a version-2 superblock */ + uint32_t feature_compat; /* compatible feature set */ + uint32_t feature_incompat; /* incompatible feature set */ + uint32_t feature_ro_compat; /* readonly-compatible feature set */ +/* 0x0030 */ + uint8_t uuid[UUID_SIZE]; /* 128-bit uuid for journal */ + +/* 0x0040 */ + uint32_t nr_users; /* Nr of filesystems sharing log */ + + uint32_t dynsuper; /* Blocknr of dynamic superblock copy*/ + +/* 0x0048 */ + uint32_t max_transaction; /* Limit of journal blocks per trans.*/ + uint32_t max_trandata; /* Limit of data blocks per trans. */ + +/* 0x0050 */ + uint8_t checksum_type; /* checksum type */ + uint8_t padding2[3]; + uint32_t padding[42]; + uint32_t checksum; /* crc32c(superblock) */ + +/* 0x0100 */ + uint8_t users[JBD_USERS_SIZE]; /* ids of all fs'es sharing the log */ + +/* 0x0400 */ +}; + +#pragma pack(pop) + +#define JBD_SUPERBLOCK_SIZE sizeof(struct jbd_sb) + +#define JBD_HAS_COMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_compat & to_be32((mask)))) +#define JBD_HAS_RO_COMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_ro_compat & to_be32((mask)))) +#define JBD_HAS_INCOMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_incompat & to_be32((mask)))) + +#define JBD_FEATURE_COMPAT_CHECKSUM 0x00000001 + +#define JBD_FEATURE_INCOMPAT_REVOKE 0x00000001 +#define JBD_FEATURE_INCOMPAT_64BIT 0x00000002 +#define JBD_FEATURE_INCOMPAT_ASYNC_COMMIT 0x00000004 +#define JBD_FEATURE_INCOMPAT_CSUM_V2 0x00000008 +#define JBD_FEATURE_INCOMPAT_CSUM_V3 0x00000010 + +/* Features known to this kernel version: */ +#define JBD_KNOWN_COMPAT_FEATURES 0 +#define JBD_KNOWN_ROCOMPAT_FEATURES 0 +#define JBD_KNOWN_INCOMPAT_FEATURES (JBD_FEATURE_INCOMPAT_REVOKE|\ + JBD_FEATURE_INCOMPAT_ASYNC_COMMIT|\ + JBD_FEATURE_INCOMPAT_64BIT|\ + JBD_FEATURE_INCOMPAT_CSUM_V2|\ + JBD_FEATURE_INCOMPAT_CSUM_V3) + +/*****************************************************************************/ + +#define EXT4_CRC32_INIT (0xFFFFFFFFUL) + +/*****************************************************************************/ + +#ifdef __cplusplus +} +#endif + + +#if CONFIG_USE_USER_MALLOC + +#define ext4_malloc ext4_user_malloc +#define ext4_calloc ext4_user_calloc +#define ext4_realloc ext4_user_realloc +#define ext4_free ext4_user_free + +#else + +#define ext4_malloc malloc +#define ext4_calloc calloc +#define ext4_realloc realloc +#define ext4_free free + +#endif + + +#endif /* EXT4_TYPES_H_ */ + +/** + * @} + */ diff --git a/clib/lib/lwext4/include/ext4_xattr.h b/clib/lib/lwext4/include/ext4_xattr.h new file mode 100644 index 0000000..d79febc --- /dev/null +++ b/clib/lib/lwext4/include/ext4_xattr.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.h + * @brief Extended Attribute manipulation. + */ + +#ifndef EXT4_XATTR_H_ +#define EXT4_XATTR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +struct ext4_xattr_info { + uint8_t name_index; + const char *name; + size_t name_len; + const void *value; + size_t value_len; +}; + +struct ext4_xattr_list_entry { + uint8_t name_index; + char *name; + size_t name_len; + struct ext4_xattr_list_entry *next; +}; + +struct ext4_xattr_search { + /* The first entry in the buffer */ + struct ext4_xattr_entry *first; + + /* The address of the buffer */ + void *base; + + /* The first inaccessible address */ + void *end; + + /* The current entry pointer */ + struct ext4_xattr_entry *here; + + /* Entry not found */ + bool not_found; +}; + +const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len, + bool *found); + +const char *ext4_get_xattr_name_prefix(uint8_t name_index, + size_t *ret_prefix_len); + +int ext4_xattr_list(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_list_entry *list, size_t *list_len); + +int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, void *buf, size_t buf_len, + size_t *data_len); + +int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len); + +int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, const void *value, + size_t value_len); + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @} + */ diff --git a/clib/lib/lwext4/include/misc/queue.h b/clib/lib/lwext4/include/misc/queue.h new file mode 100644 index 0000000..6efd6f3 --- /dev/null +++ b/clib/lib/lwext4/include/misc/queue.h @@ -0,0 +1,702 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD$ + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../ext4_config.h" + +/* + * This file defines four types of data structures: singly-linked lists, + * singly-linked tail queues, lists and tail queues. + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A singly-linked tail queue is headed by a pair of pointers, one to the + * head of the list and the other to the tail of the list. The elements are + * singly linked for minimum space and pointer manipulation overhead at the + * expense of O(n) removal for arbitrary elements. New elements can be added + * to the list after an existing element, at the head of the list, or at the + * end of the list. Elements being removed from the head of the tail queue + * should use the explicit macro for this purpose for optimum efficiency. + * A singly-linked tail queue may only be traversed in the forward direction. + * Singly-linked tail queues are ideal for applications with large datasets + * and few or no removals or for implementing a FIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may be traversed in either direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + * + * + * SLIST LIST STAILQ TAILQ + * _HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - + - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_FROM + + + + + * _FOREACH_SAFE + + + + + * _FOREACH_FROM_SAFE + + + + + * _FOREACH_REVERSE - - - + + * _FOREACH_REVERSE_FROM - - - + + * _FOREACH_REVERSE_SAFE - - - + + * _FOREACH_REVERSE_FROM_SAFE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _CONCAT - - + + + * _REMOVE_AFTER + - + - + * _REMOVE_HEAD + - + - + * _REMOVE + + + + + * _SWAP + + + + + * + */ +#ifdef QUEUE_MACRO_DEBUG +/* Store the last 2 places the queue element or head was altered */ +struct qm_trace { + unsigned long lastline; + unsigned long prevline; + const char *lastfile; + const char *prevfile; +}; + +#define TRACEBUF struct qm_trace trace; +#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) + +#define QMD_TRACE_HEAD(head) do { \ + (head)->trace.prevline = (head)->trace.lastline; \ + (head)->trace.prevfile = (head)->trace.lastfile; \ + (head)->trace.lastline = __LINE__; \ + (head)->trace.lastfile = __FILE__; \ +} while (0) + +#define QMD_TRACE_ELEM(elem) do { \ + (elem)->trace.prevline = (elem)->trace.lastline; \ + (elem)->trace.prevfile = (elem)->trace.lastfile; \ + (elem)->trace.lastline = __LINE__; \ + (elem)->trace.lastfile = __FILE__; \ +} while (0) + +#else +#define QMD_TRACE_ELEM(elem) +#define QMD_TRACE_HEAD(head) +#define QMD_SAVELINK(name, link) +#define TRACEBUF +#define TRACEBUF_INITIALIZER +#define TRASHIT(x) +#endif /* QUEUE_MACRO_DEBUG */ + +/* + * Singly-linked List declarations. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) + +#define SLIST_FIRST(head) ((head)->slh_first) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST((head)); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != NULL; \ + (varp) = &SLIST_NEXT((var), field)) + +#define SLIST_INIT(head) do { \ + SLIST_FIRST((head)) = NULL; \ +} while (0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ + SLIST_NEXT((slistelm), field) = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ + SLIST_FIRST((head)) = (elm); \ +} while (0) + +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ + if (SLIST_FIRST((head)) == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = SLIST_FIRST((head)); \ + while (SLIST_NEXT(curelm, field) != (elm)) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_REMOVE_AFTER(curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + SLIST_NEXT(elm, field) = \ + SLIST_NEXT(SLIST_NEXT(elm, field), field); \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ +} while (0) + +#define SLIST_SWAP(head1, head2, type) do { \ + struct type *swap_first = SLIST_FIRST(head1); \ + SLIST_FIRST(head1) = SLIST_FIRST(head2); \ + SLIST_FIRST(head2) = swap_first; \ +} while (0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (0) + +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ +} while (0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ +} while (0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? NULL : \ + __containerof((head)->stqh_last, struct type, field.stqe_next)) + +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = STAILQ_FIRST((head)); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_SWAP(head1, head2, type) do { \ + struct type *swap_first = STAILQ_FIRST(head1); \ + struct type **swap_last = (head1)->stqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &STAILQ_FIRST(head2); \ +} while (0) + + +/* + * List declarations. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ + +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_LIST_CHECK_HEAD(head, field) do { \ + if (LIST_FIRST((head)) != NULL && \ + LIST_FIRST((head))->field.le_prev != \ + &LIST_FIRST((head))) \ + panic("Bad list head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_LIST_CHECK_NEXT(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL && \ + LIST_NEXT((elm), field)->field.le_prev != \ + &((elm)->field.le_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_LIST_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.le_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_LIST_CHECK_HEAD(head, field) +#define QMD_LIST_CHECK_NEXT(elm, field) +#define QMD_LIST_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_INIT(head) do { \ + LIST_FIRST((head)) = NULL; \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QMD_LIST_CHECK_NEXT(listelm, field); \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_LIST_CHECK_PREV(listelm, field); \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QMD_LIST_CHECK_HEAD((head), field); \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_PREV(elm, head, type, field) \ + ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ + __containerof((elm)->field.le_prev, struct type, field.le_next)) + +#define LIST_REMOVE(elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.le_next); \ + QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ + QMD_LIST_CHECK_NEXT(elm, field); \ + QMD_LIST_CHECK_PREV(elm, field); \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ +} while (0) + +#define LIST_SWAP(head1, head2, type, field) do { \ + struct type *swap_tmp = LIST_FIRST((head1)); \ + LIST_FIRST((head1)) = LIST_FIRST((head2)); \ + LIST_FIRST((head2)) = swap_tmp; \ + if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ + if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ +} while (0) + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +/* + * Tail queue functions. + */ +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_TAILQ_CHECK_HEAD(head, field) do { \ + if (!TAILQ_EMPTY(head) && \ + TAILQ_FIRST((head))->field.tqe_prev != \ + &TAILQ_FIRST((head))) \ + panic("Bad tailq head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_TAIL(head, field) do { \ + if (*(head)->tqh_last != NULL) \ + panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ + if (TAILQ_NEXT((elm), field) != NULL && \ + TAILQ_NEXT((elm), field)->field.tqe_prev != \ + &((elm)->field.tqe_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_TAILQ_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.tqe_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_TAILQ_CHECK_HEAD(head, field) +#define QMD_TAILQ_CHECK_TAIL(head, headname) +#define QMD_TAILQ_CHECK_NEXT(elm, field) +#define QMD_TAILQ_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + QMD_TRACE_HEAD(head1); \ + QMD_TRACE_HEAD(head2); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QMD_TAILQ_CHECK_NEXT(listelm, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_TAILQ_CHECK_PREV(listelm, field); \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QMD_TAILQ_CHECK_HEAD(head, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QMD_TAILQ_CHECK_TAIL(head, field); \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ + QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ + QMD_TAILQ_CHECK_NEXT(elm, field); \ + QMD_TAILQ_CHECK_PREV(elm, field); \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + QMD_TRACE_HEAD(head); \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type, field) do { \ + struct type *swap_first = (head1)->tqh_first; \ + struct type **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/clib/lib/lwext4/include/misc/tree.h b/clib/lib/lwext4/include/misc/tree.h new file mode 100644 index 0000000..8ed5d41 --- /dev/null +++ b/clib/lib/lwext4/include/misc/tree.h @@ -0,0 +1,809 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../ext4_config.h" + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ + RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \ + RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \ + RB_PROTOTYPE_INSERT(name, type, attr); \ + RB_PROTOTYPE_REMOVE(name, type, attr); \ + RB_PROTOTYPE_FIND(name, type, attr); \ + RB_PROTOTYPE_NFIND(name, type, attr); \ + RB_PROTOTYPE_NEXT(name, type, attr); \ + RB_PROTOTYPE_PREV(name, type, attr); \ + RB_PROTOTYPE_MINMAX(name, type, attr); +#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ + attr void name##_RB_INSERT_COLOR(struct name *, struct type *) +#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ + attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *) +#define RB_PROTOTYPE_REMOVE(name, type, attr) \ + attr struct type *name##_RB_REMOVE(struct name *, struct type *) +#define RB_PROTOTYPE_INSERT(name, type, attr) \ + attr struct type *name##_RB_INSERT(struct name *, struct type *) +#define RB_PROTOTYPE_FIND(name, type, attr) \ + attr struct type *name##_RB_FIND(struct name *, struct type *) +#define RB_PROTOTYPE_NFIND(name, type, attr) \ + attr struct type *name##_RB_NFIND(struct name *, struct type *) +#define RB_PROTOTYPE_NEXT(name, type, attr) \ + attr struct type *name##_RB_NEXT(struct type *) +#define RB_PROTOTYPE_PREV(name, type, attr) \ + attr struct type *name##_RB_PREV(struct type *) +#define RB_PROTOTYPE_MINMAX(name, type, attr) \ + attr struct type *name##_RB_MINMAX(struct name *, int) + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ + RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ + RB_GENERATE_INSERT(name, type, field, cmp, attr) \ + RB_GENERATE_REMOVE(name, type, field, attr) \ + RB_GENERATE_FIND(name, type, field, cmp, attr) \ + RB_GENERATE_NFIND(name, type, field, cmp, attr) \ + RB_GENERATE_NEXT(name, type, field, attr) \ + RB_GENERATE_PREV(name, type, field, attr) \ + RB_GENERATE_MINMAX(name, type, field, attr) + +#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) != NULL && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)) \ + != NULL) \ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)) \ + != NULL) \ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE(name, type, field, attr) \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field)) != NULL) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field)) != NULL); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + +#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} + +#define RB_GENERATE_FIND(name, type, field, cmp, attr) \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} + +#define RB_GENERATE_NFIND(name, type, field, cmp, attr) \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} + +#define RB_GENERATE_NEXT(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_PREV(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_MINMAX(name, type, field, attr) \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TREE_H_ */ diff --git a/clib/lib/lwext4/src/CMakeLists.txt b/clib/lib/lwext4/src/CMakeLists.txt new file mode 100644 index 0000000..257b8cc --- /dev/null +++ b/clib/lib/lwext4/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +option(LWEXT4_BUILD_SHARED_LIB "Build shared library" OFF) + +#LIBRARY +include_directories(.) +aux_source_directory(. LWEXT4_SRC) +if(LWEXT4_BUILD_SHARED_LIB) + add_library(lwext4 SHARED ${LWEXT4_SRC}) +else() + add_library(lwext4 STATIC ${LWEXT4_SRC}) +endif() + +if (DEFINED SIZE) + add_custom_target(lib_size ALL DEPENDS lwext4 COMMAND ${SIZE} liblwext4.a) +else() + +endif() + +if (DEFINED INSTALL_LIB) +INSTALL(TARGETS lwext4 DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) +INSTALL(DIRECTORY ${PROJECT_BINARY_DIR}/include/. DESTINATION ${CMAKE_INSTALL_PREFIX}/include/lwext4) +endif() diff --git a/clib/lib/lwext4/src/ext4.c b/clib/lib/lwext4/src/ext4.c new file mode 100644 index 0000000..83d757c --- /dev/null +++ b/clib/lib/lwext4/src/ext4.c @@ -0,0 +1,3241 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4.h + * @brief Ext4 high level operations (file, directory, mountpoints...) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +/**@brief Mount point OS dependent lock*/ +#define EXT4_MP_LOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->lock(); \ + } while (0) + +/**@brief Mount point OS dependent unlock*/ +#define EXT4_MP_UNLOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->unlock(); \ + } while (0) + +/**@brief Mount point descriptor.*/ +struct ext4_mountpoint { + + /**@brief Mount done flag.*/ + bool mounted; + + /**@brief Mount point name (@ref ext4_mount)*/ + char name[CONFIG_EXT4_MAX_MP_NAME + 1]; + + /**@brief OS dependent lock/unlock functions.*/ + const struct ext4_lock *os_locks; + + /**@brief Ext4 filesystem internals.*/ + struct ext4_fs fs; + + /**@brief JBD fs.*/ + struct jbd_fs jbd_fs; + + /**@brief Journal.*/ + struct jbd_journal jbd_journal; + + /**@brief Block cache.*/ + struct ext4_bcache bc; +}; + +/**@brief Block devices descriptor.*/ +struct ext4_block_devices { + + /**@brief Block device name.*/ + char name[CONFIG_EXT4_MAX_BLOCKDEV_NAME + 1]; + + /**@brief Block device handle.*/ + struct ext4_blockdev *bd; +}; + +/**@brief Block devices.*/ +static struct ext4_block_devices s_bdevices[CONFIG_EXT4_BLOCKDEVS_COUNT]; + +/**@brief Mountpoints.*/ +static struct ext4_mountpoint s_mp[CONFIG_EXT4_MOUNTPOINTS_COUNT]; + +int ext4_device_register(struct ext4_blockdev *bd, + const char *dev_name) +{ + ext4_assert(bd && dev_name); + + if (strlen(dev_name) > CONFIG_EXT4_MAX_BLOCKDEV_NAME) + return EINVAL; + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!strcmp(s_bdevices[i].name, dev_name)) + return EEXIST; + } + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!s_bdevices[i].bd) { + strcpy(s_bdevices[i].name, dev_name); + s_bdevices[i].bd = bd; + return EOK; + } + } + + return ENOSPC; +} + +int ext4_device_unregister(const char *dev_name) +{ + ext4_assert(dev_name); + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (strcmp(s_bdevices[i].name, dev_name)) + continue; + + memset(&s_bdevices[i], 0, sizeof(s_bdevices[i])); + return EOK; + } + + return ENOENT; +} + +int ext4_device_unregister_all(void) +{ + memset(s_bdevices, 0, sizeof(s_bdevices)); + + return EOK; +} + +/****************************************************************************/ + +static bool ext4_is_dots(const uint8_t *name, size_t name_size) +{ + if ((name_size == 1) && (name[0] == '.')) + return true; + + if ((name_size == 2) && (name[0] == '.') && (name[1] == '.')) + return true; + + return false; +} + +static int ext4_has_children(bool *has_children, struct ext4_inode_ref *enode) +{ + struct ext4_sblock *sb = &enode->fs->sb; + + /* Check if node is directory */ + if (!ext4_inode_is_type(sb, enode->inode, EXT4_INODE_MODE_DIRECTORY)) { + *has_children = false; + return EOK; + } + + struct ext4_dir_iter it; + int rc = ext4_dir_iterator_init(&it, enode, 0); + if (rc != EOK) + return rc; + + /* Find a non-empty directory entry */ + bool found = false; + while (it.curr != NULL) { + if (ext4_dir_en_get_inode(it.curr) != 0) { + uint16_t nsize; + nsize = ext4_dir_en_get_name_len(sb, it.curr); + if (!ext4_is_dots(it.curr->name, nsize)) { + found = true; + break; + } + } + + rc = ext4_dir_iterator_next(&it); + if (rc != EOK) { + ext4_dir_iterator_fini(&it); + return rc; + } + } + + rc = ext4_dir_iterator_fini(&it); + if (rc != EOK) + return rc; + + *has_children = found; + + return EOK; +} + +static int ext4_link(struct ext4_mountpoint *mp, struct ext4_inode_ref *parent, + struct ext4_inode_ref *ch, const char *n, + uint32_t len, bool rename) +{ + /* Check maximum name length */ + if (len > EXT4_DIRECTORY_FILENAME_LEN) + return EINVAL; + + /* Add entry to parent directory */ + int r = ext4_dir_add_entry(parent, n, len, ch); + if (r != EOK) + return r; + + /* Fill new dir -> add '.' and '..' entries. + * Also newly allocated inode should have 0 link count. + */ + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, ch->inode, + EXT4_INODE_MODE_DIRECTORY); + if (is_dir && !rename) { + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(ch, parent); + if (r != EOK) + return r; + + ext4_inode_set_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + ch->dirty = true; + } else +#endif + { + r = ext4_dir_add_entry(ch, ".", strlen("."), ch); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + return r; + } + + r = ext4_dir_add_entry(ch, "..", strlen(".."), parent); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + ext4_dir_remove_entry(ch, ".", strlen(".")); + return r; + } + } + + /*New empty directory. Two links (. and ..) */ + ext4_inode_set_links_cnt(ch->inode, 2); + ext4_fs_inode_links_count_inc(parent); + ch->dirty = true; + parent->dirty = true; + return r; + } + /* + * In case we want to rename a directory, + * we reset the original '..' pointer. + */ + if (is_dir) { + bool idx; + idx = ext4_inode_has_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + struct ext4_dir_search_result res; + if (!idx) { + r = ext4_dir_find_entry(&res, ch, "..", strlen("..")); + if (r != EOK) + return EIO; + + ext4_dir_en_set_inode(res.dentry, parent->index); + ext4_trans_set_block_dirty(res.block.buf); + r = ext4_dir_destroy_result(ch, &res); + if (r != EOK) + return r; + + } else { +#if CONFIG_DIR_INDEX_ENABLE + r = ext4_dir_dx_reset_parent_inode(ch, parent->index); + if (r != EOK) + return r; + +#endif + } + + ext4_fs_inode_links_count_inc(parent); + parent->dirty = true; + } + if (!rename) { + ext4_fs_inode_links_count_inc(ch); + ch->dirty = true; + } + + return r; +} + +static int ext4_unlink(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + bool has_children; + int rc = ext4_has_children(&has_children, child); + if (rc != EOK) + return rc; + + /* Cannot unlink non-empty node */ + if (has_children) + return ENOTEMPTY; + + /* Remove entry from parent directory */ + rc = ext4_dir_remove_entry(parent, name, name_len); + if (rc != EOK) + return rc; + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, child->inode, + EXT4_INODE_MODE_DIRECTORY); + + /* If directory - handle links from parent */ + if (is_dir) { + ext4_fs_inode_links_count_dec(parent); + parent->dirty = true; + } + + /* + * TODO: Update timestamps of the parent + * (when we have wall-clock time). + * + * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now); + * ext4_inode_set_modification_time(parent->inode, (uint32_t) now); + * parent->dirty = true; + */ + + /* + * TODO: Update timestamp for inode. + * + * ext4_inode_set_change_inode_time(child->inode, + * (uint32_t) now); + */ + if (ext4_inode_get_links_cnt(child->inode)) { + ext4_fs_inode_links_count_dec(child); + child->dirty = true; + } + + return EOK; +} + +/****************************************************************************/ + +int ext4_mount(const char *dev_name, const char *mount_point, + bool read_only) +{ + int r; + uint32_t bsize; + struct ext4_bcache *bc; + struct ext4_blockdev *bd = 0; + struct ext4_mountpoint *mp = 0; + + ext4_assert(mount_point && dev_name); + + size_t mp_len = strlen(mount_point); + + if (mp_len > CONFIG_EXT4_MAX_MP_NAME) + return EINVAL; + + if (mount_point[mp_len - 1] != '/') + return ENOTSUP; + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!strcmp(dev_name, s_bdevices[i].name)) { + bd = s_bdevices[i].bd; + break; + } + } + + if (!bd) + return ENODEV; + + for (size_t i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!s_mp[i].mounted) { + strcpy(s_mp[i].name, mount_point); + mp = &s_mp[i]; + break; + } + + if (!strcmp(s_mp[i].name, mount_point)) + return EOK; + } + + if (!mp) + return ENOMEM; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + r = ext4_fs_init(&mp->fs, bd, read_only); + if (r != EOK) { + ext4_block_fini(bd); + return r; + } + + bsize = ext4_sb_get_block_size(&mp->fs.sb); + ext4_block_set_lb_size(bd, bsize); + bc = &mp->bc; + + r = ext4_bcache_init_dynamic(bc, CONFIG_BLOCK_DEV_CACHE_SIZE, bsize); + if (r != EOK) { + ext4_block_fini(bd); + return r; + } + + if (bsize != bc->itemsize) + return ENOTSUP; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, bc); + if (r != EOK) { + ext4_bcache_cleanup(bc); + ext4_block_fini(bd); + ext4_bcache_fini_dynamic(bc); + return r; + } + + bd->fs = &mp->fs; + mp->mounted = 1; + return r; +} + + +int ext4_umount(const char *mount_point) +{ + int i; + int r; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(s_mp[i].name, mount_point)) { + mp = &s_mp[i]; + break; + } + } + + if (!mp) + return ENODEV; + + r = ext4_fs_fini(&mp->fs); + if (r != EOK) + goto Finish; + + mp->mounted = 0; + + ext4_bcache_cleanup(mp->fs.bdev->bc); + ext4_bcache_fini_dynamic(mp->fs.bdev->bc); + + r = ext4_block_fini(mp->fs.bdev); +Finish: + mp->fs.bdev->fs = NULL; + return r; +} + +static struct ext4_mountpoint *ext4_get_mount(const char *path) +{ + for (size_t i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + + if (!s_mp[i].mounted) + continue; + + if (!strncmp(s_mp[i].name, path, strlen(s_mp[i].name))) + return &s_mp[i]; + } + + return NULL; +} + +__unused +static int __ext4_journal_start(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EOK; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_get_fs(&mp->fs, &mp->jbd_fs); + if (r != EOK) + goto Finish; + + r = jbd_journal_start(&mp->jbd_fs, &mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + goto Finish; + } + mp->fs.jbd_fs = &mp->jbd_fs; + mp->fs.jbd_journal = &mp->jbd_journal; + } +Finish: + return r; +} + +__unused +static int __ext4_journal_stop(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EOK; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_journal_stop(&mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + r = jbd_put_fs(&mp->jbd_fs); + if (r != EOK) { + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + } +Finish: + return r; +} + +__unused +static int __ext4_recover(const char *mount_point) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + int r = ENOTSUP; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_HAS_JOURNAL)) { + struct jbd_fs *jbd_fs = ext4_calloc(1, sizeof(struct jbd_fs)); + if (!jbd_fs) { + r = ENOMEM; + goto Finish; + } + + r = jbd_get_fs(&mp->fs, jbd_fs); + if (r != EOK) { + ext4_free(jbd_fs); + goto Finish; + } + + r = jbd_recover(jbd_fs); + jbd_put_fs(jbd_fs); + ext4_free(jbd_fs); + } + if (r == EOK && !mp->fs.read_only) { + uint32_t bgid; + uint64_t free_blocks_count = 0; + uint32_t free_inodes_count = 0; + struct ext4_block_group_ref bg_ref; + + /* Update superblock's stats */ + for (bgid = 0;bgid < ext4_block_group_cnt(&mp->fs.sb);bgid++) { + r = ext4_fs_get_block_group_ref(&mp->fs, bgid, &bg_ref); + if (r != EOK) + goto Finish; + + free_blocks_count += + ext4_bg_get_free_blocks_count(bg_ref.block_group, + &mp->fs.sb); + free_inodes_count += + ext4_bg_get_free_inodes_count(bg_ref.block_group, + &mp->fs.sb); + + ext4_fs_put_block_group_ref(&bg_ref); + } + ext4_sb_set_free_blocks_cnt(&mp->fs.sb, free_blocks_count); + ext4_set32(&mp->fs.sb, free_inodes_count, free_inodes_count); + /* We don't need to save the superblock stats immediately. */ + } + +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +__unused +static int __ext4_trans_start(struct ext4_mountpoint *mp) +{ + int r = EOK; + + if (mp->fs.jbd_journal && !mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans; + trans = jbd_journal_new_trans(journal); + if (!trans) { + r = ENOMEM; + goto Finish; + } + mp->fs.curr_trans = trans; + } +Finish: + return r; +} + +__unused +static int __ext4_trans_stop(struct ext4_mountpoint *mp) +{ + int r = EOK; + + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + r = jbd_journal_commit_trans(journal, trans); + mp->fs.curr_trans = NULL; + } + return r; +} + +__unused +static void __ext4_trans_abort(struct ext4_mountpoint *mp) +{ + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + jbd_journal_free_trans(journal, trans, true); + mp->fs.curr_trans = NULL; + } +} + +int ext4_journal_start(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_start(mount_point); +#endif + return r; +} + +int ext4_journal_stop(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_stop(mount_point); +#endif + return r; +} + +int ext4_recover(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_recover(mount_point); +#endif + return r; +} + +static int ext4_trans_start(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_start(mp); +#endif + return r; +} + +static int ext4_trans_stop(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_stop(mp); +#endif + return r; +} + +static void ext4_trans_abort(struct ext4_mountpoint *mp __unused) +{ +#if CONFIG_JOURNALING_ENABLE + __ext4_trans_abort(mp); +#endif +} + + +int ext4_mount_point_stats(const char *mount_point, + struct ext4_mount_stats *stats) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + stats->inodes_count = ext4_get32(&mp->fs.sb, inodes_count); + stats->free_inodes_count = ext4_get32(&mp->fs.sb, free_inodes_count); + stats->blocks_count = ext4_sb_get_blocks_cnt(&mp->fs.sb); + stats->free_blocks_count = ext4_sb_get_free_blocks_cnt(&mp->fs.sb); + stats->block_size = ext4_sb_get_block_size(&mp->fs.sb); + + stats->block_group_count = ext4_block_group_cnt(&mp->fs.sb); + stats->blocks_per_group = ext4_get32(&mp->fs.sb, blocks_per_group); + stats->inodes_per_group = ext4_get32(&mp->fs.sb, inodes_per_group); + + memcpy(stats->volume_name, mp->fs.sb.volume_name, 16); + EXT4_MP_UNLOCK(mp); + + return EOK; +} + +int ext4_mount_setup_locks(const char *mount_point, + const struct ext4_lock *locks) +{ + uint32_t i; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(s_mp[i].name, mount_point)) { + mp = &s_mp[i]; + break; + } + } + if (!mp) + return ENOENT; + + mp->os_locks = locks; + return EOK; +} + +/********************************FILE OPERATIONS*****************************/ + +static int ext4_path_check(const char *path, bool *is_goal) +{ + int i; + + for (i = 0; i < EXT4_DIRECTORY_FILENAME_LEN; ++i) { + + if (path[i] == '/') { + *is_goal = false; + return i; + } + + if (path[i] == 0) { + *is_goal = true; + return i; + } + } + + return 0; +} + +static bool ext4_parse_flags(const char *flags, uint32_t *file_flags) +{ + if (!flags) + return false; + + if (!strcmp(flags, "r") || !strcmp(flags, "rb")) { + *file_flags = O_RDONLY; + return true; + } + + if (!strcmp(flags, "w") || !strcmp(flags, "wb")) { + *file_flags = O_WRONLY | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a") || !strcmp(flags, "ab")) { + *file_flags = O_WRONLY | O_CREAT | O_APPEND; + return true; + } + + if (!strcmp(flags, "r+") || !strcmp(flags, "rb+") || + !strcmp(flags, "r+b")) { + *file_flags = O_RDWR; + return true; + } + + if (!strcmp(flags, "w+") || !strcmp(flags, "wb+") || + !strcmp(flags, "w+b")) { + *file_flags = O_RDWR | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a+") || !strcmp(flags, "ab+") || + !strcmp(flags, "a+b")) { + *file_flags = O_RDWR | O_CREAT | O_APPEND; + return true; + } + + return false; +} + +static int ext4_trunc_inode(struct ext4_mountpoint *mp, + uint32_t index, uint64_t new_size) +{ + int r = EOK; + struct ext4_fs *const fs = &mp->fs; + struct ext4_inode_ref inode_ref; + uint64_t inode_size; + bool has_trans = mp->fs.jbd_journal && mp->fs.curr_trans; + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) + return r; + + inode_size = ext4_inode_get_size(&fs->sb, inode_ref.inode); + ext4_fs_put_inode_ref(&inode_ref); + if (has_trans) + ext4_trans_stop(mp); + + while (inode_size > new_size + CONFIG_MAX_TRUNCATE_SIZE) { + + inode_size -= CONFIG_MAX_TRUNCATE_SIZE; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + break; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } else + ext4_trans_stop(mp); + } + + if (inode_size > new_size) { + + inode_size = new_size; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + } + +Finish: + + if (has_trans) + ext4_trans_start(mp); + + return r; +} + +static int ext4_trunc_dir(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *dir) +{ + int r = EOK; + bool is_dir = ext4_inode_is_type(&mp->fs.sb, dir->inode, + EXT4_INODE_MODE_DIRECTORY); + uint32_t block_size = ext4_sb_get_block_size(&mp->fs.sb); + if (!is_dir) + return EINVAL; + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(dir, parent); + if (r != EOK) + return r; + + r = ext4_trunc_inode(mp, dir->index, + EXT4_DIR_DX_INIT_BCNT * block_size); + if (r != EOK) + return r; + } else +#endif + { + r = ext4_trunc_inode(mp, dir->index, block_size); + if (r != EOK) + return r; + } + + return ext4_fs_truncate_inode(dir, 0); +} + +/* + * NOTICE: if filetype is equal to EXT4_DIRENTRY_UNKNOWN, + * any filetype of the target dir entry will be accepted. + */ +static int ext4_generic_open2(ext4_file *f, const char *path, int flags, + int ftype, uint32_t *parent_inode, + uint32_t *name_off) +{ + bool is_goal = false; + uint32_t imode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + f->mp = 0; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + if (fs->read_only && flags & O_CREAT) + return EROFS; + + f->flags = flags; + + /*Skip mount point*/ + path += strlen(mp->name); + + if (name_off) + *name_off = strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + if (parent_inode) + *parent_inode = ref.index; + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + if (ftype == EXT4_DE_DIR || ftype == EXT4_DE_UNKNOWN) + if (is_goal) + break; + + r = ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + if (r != ENOENT) + break; + + if (!(f->flags & O_CREAT)) + break; + + /*O_CREAT allows create new entry*/ + struct ext4_inode_ref child_ref; + r = ext4_fs_alloc_inode(fs, &child_ref, + is_goal ? ftype : EXT4_DE_DIR); + + if (r != EOK) + break; + + ext4_fs_inode_blocks_init(fs, &child_ref); + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, &child_ref, path, len, false); + if (r != EOK) { + /*Fail. Free new inode.*/ + ext4_fs_free_inode(&child_ref); + /*We do not want to write new inode. + But block has to be released.*/ + child_ref.dirty = false; + ext4_fs_put_inode_ref(&child_ref); + break; + } + + ext4_fs_put_inode_ref(&child_ref); + continue; + } + + if (parent_inode) + *parent_inode = ref.index; + + next_inode = ext4_dir_en_get_inode(result.dentry); + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + imode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + imode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + /*If expected file error*/ + if (imode != EXT4_INODE_MODE_DIRECTORY && !is_goal) { + r = ENOENT; + break; + } + if (ftype != EXT4_DE_UNKNOWN) { + bool df = imode != ext4_fs_correspond_inode_mode(ftype); + if (df && is_goal) { + r = ENOENT; + break; + } + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + + if (name_off) + *name_off += len + 1; + } + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + if (is_goal) { + + if ((f->flags & O_TRUNC) && (imode == EXT4_INODE_MODE_FILE)) { + r = ext4_trunc_inode(mp, ref.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + } + + f->mp = mp; + f->fsize = ext4_inode_get_size(sb, ref.inode); + f->inode = ref.index; + f->fpos = 0; + + if (f->flags & O_APPEND) + f->fpos = f->fsize; + } + + return ext4_fs_put_inode_ref(&ref); +} + +/****************************************************************************/ + +static int ext4_generic_open(ext4_file *f, const char *path, const char *flags, + bool file_expect, uint32_t *parent_inode, + uint32_t *name_off) +{ + uint32_t iflags; + int filetype; + int r; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (ext4_parse_flags(flags, &iflags) == false) + return EINVAL; + + if (file_expect == true) + filetype = EXT4_DE_REG_FILE; + else + filetype = EXT4_DE_DIR; + + if (iflags & O_CREAT) + ext4_trans_start(mp); + + r = ext4_generic_open2(f, path, iflags, filetype, parent_inode, + name_off); + + if (iflags & O_CREAT) { + if (r == EOK) + ext4_trans_stop(mp); + else + ext4_trans_abort(mp); + } + + return r; +} + +static int ext4_create_hardlink(const char *path, + struct ext4_inode_ref *child_ref, bool rename) +{ + bool is_goal = false; + uint32_t inode_mode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + /*Skip mount point*/ + path += strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + r = is_goal ? EINVAL : ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + + if (r != ENOENT || !is_goal) + break; + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, child_ref, path, len, rename); + break; + } else if (r == EOK && is_goal) { + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + r = EEXIST; + break; + } + + next_inode = result.dentry->inode; + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + inode_mode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + inode_mode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + if (inode_mode != EXT4_INODE_MODE_DIRECTORY) { + r = is_goal ? EEXIST : ENOENT; + break; + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + }; + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + r = ext4_fs_put_inode_ref(&ref); + return r; +} + +static int ext4_remove_orig_reference(const char *path, uint32_t name_off, + struct ext4_inode_ref *parent_ref, + struct ext4_inode_ref *child_ref) +{ + bool is_goal; + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /* Remove entry from parent directory */ + r = ext4_dir_remove_entry(parent_ref, path, len); + if (r != EOK) + goto Finish; + + if (ext4_inode_is_type(&mp->fs.sb, child_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + ext4_fs_inode_links_count_dec(parent_ref); + parent_ref->dirty = true; + } +Finish: + return r; +} + +int ext4_flink(const char *path, const char *hardlink_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_mountpoint *target_mp = ext4_get_mount(hardlink_path); + struct ext4_inode_ref child_ref; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + /* Will that happen? Anyway return EINVAL for such case. */ + if (mp != target_mp) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + /* Creating hardlink for directory is not allowed. */ + if (ext4_inode_is_type(&mp->fs.sb, child_ref.inode, + EXT4_INODE_MODE_DIRECTORY)) { + r = EINVAL; + goto Finish; + } + + r = ext4_create_hardlink(hardlink_path, &child_ref, false); + +Finish: + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +int ext4_frename(const char *path, const char *new_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool parent_loaded = false, child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref child_ref, parent_ref; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent_ref); + if (r != EOK) + goto Finish; + + parent_loaded = true; + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + r = ext4_create_hardlink(new_path, &child_ref, true); + if (r != EOK) + goto Finish; + + r = ext4_remove_orig_reference(path, name_off, &parent_ref, &child_ref); + if (r != EOK) + goto Finish; + +Finish: + if (parent_loaded) + ext4_fs_put_inode_ref(&parent_ref); + + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/****************************************************************************/ + +int ext4_get_sblock(const char *mount_point, struct ext4_sblock **sb) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + *sb = &mp->fs.sb; + return EOK; +} + +int ext4_cache_write_back(const char *path, bool on) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int ret; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ret = ext4_block_cache_write_back(mp->fs.bdev, on); + EXT4_MP_UNLOCK(mp); + return ret; +} + +int ext4_cache_flush(const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int ret; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ret = ext4_block_cache_flush(mp->fs.bdev); + EXT4_MP_UNLOCK(mp); + return ret; +} + +int ext4_fremove(const char *path) +{ + ext4_file f; + uint32_t parent_inode; + uint32_t child_inode; + uint32_t name_off; + bool is_goal; + int r; + int len; + struct ext4_inode_ref child; + struct ext4_inode_ref parent; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*We have file to delete. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + /* We do not allow opening files here. */ + if (ext4_inode_type(&mp->fs.sb, child.inode) == + EXT4_INODE_MODE_DIRECTORY) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Link count will be zero, the inode should be freed. */ + if (ext4_inode_get_links_cnt(child.inode) == 1) { + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_trunc_inode(mp, child.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ext4_block_cache_write_back(mp->fs.bdev, 0); + } + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /*Unlink from parent*/ + r = ext4_unlink(mp, &parent, &child, path, len); + if (r != EOK) + goto Finish; + + /*Link count is zero, the inode should be freed. */ + if (!ext4_inode_get_links_cnt(child.inode)) { + ext4_inode_set_del_time(child.inode, -1L); + + r = ext4_fs_free_inode(&child); + if (r != EOK) + goto Finish; + } + +Finish: + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&parent); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fopen(ext4_file *file, const char *path, const char *flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open(file, path, flags, true, 0, 0); + ext4_block_cache_write_back(mp->fs.bdev, 0); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fopen2(ext4_file *file, const char *path, int flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + int filetype; + + if (!mp) + return ENOENT; + + filetype = EXT4_DE_REG_FILE; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + + if (flags & O_CREAT) + ext4_trans_start(mp); + + r = ext4_generic_open2(file, path, flags, filetype, NULL, NULL); + + if (flags & O_CREAT) { + if (r == EOK) + ext4_trans_stop(mp); + else + ext4_trans_abort(mp); + } + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_fclose(ext4_file *file) +{ + ext4_assert(file && file->mp); + + file->mp = 0; + file->flags = 0; + file->inode = 0; + file->fpos = file->fsize = 0; + + return EOK; +} + +static int ext4_ftruncate_no_lock(ext4_file *file, uint64_t size) +{ + struct ext4_inode_ref ref; + int r; + + + r = ext4_fs_get_inode_ref(&file->mp->fs, file->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(&file->mp->fs.sb, ref.inode); + if (file->fsize <= size) { + r = EOK; + goto Finish; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(file->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + r = ext4_trunc_inode(file->mp, ref.index, size); + if (r != EOK) + goto Finish; + + file->fsize = size; + if (file->fpos > size) + file->fpos = size; + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(file->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; + +} + +int ext4_ftruncate(ext4_file *f, uint64_t size) +{ + int r; + ext4_assert(f && f->mp); + + if (f->mp->fs.read_only) + return EROFS; + + if (f->flags & O_RDONLY) + return EPERM; + + EXT4_MP_LOCK(f->mp); + + ext4_trans_start(f->mp); + r = ext4_ftruncate_no_lock(f, size); + if (r != EOK) + ext4_trans_abort(f->mp); + else + ext4_trans_stop(f->mp); + + EXT4_MP_UNLOCK(f->mp); + return r; +} + +int ext4_fread(ext4_file *file, void *buf, size_t size, size_t *rcnt) +{ + uint32_t unalg; + uint32_t iblock_idx; + uint32_t iblock_last; + uint32_t block_size; + + ext4_fsblk_t fblock; + ext4_fsblk_t fblock_start; + uint32_t fblock_count; + + uint8_t *u8_buf = buf; + int r; + struct ext4_inode_ref ref; + + ext4_assert(file && file->mp); + + if (file->flags & O_WRONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(file->mp); + + struct ext4_fs *const fs = &file->mp->fs; + struct ext4_sblock *const sb = &file->mp->fs.sb; + + if (rcnt) + *rcnt = 0; + + r = ext4_fs_get_inode_ref(fs, file->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(sb, ref.inode); + + block_size = ext4_sb_get_block_size(sb); + size = ((uint64_t)size > (file->fsize - file->fpos)) + ? ((size_t)(file->fsize - file->fpos)) : size; + + iblock_idx = (uint32_t)((file->fpos) / block_size); + iblock_last = (uint32_t)((file->fpos + size) / block_size); + unalg = (file->fpos) % block_size; + + /*If the size of symlink is smaller than 60 bytes*/ + bool softlink; + softlink = ext4_inode_is_type(sb, ref.inode, EXT4_INODE_MODE_SOFTLINK); + if (softlink && file->fsize < sizeof(ref.inode->blocks) + && !ext4_inode_get_blocks_count(sb, ref.inode)) { + + char *content = (char *)ref.inode->blocks; + if (file->fpos < file->fsize) { + size_t len = size; + if (unalg + size > (uint32_t)file->fsize) + len = (uint32_t)file->fsize - unalg; + memcpy(buf, content + unalg, len); + if (rcnt) + *rcnt = len; + + } + + r = EOK; + goto Finish; + } + + if (unalg) { + size_t len = size; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + /* Do we get an unwritten range? */ + if (fblock != 0) { + uint64_t off = fblock * block_size + unalg; + r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + } else { + /* Yes, we do. */ + memset(u8_buf, 0, len); + } + + u8_buf += len; + size -= len; + file->fpos += len; + + if (rcnt) + *rcnt += len; + + iblock_idx++; + } + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + while (iblock_idx < iblock_last) { + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, + &fblock, true); + if (r != EOK) + goto Finish; + + iblock_idx++; + + if (!fblock_start) + fblock_start = fblock; + + if ((fblock_start + fblock_count) != fblock) + break; + + fblock_count++; + } + + r = ext4_blocks_get_direct(file->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + goto Finish; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + file->fpos += block_size * fblock_count; + + if (rcnt) + *rcnt += block_size * fblock_count; + + fblock_start = fblock; + fblock_count = 1; + } + + if (size) { + uint64_t off; + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + off = fblock * block_size; + r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + file->fpos += size; + + if (rcnt) + *rcnt += size; + } + +Finish: + ext4_fs_put_inode_ref(&ref); + EXT4_MP_UNLOCK(file->mp); + return r; +} + +int ext4_fwrite(ext4_file *file, const void *buf, size_t size, size_t *wcnt) +{ + uint32_t unalg; + uint32_t iblk_idx; + uint32_t iblock_last; + uint32_t ifile_blocks; + uint32_t block_size; + + uint32_t fblock_count; + ext4_fsblk_t fblk; + ext4_fsblk_t fblock_start; + + struct ext4_inode_ref ref; + const uint8_t *u8_buf = buf; + int r, rr = EOK; + + ext4_assert(file && file->mp); + + if (file->mp->fs.read_only) + return EROFS; + + if (file->flags & O_RDONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(file->mp); + ext4_trans_start(file->mp); + + struct ext4_fs *const fs = &file->mp->fs; + struct ext4_sblock *const sb = &file->mp->fs.sb; + + if (wcnt) + *wcnt = 0; + + r = ext4_fs_get_inode_ref(fs, file->inode, &ref); + if (r != EOK) { + ext4_trans_abort(file->mp); + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(sb, ref.inode); + block_size = ext4_sb_get_block_size(sb); + + iblock_last = (uint32_t)((file->fpos + size) / block_size); + iblk_idx = (uint32_t)(file->fpos / block_size); + ifile_blocks = (uint32_t)((file->fsize + block_size - 1) / block_size); + + unalg = (file->fpos) % block_size; + + if (unalg) { + size_t len = size; + uint64_t off; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + + off = fblk * block_size + unalg; + r = ext4_block_writebytes(file->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + u8_buf += len; + size -= len; + file->fpos += len; + + if (wcnt) + *wcnt += len; + + iblk_idx++; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(file->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + + while (iblk_idx < iblock_last) { + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, + &fblk); + if (r != EOK) + goto Finish; + } else { + rr = ext4_fs_append_inode_dblk(&ref, &fblk, + &iblk_idx); + if (rr != EOK) { + /* Unable to append more blocks. But + * some block might be allocated already + * */ + break; + } + } + + iblk_idx++; + + if (!fblock_start) { + fblock_start = fblk; + } + + if ((fblock_start + fblock_count) != fblk) + break; + + fblock_count++; + } + + r = ext4_blocks_set_direct(file->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + break; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + file->fpos += block_size * fblock_count; + + if (wcnt) + *wcnt += block_size * fblock_count; + + fblock_start = fblk; + fblock_count = 1; + + if (rr != EOK) { + /*ext4_fs_append_inode_block has failed and no + * more blocks might be written. But node size + * should be updated.*/ + r = rr; + goto out_fsize; + } + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(file->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + if (size) { + uint64_t off; + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + } else { + r = ext4_fs_append_inode_dblk(&ref, &fblk, &iblk_idx); + if (r != EOK) + /*Node size sholud be updated.*/ + goto out_fsize; + } + + off = fblk * block_size; + r = ext4_block_writebytes(file->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + file->fpos += size; + + if (wcnt) + *wcnt += size; + } + +out_fsize: + if (file->fpos > file->fsize) { + file->fsize = file->fpos; + ext4_inode_set_size(ref.inode, file->fsize); + ref.dirty = true; + } + +Finish: + r = ext4_fs_put_inode_ref(&ref); + + if (r != EOK) + ext4_trans_abort(file->mp); + else + ext4_trans_stop(file->mp); + + EXT4_MP_UNLOCK(file->mp); + return r; +} + +int ext4_fseek(ext4_file *file, int64_t offset, uint32_t origin) +{ + switch (origin) { + case SEEK_SET: + if (offset < 0 || (uint64_t)offset > file->fsize) + return EINVAL; + + file->fpos = offset; + return EOK; + case SEEK_CUR: + if ((offset < 0 && (uint64_t)(-offset) > file->fpos) || + (offset > 0 && + (uint64_t)offset > (file->fsize - file->fpos))) + return EINVAL; + + file->fpos += offset; + return EOK; + case SEEK_END: + if (offset < 0 || (uint64_t)offset > file->fsize) + return EINVAL; + + file->fpos = file->fsize - offset; + return EOK; + } + return EINVAL; +} + +uint64_t ext4_ftell(ext4_file *file) +{ + return file->fpos; +} + +uint64_t ext4_fsize(ext4_file *file) +{ + return file->fsize; +} + + +static int ext4_trans_get_inode_ref(const char *path, + struct ext4_mountpoint *mp, + struct ext4_inode_ref *inode_ref) +{ + int r; + ext4_file f; + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + return r; + + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + return r; + } + + return r; +} + +static int ext4_trans_put_inode_ref(struct ext4_mountpoint *mp, + struct ext4_inode_ref *inode_ref) +{ + int r; + + r = ext4_fs_put_inode_ref(inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + return r; +} + + +int ext4_raw_inode_fill(const char *path, uint32_t *ret_ino, + struct ext4_inode *inode) +{ + int r; + ext4_file f; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + if (ret_ino) + *ret_ino = f.inode; + + memcpy(inode, inode_ref.inode, sizeof(struct ext4_inode)); + ext4_fs_put_inode_ref(&inode_ref); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_inode_exist(const char *path, int type) +{ + int r; + ext4_file f; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, type, NULL, NULL); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mode_set(const char *path, uint32_t mode) +{ + int r; + uint32_t orig_mode; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + orig_mode = ext4_inode_get_mode(&mp->fs.sb, inode_ref.inode); + orig_mode &= ~0xFFF; + orig_mode |= mode & 0xFFF; + ext4_inode_set_mode(&mp->fs.sb, inode_ref.inode, orig_mode); + + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_owner_set(const char *path, uint32_t uid, uint32_t gid) +{ + int r; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_uid(inode_ref.inode, uid); + ext4_inode_set_gid(inode_ref.inode, gid); + + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mode_get(const char *path, uint32_t *mode) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *mode = ext4_inode_get_mode(&mp->fs.sb, inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_owner_get(const char *path, uint32_t *uid, uint32_t *gid) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *uid = ext4_inode_get_uid(inode_ref.inode); + *gid = ext4_inode_get_gid(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_atime_set(const char *path, uint32_t atime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_access_time(inode_ref.inode, atime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mtime_set(const char *path, uint32_t mtime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_modif_time(inode_ref.inode, mtime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_ctime_set(const char *path, uint32_t ctime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_change_inode_time(inode_ref.inode, ctime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_atime_get(const char *path, uint32_t *atime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *atime = ext4_inode_get_access_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mtime_get(const char *path, uint32_t *mtime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *mtime = ext4_inode_get_modif_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_ctime_get(const char *path, uint32_t *ctime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *ctime = ext4_inode_get_change_inode_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +static int ext4_fsymlink_set(ext4_file *f, const void *buf, uint32_t size) +{ + struct ext4_inode_ref ref; + uint32_t sblock; + ext4_fsblk_t fblock; + uint32_t block_size; + int r; + + ext4_assert(f && f->mp); + + if (!size) + return EOK; + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) + return r; + + /*Sync file size*/ + block_size = ext4_sb_get_block_size(&f->mp->fs.sb); + if (size > block_size) { + r = EINVAL; + goto Finish; + } + r = ext4_ftruncate_no_lock(f, 0); + if (r != EOK) + goto Finish; + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(f->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + /*If the size of symlink is smaller than 60 bytes*/ + if (size < sizeof(ref.inode->blocks)) { + memset(ref.inode->blocks, 0, sizeof(ref.inode->blocks)); + memcpy(ref.inode->blocks, buf, size); + ext4_inode_clear_flag(ref.inode, EXT4_INODE_FLAG_EXTENTS); + } else { + uint64_t off; + ext4_fs_inode_blocks_init(&f->mp->fs, &ref); + r = ext4_fs_append_inode_dblk(&ref, &fblock, &sblock); + if (r != EOK) + goto Finish; + + off = fblock * block_size; + r = ext4_block_writebytes(f->mp->fs.bdev, off, buf, size); + if (r != EOK) + goto Finish; + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(f->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + ext4_inode_set_size(ref.inode, size); + ref.dirty = true; + + f->fsize = size; + if (f->fpos > size) + f->fpos = size; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; +} + +int ext4_fsymlink(const char *target, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR | O_CREAT, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fsymlink_set(&f, target, strlen(target)); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_readlink(const char *path, char *buf, size_t bufsize, size_t *rcnt) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + if (!buf) + return EINVAL; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open2(&f, path, O_RDONLY, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fread(&f, buf, bufsize, rcnt); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +static int ext4_mknod_set(ext4_file *f, uint32_t dev) +{ + struct ext4_inode_ref ref; + int r; + + ext4_assert(f && f->mp); + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) + return r; + + ext4_inode_set_dev(ref.inode, dev); + + ext4_inode_set_size(ref.inode, 0); + ref.dirty = true; + + f->fsize = 0; + f->fpos = 0; + + r = ext4_fs_put_inode_ref(&ref); + return r; +} + +int ext4_mknod(const char *path, int filetype, uint32_t dev) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + /* + * The filetype shouldn't be normal file, directory or + * unknown. + */ + if (filetype == EXT4_DE_UNKNOWN || + filetype == EXT4_DE_REG_FILE || + filetype == EXT4_DE_DIR || + filetype == EXT4_DE_SYMLINK) + return EINVAL; + + /* + * Nor should it be any bogus value. + */ + if (filetype != EXT4_DE_CHRDEV && + filetype != EXT4_DE_BLKDEV && + filetype != EXT4_DE_FIFO && + filetype != EXT4_DE_SOCK) + return EINVAL; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR | O_CREAT, filetype, NULL, NULL); + if (r == EOK) { + if (filetype == EXT4_DE_CHRDEV || + filetype == EXT4_DE_BLKDEV) + r = ext4_mknod_set(&f, dev); + } else { + goto Finish; + } + + ext4_fclose(&f); + +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_setxattr(const char *path, const char *name, size_t name_len, + const void *data, size_t data_size) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_set(&inode_ref, name_index, dissected_name, + dissected_len, data, data_size); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_getxattr(const char *path, const char *name, size_t name_len, + void *buf, size_t buf_size, size_t *data_size) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_get(&inode_ref, name_index, dissected_name, + dissected_len, buf, buf_size, data_size); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + size_t list_len, list_size = 0; + struct ext4_inode_ref inode_ref; + struct ext4_xattr_list_entry *xattr_list = NULL, + *entry = NULL; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_list(&inode_ref, NULL, &list_len); + if (r == EOK && list_len) { + xattr_list = ext4_malloc(list_len); + if (!xattr_list) { + ext4_fs_put_inode_ref(&inode_ref); + r = ENOMEM; + goto Finish; + } + entry = xattr_list; + r = ext4_xattr_list(&inode_ref, entry, &list_len); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + for (;entry;entry = entry->next) { + size_t prefix_len; + const char *prefix = + ext4_get_xattr_name_prefix(entry->name_index, + &prefix_len); + if (size) { + if (prefix_len + entry->name_len + 1 > size) { + ext4_fs_put_inode_ref(&inode_ref); + r = ERANGE; + goto Finish; + } + } + + if (list && size) { + memcpy(list, prefix, prefix_len); + list += prefix_len; + memcpy(list, entry->name, + entry->name_len); + list[entry->name_len] = 0; + list += entry->name_len + 1; + + size -= prefix_len + entry->name_len + 1; + } + + list_size += prefix_len + entry->name_len + 1; + } + if (ret_size) + *ret_size = list_size; + + } + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + if (xattr_list) + ext4_free(xattr_list); + + return r; + +} + +int ext4_removexattr(const char *path, const char *name, size_t name_len) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_LOCK(mp); + return r; + } + + inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_remove(&inode_ref, name_index, dissected_name, + dissected_len); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/*********************************DIRECTORY OPERATION************************/ + +int ext4_dir_rm(const char *path) +{ + int r; + int len; + ext4_file f; + + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref act; + struct ext4_inode_ref child; + struct ext4_dir_iter it; + + uint32_t name_off; + uint32_t inode_up; + uint32_t inode_current; + uint32_t depth = 1; + + bool has_children; + bool is_goal; + bool dir_end; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + struct ext4_fs *const fs = &mp->fs; + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, &inode_up, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + path += name_off; + len = ext4_path_check(path, &is_goal); + inode_current = f.inode; + + ext4_block_cache_write_back(mp->fs.bdev, 1); + + do { + + uint64_t act_curr_pos = 0; + has_children = false; + dir_end = false; + + while (r == EOK && !has_children && !dir_end) { + + /*Load directory node.*/ + r = ext4_fs_get_inode_ref(fs, inode_current, &act); + if (r != EOK) { + break; + } + + /*Initialize iterator.*/ + r = ext4_dir_iterator_init(&it, &act, act_curr_pos); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + break; + } + + if (!it.curr) { + dir_end = true; + goto End; + } + + ext4_trans_start(mp); + + /*Get up directory inode when ".." entry*/ + if ((it.curr->name_len == 2) && + ext4_is_dots(it.curr->name, it.curr->name_len)) { + inode_up = ext4_dir_en_get_inode(it.curr); + } + + /*If directory or file entry, but not "." ".." entry*/ + if (!ext4_is_dots(it.curr->name, it.curr->name_len)) { + + /*Get child inode reference do unlink + * directory/file.*/ + uint32_t cinode; + uint32_t inode_type; + cinode = ext4_dir_en_get_inode(it.curr); + r = ext4_fs_get_inode_ref(fs, cinode, &child); + if (r != EOK) + goto End; + + /*If directory with no leaf children*/ + r = ext4_has_children(&has_children, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + if (has_children) { + /*Has directory children. Go into this + * directory.*/ + inode_up = inode_current; + inode_current = cinode; + depth++; + ext4_fs_put_inode_ref(&child); + goto End; + } + inode_type = ext4_inode_type(&mp->fs.sb, + child.inode); + + /* Truncate */ + if (inode_type != EXT4_INODE_MODE_DIRECTORY) + r = ext4_trunc_inode(mp, child.index, 0); + else + r = ext4_trunc_dir(mp, &act, &child); + + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + /*No children in child directory or file. Just + * unlink.*/ + r = ext4_unlink(f.mp, &act, &child, + (char *)it.curr->name, + it.curr->name_len); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + ext4_inode_set_del_time(child.inode, -1L); + ext4_inode_set_links_cnt(child.inode, 0); + child.dirty = true; + + r = ext4_fs_free_inode(&child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + r = ext4_fs_put_inode_ref(&child); + if (r != EOK) + goto End; + + } + + r = ext4_dir_iterator_next(&it); + if (r != EOK) + goto End; + + act_curr_pos = it.curr_off; +End: + ext4_dir_iterator_fini(&it); + if (r == EOK) + r = ext4_fs_put_inode_ref(&act); + else + ext4_fs_put_inode_ref(&act); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + if (dir_end) { + /*Directory iterator reached last entry*/ + depth--; + if (depth) + inode_current = inode_up; + + } + + if (r != EOK) + break; + + } while (depth); + + /*Last unlink*/ + if (r == EOK && !depth) { + /*Load parent.*/ + struct ext4_inode_ref parent; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_up, + &parent); + if (r != EOK) + goto Finish; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_current, + &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + ext4_trans_start(mp); + + /* In this place all directories should be + * unlinked. + * Last unlink from root of current directory*/ + r = ext4_unlink(f.mp, &parent, &act, + (char *)path, len); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + if (ext4_inode_get_links_cnt(act.inode) == 2) { + ext4_inode_set_del_time(act.inode, -1L); + ext4_inode_set_links_cnt(act.inode, 0); + act.dirty = true; + /*Truncate*/ + r = ext4_trunc_dir(mp, &parent, &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + r = ext4_fs_free_inode(&act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + } + + r = ext4_fs_put_inode_ref(&parent); + if (r != EOK) + goto Finish; + + r = ext4_fs_put_inode_ref(&act); + Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_dir_mv(const char *path, const char *new_path) +{ + return ext4_frename(path, new_path); +} + +int ext4_dir_mk(const char *path) +{ + int r; + ext4_file f; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, 0, 0); + if (r == EOK) + goto Finish; + + /*Create new directory.*/ + r = ext4_generic_open(&f, path, "w", false, 0, 0); + +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_open(ext4_dir *dir, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open(&dir->f, path, "r", false, 0, 0); + dir->next_off = 0; + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_close(ext4_dir *dir) +{ + return ext4_fclose(&dir->f); +} + +const ext4_direntry *ext4_dir_entry_next(ext4_dir *dir) +{ +#define EXT4_DIR_ENTRY_OFFSET_TERM (uint64_t)(-1) + + int r; + uint16_t name_length; + ext4_direntry *de = 0; + struct ext4_inode_ref dir_inode; + struct ext4_dir_iter it; + + EXT4_MP_LOCK(dir->f.mp); + + if (dir->next_off == EXT4_DIR_ENTRY_OFFSET_TERM) { + EXT4_MP_UNLOCK(dir->f.mp); + return 0; + } + + r = ext4_fs_get_inode_ref(&dir->f.mp->fs, dir->f.inode, &dir_inode); + if (r != EOK) { + goto Finish; + } + + r = ext4_dir_iterator_init(&it, &dir_inode, dir->next_off); + if (r != EOK) { + ext4_fs_put_inode_ref(&dir_inode); + goto Finish; + } + + memset(&dir->de.name, 0, sizeof(dir->de.name)); + name_length = ext4_dir_en_get_name_len(&dir->f.mp->fs.sb, + it.curr); + memcpy(&dir->de.name, it.curr->name, name_length); + + /* Directly copying the content isn't safe for Big-endian targets*/ + dir->de.inode = ext4_dir_en_get_inode(it.curr); + dir->de.entry_length = ext4_dir_en_get_entry_len(it.curr); + dir->de.name_length = name_length; + dir->de.inode_type = ext4_dir_en_get_inode_type(&dir->f.mp->fs.sb, + it.curr); + + de = &dir->de; + + ext4_dir_iterator_next(&it); + + dir->next_off = it.curr ? it.curr_off : EXT4_DIR_ENTRY_OFFSET_TERM; + + ext4_dir_iterator_fini(&it); + ext4_fs_put_inode_ref(&dir_inode); + +Finish: + EXT4_MP_UNLOCK(dir->f.mp); + return de; +} + +void ext4_dir_entry_rewind(ext4_dir *dir) +{ + dir->next_off = 0; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_balloc.c b/clib/lib/lwext4/src/ext4_balloc.c new file mode 100644 index 0000000..7984e7c --- /dev/null +++ b/clib/lib/lwext4/src/ext4_balloc.c @@ -0,0 +1,669 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_balloc.c + * @brief Physical block allocator. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**@brief Compute number of block group from block address. + * @param s superblock pointer. + * @param baddr Absolute address of block. + * @return Block group index + */ +uint32_t ext4_balloc_get_bgid_of_block(struct ext4_sblock *s, + uint64_t baddr) +{ + if (ext4_get32(s, first_data_block) && baddr) + baddr--; + + return (uint32_t)(baddr / ext4_get32(s, blocks_per_group)); +} + +/**@brief Compute the starting block address of a block group + * @param s superblock pointer. + * @param bgid block group index + * @return Block address + */ +uint64_t ext4_balloc_get_block_of_bgid(struct ext4_sblock *s, + uint32_t bgid) +{ + uint64_t baddr = 0; + if (ext4_get32(s, first_data_block)) + baddr++; + + baddr += bgid * ext4_get32(s, blocks_per_group); + return baddr; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_balloc_bitmap_csum(struct ext4_sblock *sb, + void *bitmap) +{ + uint32_t checksum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t blocks_per_group = ext4_get32(sb, blocks_per_group); + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bitmap, blocks_per_group / 8); + } + return checksum; +} +#else +#define ext4_balloc_bitmap_csum(...) 0 +#endif + +void ext4_balloc_set_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->block_bitmap_csum_lo = lo_checksum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->block_bitmap_csum_hi = hi_checksum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_balloc_verify_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->block_bitmap_csum_lo != lo_checksum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->block_bitmap_csum_hi != hi_checksum) + return false; + + return true; +} +#else +#define ext4_balloc_verify_bitmap_csum(...) true +#endif + +int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr) +{ + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bg_id, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block bitmap_block; + + rc = ext4_trans_block_get(fs->bdev, &bitmap_block, bitmap_block_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, bitmap_block.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Modify bitmap */ + ext4_bmap_bit_clr(bitmap_block.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg, bitmap_block.data); + ext4_trans_set_block_dirty(bitmap_block.buf); + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &bitmap_block); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks++; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks++; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + + bg_ref.dirty = true; + + rc = ext4_trans_try_revoke_block(fs->bdev, baddr); + if (rc != EOK) { + bg_ref.dirty = false; + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + ext4_bcache_invalidate_lba(fs->bdev->bc, baddr, 1); + /* Release block group reference */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + + return rc; +} + +int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t first, uint32_t count) +{ + int rc = EOK; + uint32_t blk_cnt = count; + ext4_fsblk_t start_block = first; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t bg_first = ext4_balloc_get_bgid_of_block(sb, first); + + /* Compute indexes */ + uint32_t bg_last = ext4_balloc_get_bgid_of_block(sb, first + count - 1); + + if (!ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG)) { + /*It is not possible without flex_bg that blocks are continuous + * and and last block belongs to other bg.*/ + if (bg_last != bg_first) { + ext4_dbg(DEBUG_BALLOC, DBG_WARN "FLEX_BG: disabled & " + "bg_last: %"PRIu32" bg_first: %"PRIu32"\n", + bg_last, bg_first); + } + } + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + while (bg_first <= bg_last) { + + rc = ext4_fs_get_block_group_ref(fs, bg_first, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + uint32_t idx_in_bg_first; + idx_in_bg_first = ext4_fs_addr_to_idx_bg(sb, first); + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_blk = ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block blk; + rc = ext4_trans_block_get(fs->bdev, &blk, bitmap_blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, blk.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + uint32_t free_cnt; + free_cnt = ext4_sb_get_block_size(sb) * 8 - idx_in_bg_first; + + /*If last block, free only count blocks*/ + free_cnt = count > free_cnt ? free_cnt : count; + + /* Modify bitmap */ + ext4_bmap_bits_free(blk.data, idx_in_bg_first, free_cnt); + ext4_balloc_set_bitmap_csum(sb, bg, blk.data); + ext4_trans_set_block_dirty(blk.buf); + + count -= free_cnt; + first += free_cnt; + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks += free_cnt; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks; + ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= free_cnt * (block_size / EXT4_INODE_BLOCK_SIZE); + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks += free_cnt; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + bg_ref.dirty = true; + + /* Release block group reference */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + break; + + bg_first++; + } + + uint32_t i; + for (i = 0;i < blk_cnt;i++) { + rc = ext4_trans_try_revoke_block(fs->bdev, start_block + i); + if (rc != EOK) + return rc; + + } + + ext4_bcache_invalidate_lba(fs->bdev->bc, start_block, blk_cnt); + /*All blocks should be released*/ + ext4_assert(count == 0); + + return rc; +} + +int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *fblock) +{ + ext4_fsblk_t alloc = 0; + ext4_fsblk_t bmp_blk_adr; + uint32_t rel_blk_idx = 0; + uint64_t free_blocks; + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Load block group number for goal and relative index */ + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, goal); + uint32_t idx_in_bg = ext4_fs_addr_to_idx_bg(sb, goal); + + struct ext4_block b; + struct ext4_block_group_ref bg_ref; + + /* Load block group reference */ + r = ext4_fs_get_block_group_ref(inode_ref->fs, bg_id, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + free_blocks = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto goal_failed; + } + + /* Compute indexes */ + ext4_fsblk_t first_in_bg; + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref.index); + + uint32_t first_in_bg_index; + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if goal is free */ + if (ext4_bmap_is_bit_clr(b.data, idx_in_bg)) { + ext4_bmap_bit_set(b.data, idx_in_bg); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, + b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, idx_in_bg, bg_id); + goto success; + } + + uint32_t blk_in_bg = ext4_blocks_in_group_cnt(sb, bg_id); + + uint32_t end_idx = (idx_in_bg + 63) & ~63; + if (end_idx > blk_in_bg) + end_idx = blk_in_bg; + + /* Try to find free block near to goal */ + uint32_t tmp_idx; + for (tmp_idx = idx_in_bg + 1; tmp_idx < end_idx; ++tmp_idx) { + if (ext4_bmap_is_bit_clr(b.data, tmp_idx)) { + ext4_bmap_bit_set(b.data, tmp_idx); + + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, tmp_idx, bg_id); + goto success; + } + } + + /* Find free bit in bitmap */ + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bg_id); + goto success; + } + + /* No free block found yet */ + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + +goal_failed: + + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) + return r; + + /* Try other block groups */ + uint32_t block_group_count = ext4_block_group_cnt(sb); + uint32_t bgid = (bg_id + 1) % block_group_count; + uint32_t count = block_group_count; + + while (count > 0) { + r = ext4_fs_get_block_group_ref(inode_ref->fs, bgid, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto next_group; + } + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg, sb); + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Compute indexes */ + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bgid); + idx_in_bg = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + blk_in_bg = ext4_blocks_in_group_cnt(sb, bgid); + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, + &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bgid); + goto success; + } + + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + next_group: + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) { + return r; + } + + /* Goto next group */ + bgid = (bgid + 1) % block_group_count; + count--; + } + + return ENOSPC; + +success: + /* Empty command - because of syntax */ + ; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks (different block size!) count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + r = ext4_fs_put_block_group_ref(&bg_ref); + + *fblock = alloc; + return r; +} + +int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr, bool *free) +{ + int rc; + + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t block_group = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_addr; + bmp_blk_addr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg_ref.block_group, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if block is free */ + *free = ext4_bmap_is_bit_clr(b.data, index_in_group); + + /* Allocate block if possible */ + if (*free) { + ext4_bmap_bit_set(b.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + } + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If block is not free, return */ + if (!(*free)) + goto terminate; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + +terminate: + return ext4_fs_put_block_group_ref(&bg_ref); +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_bcache.c b/clib/lib/lwext4/src/ext4_bcache.c new file mode 100644 index 0000000..9d3c7fb --- /dev/null +++ b/clib/lib/lwext4/src/ext4_bcache.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bcache.c + * @brief Block cache allocator. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static int ext4_bcache_lba_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +static int ext4_bcache_lru_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lru_id > b->lru_id) + return 1; + else if (a->lru_id < b->lru_id) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(ext4_buf_lba, ext4_buf, lba_node, + ext4_bcache_lba_compare, static inline) +RB_GENERATE_INTERNAL(ext4_buf_lru, ext4_buf, lru_node, + ext4_bcache_lru_compare, static inline) + +int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt, + uint32_t itemsize) +{ + ext4_assert(bc && cnt && itemsize); + + memset(bc, 0, sizeof(struct ext4_bcache)); + + bc->cnt = cnt; + bc->itemsize = itemsize; + bc->ref_blocks = 0; + bc->max_ref_blocks = 0; + + return EOK; +} + +void ext4_bcache_cleanup(struct ext4_bcache *bc) +{ + struct ext4_buf *buf, *tmp; + RB_FOREACH_SAFE(buf, ext4_buf_lba, &bc->lba_root, tmp) { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_drop_buf(bc, buf); + } +} + +int ext4_bcache_fini_dynamic(struct ext4_bcache *bc) +{ + memset(bc, 0, sizeof(struct ext4_bcache)); + return EOK; +} + +/**@brief: + * + * This is ext4_bcache, the module handling basic buffer-cache stuff. + * + * Buffers in a bcache are sorted by their LBA and stored in a + * RB-Tree(lba_root). + * + * Bcache also maintains another RB-Tree(lru_root) right now, where + * buffers are sorted by their LRU id. + * + * A singly-linked list is used to track those dirty buffers which are + * ready to be flushed. (Those buffers which are dirty but also referenced + * are not considered ready to be flushed.) + * + * When a buffer is not referenced, it will be stored in both lba_root + * and lru_root, while it will only be stored in lba_root when it is + * referenced. + */ + +static struct ext4_buf * +ext4_buf_alloc(struct ext4_bcache *bc, uint64_t lba) +{ + void *data; + struct ext4_buf *buf; + data = ext4_malloc(bc->itemsize); + if (!data) + return NULL; + + buf = ext4_calloc(1, sizeof(struct ext4_buf)); + if (!buf) { + ext4_free(data); + return NULL; + } + + buf->lba = lba; + buf->data = data; + buf->bc = bc; + return buf; +} + +static void ext4_buf_free(struct ext4_buf *buf) +{ + ext4_free(buf->data); + ext4_free(buf); +} + +static struct ext4_buf * +ext4_buf_lookup(struct ext4_bcache *bc, uint64_t lba) +{ + struct ext4_buf tmp = { + .lba = lba + }; + + return RB_FIND(ext4_buf_lba, &bc->lba_root, &tmp); +} + +struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc) +{ + return RB_MIN(ext4_buf_lru, &bc->lru_root); +} + +void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf) +{ + /* Warn on dropping any referenced buffers.*/ + if (buf->refctr) { + ext4_dbg(DEBUG_BCACHE, DBG_WARN "Buffer is still referenced. " + "lba: %" PRIu64 ", refctr: %" PRIu32 "\n", + buf->lba, buf->refctr); + } else + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + + RB_REMOVE(ext4_buf_lba, &bc->lba_root, buf); + + /*Forcibly drop dirty buffer.*/ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + ext4_buf_free(buf); + bc->ref_blocks--; +} + +void ext4_bcache_invalidate_buf(struct ext4_bcache *bc, + struct ext4_buf *buf) +{ + buf->end_write = NULL; + buf->end_write_arg = NULL; + + /* Clear both dirty and up-to-date flags. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + ext4_bcache_clear_dirty(buf); +} + +void ext4_bcache_invalidate_lba(struct ext4_bcache *bc, + uint64_t from, + uint32_t cnt) +{ + uint64_t end = from + cnt - 1; + struct ext4_buf *tmp = ext4_buf_lookup(bc, from), *buf; + RB_FOREACH_FROM(buf, ext4_buf_lba, tmp) { + if (buf->lba > end) + break; + + ext4_bcache_invalidate_buf(bc, buf); + } +} + +struct ext4_buf * +ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b, + uint64_t lba) +{ + struct ext4_buf *buf = ext4_buf_lookup(bc, lba); + if (buf) { + /* If buffer is not referenced. */ + if (!buf->refctr) { + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + } + + ext4_bcache_inc_ref(buf); + + b->lb_id = lba; + b->buf = buf; + b->data = buf->data; + } + return buf; +} + +int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b, + bool *is_new) +{ + /* Try to search the buffer with exaxt LBA. */ + struct ext4_buf *buf = ext4_bcache_find_get(bc, b, b->lb_id); + if (buf) { + *is_new = false; + return EOK; + } + + /* We need to allocate one buffer.*/ + buf = ext4_buf_alloc(bc, b->lb_id); + if (!buf) + return ENOMEM; + + RB_INSERT(ext4_buf_lba, &bc->lba_root, buf); + /* One more buffer in bcache now. :-) */ + bc->ref_blocks++; + + /*Calc ref blocks max depth*/ + if (bc->max_ref_blocks < bc->ref_blocks) + bc->max_ref_blocks = bc->ref_blocks; + + + ext4_bcache_inc_ref(buf); + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + + b->buf = buf; + b->data = buf->data; + + *is_new = true; + return EOK; +} + +int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b) +{ + struct ext4_buf *buf = b->buf; + + ext4_assert(bc && b); + + /*Check if valid.*/ + ext4_assert(b->lb_id); + + /*Block should have a valid pointer to ext4_buf.*/ + ext4_assert(buf); + + /*Check if someone don't try free unreferenced block cache.*/ + ext4_assert(buf->refctr); + + /*Just decrease reference counter*/ + ext4_bcache_dec_ref(buf); + + /* We are the last one touching this buffer, do the cleanups. */ + if (!buf->refctr) { + RB_INSERT(ext4_buf_lru, &bc->lru_root, buf); + /* This buffer is ready to be flushed. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + if (bc->bdev->cache_write_back && + !ext4_bcache_test_flag(buf, BC_FLUSH) && + !ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_insert_dirty_node(bc, buf); + else { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_clear_flag(buf, BC_FLUSH); + } + } + + /* The buffer is invalidated...drop it. */ + if (!ext4_bcache_test_flag(buf, BC_UPTODATE) || + ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_drop_buf(bc, buf); + + } + + b->lb_id = 0; + b->data = 0; + + return EOK; +} + +bool ext4_bcache_is_full(struct ext4_bcache *bc) +{ + return (bc->cnt <= bc->ref_blocks); +} + + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_bitmap.c b/clib/lib/lwext4/src/ext4_bitmap.c new file mode 100644 index 0000000..43b6431 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_bitmap.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bitmap.c + * @brief Block/inode bitmap allocator. + */ + +#include +#include +#include +#include +#include + +#include + +void ext4_bmap_bits_free(uint8_t *bmap, uint32_t sbit, uint32_t bcnt) +{ + uint32_t i = sbit; + + while (i & 7) { + + if (!bcnt) + return; + + ext4_bmap_bit_clr(bmap, i); + + bcnt--; + i++; + } + sbit = i; + bmap += (sbit >> 3); + +#if CONFIG_UNALIGNED_ACCESS + while (bcnt >= 32) { + *(uint32_t *)bmap = 0; + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + *(uint16_t *)bmap = 0; + bmap += 2; + bcnt -= 16; + sbit += 16; + } +#endif + + while (bcnt >= 8) { + *bmap = 0; + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + ext4_bmap_bit_clr(bmap, i); + } +} + +int ext4_bmap_bit_find_clr(uint8_t *bmap, uint32_t sbit, uint32_t ebit, + uint32_t *bit_id) +{ + uint32_t i; + uint32_t bcnt = ebit - sbit; + + i = sbit; + + while (i & 7) { + + if (!bcnt) + return ENOSPC; + + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit; + return EOK; + } + + i++; + bcnt--; + } + + sbit = i; + bmap += (sbit >> 3); + +#if CONFIG_UNALIGNED_ACCESS + while (bcnt >= 32) { + if (*(uint32_t *)bmap != 0xFFFFFFFF) + goto finish_it; + + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + if (*(uint16_t *)bmap != 0xFFFF) + goto finish_it; + + bmap += 2; + bcnt -= 16; + sbit += 16; + } +finish_it: +#endif + while (bcnt >= 8) { + if (*bmap != 0xFF) { + for (i = 0; i < 8; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + } + + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_block_group.c b/clib/lib/lwext4/src/ext4_block_group.c new file mode 100644 index 0000000..d2bb1b1 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_block_group.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_block_group.c + * @brief Block group function set. + */ + +#include +#include +#include +#include +#include + +#include + +/**@brief CRC-16 look up table*/ +static uint16_t const crc16_tab[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, + 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, + 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, + 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, + 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, + 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, + 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, + 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, + 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, + 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, + 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, + 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, + 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, + 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, + 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, + 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, + 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, + 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, + 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, + 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, + 0x4100, 0x81C1, 0x8081, 0x4040}; + +uint16_t ext4_bg_crc16(uint16_t crc, const uint8_t *buffer, size_t len) +{ + while (len--) + + crc = (((crc >> 8) & 0xffU) ^ + crc16_tab[(crc ^ *buffer++) & 0xffU]) & + 0x0000ffffU; + return crc; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_blockdev.c b/clib/lib/lwext4/src/ext4_blockdev.c new file mode 100644 index 0000000..c01093a --- /dev/null +++ b/clib/lib/lwext4/src/ext4_blockdev.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_blockdev.c + * @brief Block device module. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +static void ext4_bdif_lock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->lock) + return; + + int r = bdev->bdif->lock(bdev); + ext4_assert(r == EOK); +} + +static void ext4_bdif_unlock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->unlock) + return; + + int r = bdev->bdif->unlock(bdev); + ext4_assert(r == EOK); +} + +static int ext4_bdif_bread(struct ext4_blockdev *bdev, void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bread(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bread_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +static int ext4_bdif_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bwrite(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bwrite_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +int ext4_block_init(struct ext4_blockdev *bdev) +{ + int rc; + ext4_assert(bdev); + ext4_assert(bdev->bdif); + ext4_assert(bdev->bdif->open && + bdev->bdif->close && + bdev->bdif->bread && + bdev->bdif->bwrite); + + if (bdev->bdif->ph_refctr) { + bdev->bdif->ph_refctr++; + return EOK; + } + + /*Low level block init*/ + rc = bdev->bdif->open(bdev); + if (rc != EOK) + return rc; + + bdev->bdif->ph_refctr = 1; + return EOK; +} + +int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc) +{ + ext4_assert(bdev && bc); + bdev->bc = bc; + bc->bdev = bdev; + return EOK; +} + +void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint32_t lb_bsize) +{ + /*Logical block size has to be multiply of physical */ + ext4_assert(!(lb_bsize % bdev->bdif->ph_bsize)); + + bdev->lg_bsize = lb_bsize; + bdev->lg_bcnt = bdev->part_size / lb_bsize; +} + +int ext4_block_fini(struct ext4_blockdev *bdev) +{ + ext4_assert(bdev); + + if (!bdev->bdif->ph_refctr) + return EOK; + + bdev->bdif->ph_refctr--; + if (bdev->bdif->ph_refctr) + return EOK; + + /*Low level block fini*/ + return bdev->bdif->close(bdev); +} + +int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf) +{ + int r; + struct ext4_bcache *bc = bdev->bc; + + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + r = ext4_blocks_set_direct(bdev, buf->data, buf->lba, 1); + if (r) { + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + + return r; + } + + ext4_bcache_remove_dirty_node(bc, buf); + ext4_bcache_clear_flag(buf, BC_DIRTY); + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + } + return EOK; +} + +int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba) +{ + int r = EOK; + struct ext4_buf *buf; + struct ext4_block b; + buf = ext4_bcache_find_get(bdev->bc, &b, lba); + if (buf) { + r = ext4_block_flush_buf(bdev, buf); + ext4_bcache_free(bdev->bc, &b); + } + return r; +} + +int ext4_block_cache_shake(struct ext4_blockdev *bdev) +{ + int r = EOK; + struct ext4_buf *buf; + if (bdev->bc->dont_shake) + return EOK; + + bdev->bc->dont_shake = true; + + while (!RB_EMPTY(&bdev->bc->lru_root) && + ext4_bcache_is_full(bdev->bc)) { + + buf = ext4_buf_lowest_lru(bdev->bc); + ext4_assert(buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) { + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + break; + + } + + ext4_bcache_drop_buf(bdev->bc, buf); + } + bdev->bc->dont_shake = false; + return r; +} + +int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + bool is_new; + int r; + + ext4_assert(bdev && b); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (!(lba < bdev->lg_bcnt)) + return ENXIO; + + b->lb_id = lba; + + /*If cache is full we have to (flush and) drop it anyway :(*/ + r = ext4_block_cache_shake(bdev); + if (r != EOK) + return r; + + r = ext4_bcache_alloc(bdev->bc, b, &is_new); + if (r != EOK) + return r; + + if (!b->data) + return ENOMEM; + + return EOK; +} + +int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + if (ext4_bcache_test_flag(b->buf, BC_UPTODATE)) { + /* Data in the cache is up-to-date. + * Reading from physical device is not required */ + return EOK; + } + + r = ext4_blocks_get_direct(bdev, b->data, lba, 1); + if (r != EOK) { + ext4_bcache_free(bdev->bc, b); + b->lb_id = 0; + return r; + } + + /* Mark buffer up-to-date, since + * fresh data is read from physical device just now. */ + ext4_bcache_set_flag(b->buf, BC_UPTODATE); + return EOK; +} + +int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b) +{ + ext4_assert(bdev && b); + ext4_assert(b->buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + return ext4_bcache_free(bdev->bc, b); +} + +int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba, + uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bread(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf, + uint64_t lba, uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bwrite(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off, + const void *buf, uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + const uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t wlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf + unalg, p, wlen); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + p += wlen; + len -= wlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + if (blen != 0) { + r = ext4_bdif_bwrite(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + } + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf, p, len); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + } + + return r; +} + +int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf, + uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t rlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf + unalg, rlen); + + p += rlen; + len -= rlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + + if (blen != 0) { + r = ext4_bdif_bread(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + } + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf, len); + } + + return r; +} + +int ext4_block_cache_flush(struct ext4_blockdev *bdev) +{ + while (!SLIST_EMPTY(&bdev->bc->dirty_list)) { + int r; + struct ext4_buf *buf = SLIST_FIRST(&bdev->bc->dirty_list); + ext4_assert(buf); + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + return r; + + } + return EOK; +} + +int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off) +{ + if (on_off) + bdev->cache_write_back++; + + if (!on_off && bdev->cache_write_back) + bdev->cache_write_back--; + + if (bdev->cache_write_back) + return EOK; + + /*Flush data in all delayed cache blocks*/ + return ext4_block_cache_flush(bdev); +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_crc32.c b/clib/lib/lwext4/src/ext4_crc32.c new file mode 100644 index 0000000..17ae0d0 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_crc32.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Based on FreeBSD. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_crc32c.c + * @brief Crc32c routine. Taken from FreeBSD kernel. + */ + +#include +#include +#include +#include +#include + +#include "ext4_crc32.h" + +static const uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +static const uint32_t crc32c_tab[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, + 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, + 0x6BE22838L, 0x9989AB3BL, 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, + 0x5E133C24L, 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, 0x9A879FA0L, + 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, 0x5D1D08BFL, 0xAF768BBCL, + 0xBC267848L, 0x4E4DFB4BL, 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, + 0x33ED7D2AL, 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, 0x6DFE410EL, + 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, 0x30E349B1L, 0xC288CAB2L, + 0xD1D83946L, 0x23B3BA45L, 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, + 0xE4292D5AL, 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, 0x417B1DBCL, + 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, 0x86E18AA3L, 0x748A09A0L, + 0x67DAFA54L, 0x95B17957L, 0xCBA24573L, 0x39C9C670L, 0x2A993584L, + 0xD8F2B687L, 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, 0x96BF4DCCL, + 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, 0xDBFC821CL, 0x2997011FL, + 0x3AC7F2EBL, 0xC8AC71E8L, 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, + 0x0F36E6F7L, 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, 0xEB1FCBADL, + 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, 0x2C855CB2L, 0xDEEEDFB1L, + 0xCDBE2C45L, 0x3FD5AF46L, 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, + 0x62C8A7F9L, 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, 0x3CDB9BDDL, + 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, 0x82F63B78L, 0x709DB87BL, + 0x63CD4B8FL, 0x91A6C88CL, 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, + 0x563C5F93L, 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, 0x92A8FC17L, + 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, 0x55326B08L, 0xA759E80BL, + 0xB4091BFFL, 0x466298FCL, 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, + 0x0B21572CL, 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, 0x65D122B9L, + 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, 0x2892ED69L, 0xDAF96E6AL, + 0xC9A99D9EL, 0x3BC21E9DL, 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, + 0xFC588982L, 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, 0x38CC2A06L, + 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, 0xFF56BD19L, 0x0D3D3E1AL, + 0x1E6DCDEEL, 0xEC064EEDL, 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, + 0xD0DDD530L, 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, 0x8ECEE914L, + 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, 0xD3D3E1ABL, 0x21B862A8L, + 0x32E8915CL, 0xC083125FL, 0x144976B4L, 0xE622F5B7L, 0xF5720643L, + 0x07198540L, 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, 0xE330A81AL, + 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, 0x24AA3F05L, 0xD6C1BC06L, + 0xC5914FF2L, 0x37FACCF1L, 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, + 0x7AB90321L, 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, 0x34F4F86AL, + 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, 0x79B737BAL, 0x8BDCB4B9L, + 0x988C474DL, 0x6AE7C44EL, 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, + 0xAD7D5351L}; + +static inline uint32_t crc32(uint32_t crc, const void *buf, uint32_t size, + const uint32_t *tab) +{ + const uint8_t *p = (const uint8_t *)buf; + + while (size--) + crc = tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return (crc); +} + +uint32_t ext4_crc32(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32_tab); +} + +uint32_t ext4_crc32c(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32c_tab); +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_debug.c b/clib/lib/lwext4/src/ext4_debug.c new file mode 100644 index 0000000..356a157 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_debug.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_debug.c + * @brief Debug printf and assert macros. + */ + +#include +#include +#include +#include +#include + +#include + +static uint32_t debug_mask; + +void ext4_dmask_set(uint32_t m) +{ + debug_mask |= m; +} + +void ext4_dmask_clr(uint32_t m) +{ + debug_mask &= ~m; +} + +uint32_t ext4_dmask_get(void) +{ + return debug_mask; +} + + + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_dir.c b/clib/lib/lwext4/src/ext4_dir.c new file mode 100644 index 0000000..29a51c5 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_dir.c @@ -0,0 +1,708 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir.h + * @brief Directory handle procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/****************************************************************************/ + +/* Walk through a dirent block to find a checksum "dirent" at the tail */ +static struct ext4_dir_entry_tail * +ext4_dir_get_tail(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *de) +{ + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + t = EXT4_DIRENT_TAIL(de, ext4_sb_get_block_size(sb)); + + if (t->reserved_zero1 || t->reserved_zero2) + return NULL; + if (to_le16(t->rec_len) != sizeof(struct ext4_dir_entry_tail)) + return NULL; + if (t->reserved_ft != EXT4_DIRENTRY_DIR_CSUM) + return NULL; + + return t; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int size) +{ + uint32_t csum; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); + + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against directory entries */ + csum = ext4_crc32c(csum, dirent, size); + return csum; +} +#else +#define ext4_dir_csum(...) 0 +#endif + +bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ +#ifdef CONFIG_META_CSUM_ENABLE + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + t = ext4_dir_get_tail(inode_ref, dirent); + if (!t) { + /* There is no space to hold the checksum */ + return false; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + if (t->checksum != to_le32(csum)) + return false; + + } +#endif + return true; +} + +void ext4_dir_init_entry_tail(struct ext4_dir_entry_tail *t) +{ + memset(t, 0, sizeof(struct ext4_dir_entry_tail)); + t->rec_len = to_le16(sizeof(struct ext4_dir_entry_tail)); + t->reserved_ft = EXT4_DIRENTRY_DIR_CSUM; +} + +void ext4_dir_set_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + t = ext4_dir_get_tail(inode_ref, dirent); + if (!t) { + /* There is no space to hold the checksum */ + return; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + t->checksum = to_le32(csum); + } +} + +/**@brief Do some checks before returning iterator. + * @param it Iterator to be checked + * @param block_size Size of data block + * @return Error code + */ +static int ext4_dir_iterator_set(struct ext4_dir_iter *it, + uint32_t block_size) +{ + uint32_t off_in_block = it->curr_off % block_size; + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + + it->curr = NULL; + + /* Ensure proper alignment */ + if ((off_in_block % 4) != 0) + return EIO; + + /* Ensure that the core of the entry does not overflow the block */ + if (off_in_block > block_size - 8) + return EIO; + + struct ext4_dir_en *en; + en = (void *)(it->curr_blk.data + off_in_block); + + /* Ensure that the whole entry does not overflow the block */ + uint16_t length = ext4_dir_en_get_entry_len(en); + if (off_in_block + length > block_size) + return EIO; + + /* Ensure the name length is not too large */ + if (ext4_dir_en_get_name_len(sb, en) > length - 8) + return EIO; + + /* Everything OK - "publish" the entry */ + it->curr = en; + return EOK; +} + +/**@brief Seek to next valid directory entry. + * Here can be jumped to the next data block. + * @param it Initialized iterator + * @param pos Position of the next entry + * @return Error code + */ +static int ext4_dir_iterator_seek(struct ext4_dir_iter *it, uint64_t pos) +{ + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + struct ext4_inode *inode = it->inode_ref->inode; + struct ext4_blockdev *bdev = it->inode_ref->fs->bdev; + uint64_t size = ext4_inode_get_size(sb, inode); + int r; + + /* The iterator is not valid until we seek to the desired position */ + it->curr = NULL; + + /* Are we at the end? */ + if (pos >= size) { + if (it->curr_blk.lb_id) { + + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + if (r != EOK) + return r; + } + + it->curr_off = pos; + return EOK; + } + + /* Compute next block address */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t current_blk_idx = it->curr_off / block_size; + uint32_t next_blk_idx = (uint32_t)(pos / block_size); + + /* + * If we don't have a block or are moving across block boundary, + * we need to get another block + */ + if ((it->curr_blk.lb_id == 0) || + (current_blk_idx != next_blk_idx)) { + if (it->curr_blk.lb_id) { + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + + if (r != EOK) + return r; + } + + ext4_fsblk_t next_blk; + r = ext4_fs_get_inode_dblk_idx(it->inode_ref, next_blk_idx, + &next_blk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(bdev, &it->curr_blk, next_blk); + if (r != EOK) { + it->curr_blk.lb_id = 0; + return r; + } + } + + it->curr_off = pos; + return ext4_dir_iterator_set(it, block_size); +} + +int ext4_dir_iterator_init(struct ext4_dir_iter *it, + struct ext4_inode_ref *inode_ref, uint64_t pos) +{ + it->inode_ref = inode_ref; + it->curr = 0; + it->curr_off = 0; + it->curr_blk.lb_id = 0; + + return ext4_dir_iterator_seek(it, pos); +} + +int ext4_dir_iterator_next(struct ext4_dir_iter *it) +{ + int r = EOK; + uint16_t skip; + + while (r == EOK) { + skip = ext4_dir_en_get_entry_len(it->curr); + r = ext4_dir_iterator_seek(it, it->curr_off + skip); + + if (!it->curr) + break; + /*Skip NULL referenced entry*/ + if (ext4_dir_en_get_inode(it->curr) != 0) + break; + } + + return r; +} + +int ext4_dir_iterator_fini(struct ext4_dir_iter *it) +{ + it->curr = 0; + + if (it->curr_blk.lb_id) + return ext4_block_set(it->inode_ref->fs->bdev, &it->curr_blk); + + return EOK; +} + +void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, + uint16_t entry_len, struct ext4_inode_ref *child, + const char *name, size_t name_len) +{ + /* Check maximum entry length */ + ext4_assert(entry_len <= ext4_sb_get_block_size(sb)); + + /* Set type of entry */ + switch (ext4_inode_type(sb, child->inode)) { + case EXT4_INODE_MODE_DIRECTORY: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_DIR); + break; + case EXT4_INODE_MODE_FILE: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_REG_FILE); + break; + case EXT4_INODE_MODE_SOFTLINK: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_SYMLINK); + break; + case EXT4_INODE_MODE_CHARDEV: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_CHRDEV); + break; + case EXT4_INODE_MODE_BLOCKDEV: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_BLKDEV); + break; + case EXT4_INODE_MODE_FIFO: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_FIFO); + break; + case EXT4_INODE_MODE_SOCKET: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_SOCK); + break; + default: + /* FIXME: unsupported filetype */ + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_UNKNOWN); + } + + /* Set basic attributes */ + ext4_dir_en_set_inode(en, child->index); + ext4_dir_en_set_entry_len(en, entry_len); + ext4_dir_en_set_name_len(sb, en, (uint16_t)name_len); + + /* Write name */ + memcpy(en->name, name, name_len); +} + +int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len, struct ext4_inode_ref *child) +{ + int r; + struct ext4_fs *fs = parent->fs; + struct ext4_sblock *sb = &parent->fs->sb; + +#if CONFIG_DIR_INDEX_ENABLE + /* Index adding (if allowed) */ + if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_add_entry(parent, child, name, name_len); + + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + uint32_t iblock = 0; + ext4_fsblk_t fblock = 0; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Find block, where is space for new entry and try to add */ + bool success = false; + for (iblock = 0; iblock < total_blocks; ++iblock) { + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + struct ext4_block block; + r = ext4_trans_block_get(fs->bdev, &block, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)block.data)) { + ext4_dbg(DEBUG_DIR, + DBG_WARN "Leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + iblock); + } + + /* If adding is successful, function can finish */ + r = ext4_dir_try_insert_entry(sb, parent, &block, child, + name, name_len); + if (r == EOK) + success = true; + + r = ext4_block_set(fs->bdev, &block); + if (r != EOK) + return r; + + if (success) + return EOK; + } + + /* No free block found - needed to allocate next data block */ + + iblock = 0; + fblock = 0; + r = ext4_fs_append_inode_dblk(parent, &fblock, &iblock); + if (r != EOK) + return r; + + /* Load new block */ + struct ext4_block b; + + r = ext4_trans_block_get_noread(fs->bdev, &b, fblock); + if (r != EOK) + return r; + + /* Fill block with zeroes */ + memset(b.data, 0, block_size); + struct ext4_dir_en *blk_en = (void *)b.data; + + /* Save new block */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t el = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_write_entry(sb, blk_en, el, child, name, name_len); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(b.data, block_size)); + } else { + ext4_dir_write_entry(sb, blk_en, block_size, child, name, + name_len); + } + + ext4_dir_set_csum(parent, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(fs->bdev, &b); + + return r; +} + +int ext4_dir_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + int r; + struct ext4_sblock *sb = &parent->fs->sb; + + /* Entry clear */ + result->block.lb_id = 0; + result->dentry = NULL; + +#if CONFIG_DIR_INDEX_ENABLE + /* Index search */ + if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_find_entry(result, parent, name_len, name); + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + + uint32_t iblock; + ext4_fsblk_t fblock; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Walk through all data blocks */ + for (iblock = 0; iblock < total_blocks; ++iblock) { + /* Load block address */ + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + /* Load data block */ + struct ext4_block b; + r = ext4_trans_block_get(parent->fs->bdev, &b, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)b.data)) { + ext4_dbg(DEBUG_DIR, + DBG_WARN "Leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + iblock); + } + + /* Try to find entry in block */ + struct ext4_dir_en *res_entry; + r = ext4_dir_find_in_block(&b, sb, name_len, name, &res_entry); + if (r == EOK) { + result->block = b; + result->dentry = res_entry; + return EOK; + } + + /* Entry not found - put block and continue to the next block */ + + r = ext4_block_set(parent->fs->bdev, &b); + if (r != EOK) + return r; + } + + return ENOENT; +} + +int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + struct ext4_sblock *sb = &parent->fs->sb; + /* Check if removing from directory */ + if (!ext4_inode_is_type(sb, parent->inode, EXT4_INODE_MODE_DIRECTORY)) + return ENOTDIR; + + /* Try to find entry */ + struct ext4_dir_search_result result; + int rc = ext4_dir_find_entry(&result, parent, name, name_len); + if (rc != EOK) + return rc; + + /* Invalidate entry */ + ext4_dir_en_set_inode(result.dentry, 0); + + /* Store entry position in block */ + uint32_t pos = (uint8_t *)result.dentry - result.block.data; + + /* + * If entry is not the first in block, it must be merged + * with previous entry + */ + if (pos != 0) { + uint32_t offset = 0; + + /* Start from the first entry in block */ + struct ext4_dir_en *tmp_de =(void *)result.block.data; + uint16_t de_len = ext4_dir_en_get_entry_len(tmp_de); + + /* Find direct predecessor of removed entry */ + while ((offset + de_len) < pos) { + offset += ext4_dir_en_get_entry_len(tmp_de); + tmp_de = (void *)(result.block.data + offset); + de_len = ext4_dir_en_get_entry_len(tmp_de); + } + + ext4_assert(de_len + offset == pos); + + /* Add to removed entry length to predecessor's length */ + uint16_t del_len; + del_len = ext4_dir_en_get_entry_len(result.dentry); + ext4_dir_en_set_entry_len(tmp_de, de_len + del_len); + } + + ext4_dir_set_csum(parent, + (struct ext4_dir_en *)result.block.data); + ext4_trans_set_block_dirty(result.block.buf); + + return ext4_dir_destroy_result(parent, &result); +} + +int ext4_dir_try_insert_entry(struct ext4_sblock *sb, + struct ext4_inode_ref *inode_ref, + struct ext4_block *dst_blk, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + /* Compute required length entry and align it to 4 bytes */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint16_t required_len = sizeof(struct ext4_fake_dir_entry) + name_len; + + if ((required_len % 4) != 0) + required_len += 4 - (required_len % 4); + + /* Initialize pointers, stop means to upper bound */ + struct ext4_dir_en *start = (void *)dst_blk->data; + struct ext4_dir_en *stop = (void *)(dst_blk->data + block_size); + + /* + * Walk through the block and check for invalid entries + * or entries with free space for new entry + */ + while (start < stop) { + uint32_t inode = ext4_dir_en_get_inode(start); + uint16_t rec_len = ext4_dir_en_get_entry_len(start); + uint8_t itype = ext4_dir_en_get_inode_type(sb, start); + + /* If invalid and large enough entry, use it */ + if ((inode == 0) && (itype != EXT4_DIRENTRY_DIR_CSUM) && + (rec_len >= required_len)) { + ext4_dir_write_entry(sb, start, rec_len, child, name, + name_len); + ext4_dir_set_csum(inode_ref, (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + + return EOK; + } + + /* Valid entry, try to split it */ + if (inode != 0) { + uint16_t used_len; + used_len = ext4_dir_en_get_name_len(sb, start); + + uint16_t sz; + sz = sizeof(struct ext4_fake_dir_entry) + used_len; + + if ((used_len % 4) != 0) + sz += 4 - (used_len % 4); + + uint16_t free_space = rec_len - sz; + + /* There is free space for new entry */ + if (free_space >= required_len) { + /* Cut tail of current entry */ + struct ext4_dir_en * new_entry; + new_entry = (void *)((uint8_t *)start + sz); + ext4_dir_en_set_entry_len(start, sz); + ext4_dir_write_entry(sb, new_entry, free_space, + child, name, name_len); + + ext4_dir_set_csum(inode_ref, + (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + return EOK; + } + } + + /* Jump to the next entry */ + start = (void *)((uint8_t *)start + rec_len); + } + + /* No free space found for new entry */ + return ENOSPC; +} + +int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, + size_t name_len, const char *name, + struct ext4_dir_en **res_entry) +{ + /* Start from the first entry in block */ + struct ext4_dir_en *de = (struct ext4_dir_en *)block->data; + + /* Set upper bound for cycling */ + uint8_t *addr_limit = block->data + ext4_sb_get_block_size(sb); + + /* Walk through the block and check entries */ + while ((uint8_t *)de < addr_limit) { + /* Termination condition */ + if ((uint8_t *)de + name_len > addr_limit) + break; + + /* Valid entry - check it */ + if (ext4_dir_en_get_inode(de) != 0) { + /* For more efficient compare only lengths firstly*/ + uint16_t el = ext4_dir_en_get_name_len(sb, de); + if (el == name_len) { + /* Compare names */ + if (memcmp(name, de->name, name_len) == 0) { + *res_entry = de; + return EOK; + } + } + } + + uint16_t de_len = ext4_dir_en_get_entry_len(de); + + /* Corrupted entry */ + if (de_len == 0) + return EINVAL; + + /* Jump to next entry */ + de = (struct ext4_dir_en *)((uint8_t *)de + de_len); + } + + /* Entry not found */ + return ENOENT; +} + +int ext4_dir_destroy_result(struct ext4_inode_ref *parent, + struct ext4_dir_search_result *result) +{ + if (result->block.lb_id) + return ext4_block_set(parent->fs->bdev, &result->block); + + return EOK; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_dir_idx.c b/clib/lib/lwext4/src/ext4_dir_idx.c new file mode 100644 index 0000000..f916cc6 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_dir_idx.c @@ -0,0 +1,1402 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir_idx.c + * @brief Directory indexing procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Get hash version used in directory index. + * @param ri Pointer to root info structure of index + * @return Hash algorithm version + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_hash_version(struct ext4_dir_idx_rinfo *ri) +{ + return ri->hash_version; +} + +/**@brief Set hash version, that will be used in directory index. + * @param ri Pointer to root info structure of index + * @param v Hash algorithm version + */ +static inline void +ext4_dir_dx_rinfo_set_hash_version(struct ext4_dir_idx_rinfo *ri, uint8_t v) +{ + ri->hash_version = v; +} + +/**@brief Get length of root_info structure in bytes. + * @param ri Pointer to root info structure of index + * @return Length of the structure + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_info_length(struct ext4_dir_idx_rinfo *ri) +{ + return ri->info_length; +} + +/**@brief Set length of root_info structure in bytes. + * @param ri Pointer to root info structure of index + * @param len Length of the structure + */ +static inline void +ext4_dir_dx_root_info_set_info_length(struct ext4_dir_idx_rinfo *ri, + uint8_t len) +{ + ri->info_length = len; +} + +/**@brief Get number of indirect levels of HTree. + * @param ri Pointer to root info structure of index + * @return Height of HTree (actually only 0 or 1) + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_indirect_levels(struct ext4_dir_idx_rinfo *ri) +{ + return ri->indirect_levels; +} + +/**@brief Set number of indirect levels of HTree. + * @param ri Pointer to root info structure of index + * @param l Height of HTree (actually only 0 or 1) + */ +static inline void +ext4_dir_dx_rinfo_set_indirect_levels(struct ext4_dir_idx_rinfo *ri, uint8_t l) +{ + ri->indirect_levels = l; +} + +/**@brief Get maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @return Maximum of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_limit(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->limit); +} + +/**@brief Set maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @param limit Maximum of entries in node + */ +static inline void +ext4_dir_dx_climit_set_limit(struct ext4_dir_idx_climit *climit, uint16_t limit) +{ + climit->limit = to_le16(limit); +} + +/**@brief Get current number of index node entries. + * @param climit Pointer to counlimit structure + * @return Number of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_count(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->count); +} + +/**@brief Set current number of index node entries. + * @param climit Pointer to counlimit structure + * @param count Number of entries in node + */ +static inline void +ext4_dir_dx_climit_set_count(struct ext4_dir_idx_climit *climit, uint16_t count) +{ + climit->count = to_le16(count); +} + +/**@brief Get hash value of index entry. + * @param entry Pointer to index entry + * @return Hash value + */ +static inline uint32_t +ext4_dir_dx_entry_get_hash(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->hash); +} + +/**@brief Set hash value of index entry. + * @param entry Pointer to index entry + * @param hash Hash value + */ +static inline void +ext4_dir_dx_entry_set_hash(struct ext4_dir_idx_entry *entry, uint32_t hash) +{ + entry->hash = to_le32(hash); +} + +/**@brief Get block address where child node is located. + * @param entry Pointer to index entry + * @return Block address of child node + */ +static inline uint32_t +ext4_dir_dx_entry_get_block(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->block); +} + +/**@brief Set block address where child node is located. + * @param entry Pointer to index entry + * @param block Block address of child node + */ +static inline void +ext4_dir_dx_entry_set_block(struct ext4_dir_idx_entry *entry, uint32_t block) +{ + entry->block = to_le32(block); +} + +/**@brief Sort entry item.*/ +struct ext4_dx_sort_entry { + uint32_t hash; + uint32_t rec_len; + void *dentry; +}; + +static int ext4_dir_dx_hash_string(struct ext4_hash_info *hinfo, int len, + const char *name) +{ + return ext2_htree_hash(name, len, hinfo->seed, hinfo->hash_version, + &hinfo->hash, &hinfo->minor_hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_dx_checksum(struct ext4_inode_ref *inode_ref, void *de, + int count_offset, int count, + struct ext4_dir_idx_tail *t) +{ + uint32_t orig_cum, csum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + int sz; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen; + ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); + + sz = count_offset + (count * sizeof(struct ext4_dir_idx_tail)); + orig_cum = t->checksum; + t->checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* After that calculate crc32 checksum against all the dx_entry */ + csum = ext4_crc32c(csum, de, sz); + /* Finally calculate crc32 checksum for dx_tail */ + csum = ext4_crc32c(csum, t, sizeof(struct ext4_dir_idx_tail)); + t->checksum = orig_cum; + } + return csum; +} + +static struct ext4_dir_idx_climit * +ext4_dir_dx_get_climit(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int *offset) +{ + struct ext4_dir_en *dp; + struct ext4_dir_idx_root *root; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint16_t entry_len = ext4_dir_en_get_entry_len(dirent); + int count_offset; + + + if (entry_len == 12) { + root = (struct ext4_dir_idx_root *)dirent; + dp = (struct ext4_dir_en *)&root->dots[1]; + if (ext4_dir_en_get_entry_len(dp) != (block_size - 12)) + return NULL; + if (root->info.reserved_zero) + return NULL; + if (root->info.info_length != sizeof(struct ext4_dir_idx_rinfo)) + return NULL; + count_offset = 32; + } else if (entry_len == block_size) { + count_offset = 8; + } else { + return NULL; + } + + if (offset) + *offset = count_offset; + return (struct ext4_dir_idx_climit *)(((char *)dirent) + count_offset); +} + +/* + * BIG FAT NOTES: + * Currently we do not verify the checksum of HTree node. + */ +static bool ext4_dir_dx_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *de) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + int coff, limit, cnt; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, de, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return true; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + cnt = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return true; + } + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + + uint32_t c; + c = to_le32(ext4_dir_dx_checksum(inode_ref, de, coff, cnt, t)); + if (t->checksum != c) + return false; + } + return true; +} + + +static void ext4_dir_set_dx_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ + int coff, limit, count; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, dirent, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + count = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return; + } + + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + t->checksum = to_le32(ext4_dir_dx_checksum(inode_ref, dirent, + coff, count, t)); + } +} +#else +#define ext4_dir_dx_csum_verify(...) true +#define ext4_dir_set_dx_csum(...) +#endif + +/****************************************************************************/ + +int ext4_dir_dx_init(struct ext4_inode_ref *dir, struct ext4_inode_ref *parent) +{ + /* Load block 0, where will be index root located */ + ext4_fsblk_t fblock; + uint32_t iblock = 0; + bool need_append = + (ext4_inode_get_size(&dir->fs->sb, dir->inode) + < EXT4_DIR_DX_INIT_BCNT) + ? true : false; + struct ext4_sblock *sb = &dir->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&dir->fs->sb); + struct ext4_block block; + + int rc; + + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) + return rc; + + rc = ext4_trans_block_get_noread(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + struct ext4_dir_idx_rinfo *info = &(root->info); + + memset(root, 0, sizeof(struct ext4_dir_idx_root)); + struct ext4_dir_en *de; + + /* Initialize dot entries */ + de = (struct ext4_dir_en *)root->dots; + ext4_dir_write_entry(sb, de, 12, dir, ".", strlen(".")); + + de = (struct ext4_dir_en *)(root->dots + 1); + uint16_t elen = block_size - 12; + ext4_dir_write_entry(sb, de, elen, parent, "..", strlen("..")); + + /* Initialize root info structure */ + uint8_t hash_version = ext4_get8(&dir->fs->sb, default_hash_version); + + ext4_dir_dx_rinfo_set_hash_version(info, hash_version); + ext4_dir_dx_rinfo_set_indirect_levels(info, 0); + ext4_dir_dx_root_info_set_info_length(info, 8); + + /* Set limit and current number of entries */ + struct ext4_dir_idx_climit *climit; + climit = (struct ext4_dir_idx_climit *)&root->en; + + ext4_dir_dx_climit_set_count(climit, 1); + + uint32_t entry_space; + entry_space = block_size - 2 * sizeof(struct ext4_dir_idx_dot_en) - + sizeof(struct ext4_dir_idx_rinfo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + uint16_t root_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + ext4_dir_dx_climit_set_limit(climit, root_limit); + + /* Append new block, where will be new entries inserted in the future */ + iblock++; + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + struct ext4_block new_block; + rc = ext4_trans_block_get_noread(dir->fs->bdev, &new_block, fblock); + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + /* Fill the whole block with empty entry */ + struct ext4_dir_en *be = (void *)new_block.data; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t len = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_en_set_entry_len(be, len); + ext4_dir_en_set_name_len(sb, be, 0); + ext4_dir_en_set_inode_type(sb, be, EXT4_DE_UNKNOWN); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(be, block_size)); + ext4_dir_set_csum(dir, be); + } else { + ext4_dir_en_set_entry_len(be, block_size); + } + + ext4_dir_en_set_inode(be, 0); + + ext4_trans_set_block_dirty(new_block.buf); + rc = ext4_block_set(dir->fs->bdev, &new_block); + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + /* Connect new block to the only entry in index */ + struct ext4_dir_idx_entry *entry = root->en; + ext4_dir_dx_entry_set_block(entry, iblock); + + ext4_dir_set_dx_csum(dir, (struct ext4_dir_en *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/**@brief Initialize hash info structure necessary for index operations. + * @param hinfo Pointer to hinfo to be initialized + * @param root_block Root block (number 0) of index + * @param sb Pointer to superblock + * @param name_len Length of name to be computed hash value from + * @param name Name to be computed hash value from + * @return Standard error code + */ +static int ext4_dir_hinfo_init(struct ext4_hash_info *hinfo, + struct ext4_block *root_block, + struct ext4_sblock *sb, size_t name_len, + const char *name) +{ + struct ext4_dir_idx_root *root; + + root = (struct ext4_dir_idx_root *)root_block->data; + if ((root->info.hash_version != EXT2_HTREE_LEGACY) && + (root->info.hash_version != EXT2_HTREE_HALF_MD4) && + (root->info.hash_version != EXT2_HTREE_TEA)) + return EXT4_ERR_BAD_DX_DIR; + + /* Check unused flags */ + if (root->info.unused_flags != 0) + return EXT4_ERR_BAD_DX_DIR; + + /* Check indirect levels */ + if (root->info.indirect_levels > 1) + return EXT4_ERR_BAD_DX_DIR; + + /* Check if node limit is correct */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t entry_space = block_size; + entry_space -= 2 * sizeof(struct ext4_dir_idx_dot_en); + entry_space -= sizeof(struct ext4_dir_idx_rinfo); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + struct ext4_dir_idx_climit *climit = (void *)&root->en; + uint16_t limit = ext4_dir_dx_climit_get_limit(climit); + if (limit != entry_space) + return EXT4_ERR_BAD_DX_DIR; + + /* Check hash version and modify if necessary */ + hinfo->hash_version = ext4_dir_dx_rinfo_get_hash_version(&root->info); + if ((hinfo->hash_version <= EXT2_HTREE_TEA) && + (ext4_sb_check_flag(sb, EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH))) { + /* Use unsigned hash */ + hinfo->hash_version += 3; + } + + /* Load hash seed from superblock */ + hinfo->seed = ext4_get8(sb, hash_seed); + + /* Compute hash value of name */ + if (name) + return ext4_dir_dx_hash_string(hinfo, name_len, name); + + return EOK; +} + +/**@brief Walk through index tree and load leaf with corresponding hash value. + * @param hinfo Initialized hash info structure + * @param inode_ref Current i-node + * @param root_block Root block (iblock 0), where is root node located + * @param dx_block Pointer to leaf node in dx_blocks array + * @param dx_blocks Array with the whole path from root to leaf + * @return Standard error code + */ +static int ext4_dir_dx_get_leaf(struct ext4_hash_info *hinfo, + struct ext4_inode_ref *inode_ref, + struct ext4_block *root_block, + struct ext4_dir_idx_block **dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + struct ext4_dir_idx_root *root; + struct ext4_dir_idx_entry *entries; + struct ext4_dir_idx_entry *p; + struct ext4_dir_idx_entry *q; + struct ext4_dir_idx_entry *m; + struct ext4_dir_idx_entry *at; + ext4_fsblk_t fblk; + uint32_t block_size; + uint16_t limit; + uint16_t entry_space; + uint8_t ind_level; + int r; + + struct ext4_dir_idx_block *tmp_dx_blk = dx_blocks; + struct ext4_block *tmp_blk = root_block; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + block_size = ext4_sb_get_block_size(sb); + root = (struct ext4_dir_idx_root *)root_block->data; + entries = (struct ext4_dir_idx_entry *)&root->en; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + ind_level = ext4_dir_dx_rinfo_get_indirect_levels(&root->info); + + /* Walk through the index tree */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)entries); + if ((cnt == 0) || (cnt > limit)) + return EXT4_ERR_BAD_DX_DIR; + + /* Do binary search in every node */ + p = entries + 1; + q = entries + cnt - 1; + + while (p <= q) { + m = p + (q - p) / 2; + if (ext4_dir_dx_entry_get_hash(m) > hinfo->hash) + q = m - 1; + else + p = m + 1; + } + + at = p - 1; + + /* Write results */ + memcpy(&tmp_dx_blk->b, tmp_blk, sizeof(struct ext4_block)); + tmp_dx_blk->entries = entries; + tmp_dx_blk->position = at; + + /* Is algorithm in the leaf? */ + if (ind_level == 0) { + *dx_block = tmp_dx_blk; + return EOK; + } + + /* Goto child node */ + uint32_t n_blk = ext4_dir_dx_entry_get_block(at); + + ind_level--; + + r = ext4_fs_get_inode_dblk_idx(inode_ref, n_blk, &fblk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(inode_ref->fs->bdev, tmp_blk, fblk); + if (r != EOK) + return r; + + entries = ((struct ext4_dir_idx_node *)tmp_blk->data)->entries; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + + entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + if (limit != entry_space) { + ext4_block_set(inode_ref->fs->bdev, tmp_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)tmp_blk->data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + n_blk); + } + + ++tmp_dx_blk; + } + + /* Unreachable */ + return EOK; +} + +/**@brief Check if the the next block would be checked during entry search. + * @param inode_ref Directory i-node + * @param hash Hash value to check + * @param dx_block Current block + * @param dx_blocks Array with path from root to leaf node + * @return Standard Error code + */ +static int ext4_dir_dx_next_block(struct ext4_inode_ref *inode_ref, + uint32_t hash, + struct ext4_dir_idx_block *dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + int r; + uint32_t num_handles = 0; + ext4_fsblk_t blk_adr; + struct ext4_dir_idx_block *p = dx_block; + + /* Try to find data block with next bunch of entries */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)p->entries); + + p->position++; + if (p->position < p->entries + cnt) + break; + + if (p == dx_blocks) + return EOK; + + num_handles++; + p--; + } + + /* Check hash collision (if not occurred - no next block cannot be + * used)*/ + uint32_t current_hash = ext4_dir_dx_entry_get_hash(p->position); + if ((hash & 1) == 0) { + if ((current_hash & ~1) != hash) + return 0; + } + + /* Fill new path */ + while (num_handles--) { + uint32_t blk = ext4_dir_dx_entry_get_block(p->position); + r = ext4_fs_get_inode_dblk_idx(inode_ref, blk, &blk_adr, false); + if (r != EOK) + return r; + + struct ext4_block b; + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, blk_adr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + blk); + } + + p++; + + /* Don't forget to put old block (prevent memory leak) */ + r = ext4_block_set(inode_ref->fs->bdev, &p->b); + if (r != EOK) + return r; + + memcpy(&p->b, &b, sizeof(b)); + p->entries = ((struct ext4_dir_idx_node *)b.data)->entries; + p->position = p->entries; + } + + return ENOENT; +} + +int ext4_dir_dx_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *inode_ref, size_t name_len, + const char *name) +{ + /* Load direct block 0 (index root) */ + ext4_fsblk_t root_block_addr; + int rc2; + int rc; + rc = ext4_fs_get_inode_dblk_idx(inode_ref, 0, &root_block_addr, false); + if (rc != EOK) + return rc; + + struct ext4_fs *fs = inode_ref->fs; + + struct ext4_block root_block; + rc = ext4_trans_block_get(fs->bdev, &root_block, root_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)root_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + (uint32_t)0); + } + + /* Initialize hash info (compute hash value) */ + struct ext4_hash_info hinfo; + rc = ext4_dir_hinfo_init(&hinfo, &root_block, &fs->sb, name_len, name); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index tree, + * specified in the Linux driver. + */ + struct ext4_dir_idx_block dx_blocks[2]; + struct ext4_dir_idx_block *dx_block; + struct ext4_dir_idx_block *tmp; + + rc = ext4_dir_dx_get_leaf(&hinfo, inode_ref, &root_block, &dx_block, + dx_blocks); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + do { + /* Load leaf block */ + uint32_t leaf_blk_idx; + ext4_fsblk_t leaf_block_addr; + struct ext4_block b; + + leaf_blk_idx = ext4_dir_dx_entry_get_block(dx_block->position); + rc = ext4_fs_get_inode_dblk_idx(inode_ref, leaf_blk_idx, + &leaf_block_addr, false); + if (rc != EOK) + goto cleanup; + + rc = ext4_trans_block_get(fs->bdev, &b, leaf_block_addr); + if (rc != EOK) + goto cleanup; + + if (!ext4_dir_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + leaf_blk_idx); + } + + /* Linear search inside block */ + struct ext4_dir_en *de; + rc = ext4_dir_find_in_block(&b, &fs->sb, name_len, name, &de); + + /* Found => return it */ + if (rc == EOK) { + result->block = b; + result->dentry = de; + goto cleanup; + } + + /* Not found, leave untouched */ + rc2 = ext4_block_set(fs->bdev, &b); + if (rc2 != EOK) + goto cleanup; + + if (rc != ENOENT) + goto cleanup; + + /* check if the next block could be checked */ + rc = ext4_dir_dx_next_block(inode_ref, hinfo.hash, dx_block, + &dx_blocks[0]); + if (rc < 0) + goto cleanup; + } while (rc == ENOENT); + + /* Entry not found */ + rc = ENOENT; + +cleanup: + /* The whole path must be released (preventing memory leak) */ + tmp = dx_blocks; + + while (tmp <= dx_block) { + rc2 = ext4_block_set(fs->bdev, &tmp->b); + if (rc == EOK && rc2 != EOK) + rc = rc2; + ++tmp; + } + + return rc; +} + +/**@brief Compare function used to pass in quicksort implementation. + * It can compare two entries by hash value. + * @param arg1 First entry + * @param arg2 Second entry + * + * @return Classic compare result + * (0: equal, -1: arg1 < arg2, 1: arg1 > arg2) + */ +static int ext4_dir_dx_entry_comparator(const void *arg1, const void *arg2) +{ + struct ext4_dx_sort_entry *entry1 = (void *)arg1; + struct ext4_dx_sort_entry *entry2 = (void *)arg2; + + if (entry1->hash == entry2->hash) + return 0; + + if (entry1->hash < entry2->hash) + return -1; + else + return 1; +} + +/**@brief Insert new index entry to block. + * Note that space for new entry must be checked by caller. + * @param inode_ref Directory i-node + * @param index_block Block where to insert new entry + * @param hash Hash value covered by child node + * @param iblock Logical number of child block + * + */ +static void +ext4_dir_dx_insert_entry(struct ext4_inode_ref *inode_ref __unused, + struct ext4_dir_idx_block *index_block, + uint32_t hash, uint32_t iblock) +{ + struct ext4_dir_idx_entry *old_index_entry = index_block->position; + struct ext4_dir_idx_entry *new_index_entry = old_index_entry + 1; + struct ext4_dir_idx_climit *climit = (void *)index_block->entries; + struct ext4_dir_idx_entry *start_index = index_block->entries; + uint32_t count = ext4_dir_dx_climit_get_count(climit); + + size_t bytes; + bytes = (uint8_t *)(start_index + count) - (uint8_t *)(new_index_entry); + + memmove(new_index_entry + 1, new_index_entry, bytes); + + ext4_dir_dx_entry_set_block(new_index_entry, iblock); + ext4_dir_dx_entry_set_hash(new_index_entry, hash); + ext4_dir_dx_climit_set_count(climit, count + 1); + ext4_dir_set_dx_csum(inode_ref, (void *)index_block->b.data); + ext4_trans_set_block_dirty(index_block->b.buf); +} + +/**@brief Split directory entries to two parts preventing node overflow. + * @param inode_ref Directory i-node + * @param hinfo Hash info + * @param old_data_block Block with data to be split + * @param index_block Block where index entries are located + * @param new_data_block Output value for newly allocated data block + */ +static int ext4_dir_dx_split_data(struct ext4_inode_ref *inode_ref, + struct ext4_hash_info *hinfo, + struct ext4_block *old_data_block, + struct ext4_dir_idx_block *index_block, + struct ext4_block *new_data_block) +{ + int rc = EOK; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Allocate buffer for directory entries */ + uint8_t *entry_buffer = ext4_malloc(block_size); + if (entry_buffer == NULL) + return ENOMEM; + + /* dot entry has the smallest size available */ + uint32_t max_ecnt = block_size / sizeof(struct ext4_dir_idx_dot_en); + + /* Allocate sort entry */ + struct ext4_dx_sort_entry *sort; + + sort = ext4_malloc(max_ecnt * sizeof(struct ext4_dx_sort_entry)); + if (sort == NULL) { + ext4_free(entry_buffer); + return ENOMEM; + } + + uint32_t idx = 0; + uint32_t real_size = 0; + + /* Initialize hinfo */ + struct ext4_hash_info hinfo_tmp; + memcpy(&hinfo_tmp, hinfo, sizeof(struct ext4_hash_info)); + + /* Load all valid entries to the buffer */ + struct ext4_dir_en *de = (void *)old_data_block->data; + uint8_t *entry_buffer_ptr = entry_buffer; + while ((void *)de < (void *)(old_data_block->data + block_size)) { + /* Read only valid entries */ + if (ext4_dir_en_get_inode(de) && de->name_len) { + uint16_t len = ext4_dir_en_get_name_len(sb, de); + rc = ext4_dir_dx_hash_string(&hinfo_tmp, len, + (char *)de->name); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + uint32_t rec_len = 8 + len; + if ((rec_len % 4) != 0) + rec_len += 4 - (rec_len % 4); + + memcpy(entry_buffer_ptr, de, rec_len); + + sort[idx].dentry = entry_buffer_ptr; + sort[idx].rec_len = rec_len; + sort[idx].hash = hinfo_tmp.hash; + + entry_buffer_ptr += rec_len; + real_size += rec_len; + idx++; + } + + size_t elen = ext4_dir_en_get_entry_len(de); + de = (void *)((uint8_t *)de + elen); + } + + qsort(sort, idx, sizeof(struct ext4_dx_sort_entry), + ext4_dir_dx_entry_comparator); + + /* Allocate new block for store the second part of entries */ + ext4_fsblk_t new_fblock; + uint32_t new_iblock; + rc = ext4_fs_append_inode_dblk(inode_ref, &new_fblock, &new_iblock); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + /* Load new block */ + struct ext4_block new_data_block_tmp; + rc = ext4_trans_block_get_noread(inode_ref->fs->bdev, &new_data_block_tmp, + new_fblock); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + /* + * Distribute entries to two blocks (by size) + * - compute the half + */ + uint32_t new_hash = 0; + uint32_t current_size = 0; + uint32_t mid = 0; + uint32_t i; + for (i = 0; i < idx; ++i) { + if ((current_size + sort[i].rec_len) > (block_size / 2)) { + new_hash = sort[i].hash; + mid = i; + break; + } + + current_size += sort[i].rec_len; + } + + /* Check hash collision */ + uint32_t continued = 0; + if (new_hash == sort[mid - 1].hash) + continued = 1; + + uint32_t off = 0; + void *ptr; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + block_size -= sizeof(struct ext4_dir_entry_tail); + + /* First part - to the old block */ + for (i = 0; i < mid; ++i) { + ptr = old_data_block->data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (mid - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + /* Second part - to the new block */ + off = 0; + for (i = mid; i < idx; ++i) { + ptr = new_data_block_tmp.data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (idx - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Do some steps to finish operation */ + sb = &inode_ref->fs->sb; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_entry_tail *t; + + t = EXT4_DIRENT_TAIL(old_data_block->data, block_size); + ext4_dir_init_entry_tail(t); + t = EXT4_DIRENT_TAIL(new_data_block_tmp.data, block_size); + ext4_dir_init_entry_tail(t); + } + ext4_dir_set_csum(inode_ref, (void *)old_data_block->data); + ext4_dir_set_csum(inode_ref, (void *)new_data_block_tmp.data); + ext4_trans_set_block_dirty(old_data_block->buf); + ext4_trans_set_block_dirty(new_data_block_tmp.buf); + + ext4_free(sort); + ext4_free(entry_buffer); + + ext4_dir_dx_insert_entry(inode_ref, index_block, new_hash + continued, + new_iblock); + + *new_data_block = new_data_block_tmp; + return EOK; +} + +/**@brief Split index node and maybe some parent nodes in the tree hierarchy. + * @param ino_ref Directory i-node + * @param dx_blks Array with path from root to leaf node + * @param dxb Leaf block to be split if needed + * @return Error code + */ +static int +ext4_dir_dx_split_index(struct ext4_inode_ref *ino_ref, + struct ext4_dir_idx_block *dx_blks, + struct ext4_dir_idx_block *dxb, + struct ext4_dir_idx_block **new_dx_block) +{ + struct ext4_sblock *sb = &ino_ref->fs->sb; + struct ext4_dir_idx_entry *e; + int r; + + uint32_t block_size = ext4_sb_get_block_size(&ino_ref->fs->sb); + uint32_t entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + uint32_t node_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + + bool meta_csum = ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM); + + if (dxb == dx_blks) + e = ((struct ext4_dir_idx_root *)dxb->b.data)->en; + else + e = ((struct ext4_dir_idx_node *)dxb->b.data)->entries; + + struct ext4_dir_idx_climit *climit = (struct ext4_dir_idx_climit *)e; + + uint16_t leaf_limit = ext4_dir_dx_climit_get_limit(climit); + uint16_t leaf_count = ext4_dir_dx_climit_get_count(climit); + + /* Check if is necessary to split index block */ + if (leaf_limit == leaf_count) { + struct ext4_dir_idx_entry *ren; + ptrdiff_t levels = dxb - dx_blks; + + ren = ((struct ext4_dir_idx_root *)dx_blks[0].b.data)->en; + struct ext4_dir_idx_climit *rclimit = (void *)ren; + uint16_t root_limit = ext4_dir_dx_climit_get_limit(rclimit); + uint16_t root_count = ext4_dir_dx_climit_get_count(rclimit); + + + /* Linux limitation */ + if ((levels > 0) && (root_limit == root_count)) + return ENOSPC; + + /* Add new block to directory */ + ext4_fsblk_t new_fblk; + uint32_t new_iblk; + r = ext4_fs_append_inode_dblk(ino_ref, &new_fblk, &new_iblk); + if (r != EOK) + return r; + + /* load new block */ + struct ext4_block b; + r = ext4_trans_block_get_noread(ino_ref->fs->bdev, &b, new_fblk); + if (r != EOK) + return r; + + struct ext4_dir_idx_node *new_node = (void *)b.data; + struct ext4_dir_idx_entry *new_en = new_node->entries; + + memset(&new_node->fake, 0, sizeof(struct ext4_fake_dir_entry)); + new_node->fake.entry_length = block_size; + + /* Split leaf node */ + if (levels > 0) { + uint32_t count_left = leaf_count / 2; + uint32_t count_right = leaf_count - count_left; + uint32_t hash_right; + size_t sz; + + struct ext4_dir_idx_climit *left_climit; + struct ext4_dir_idx_climit *right_climit; + + hash_right = ext4_dir_dx_entry_get_hash(e + count_left); + /* Copy data to new node */ + sz = count_right * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e + count_left, sz); + + /* Initialize new node */ + left_climit = (struct ext4_dir_idx_climit *)e; + right_climit = (struct ext4_dir_idx_climit *)new_en; + + ext4_dir_dx_climit_set_count(left_climit, count_left); + ext4_dir_dx_climit_set_count(right_climit, count_right); + + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(right_climit, node_limit); + + /* Which index block is target for new entry */ + uint32_t position_index = + (dxb->position - dxb->entries); + if (position_index >= count_left) { + ext4_dir_set_dx_csum( + ino_ref, + (struct ext4_dir_en *) + dxb->b.data); + ext4_trans_set_block_dirty(dxb->b.buf); + + struct ext4_block block_tmp = dxb->b; + + dxb->b = b; + + dxb->position = + new_en + position_index - count_left; + dxb->entries = new_en; + + b = block_tmp; + } + + /* Finally insert new entry */ + ext4_dir_dx_insert_entry(ino_ref, dx_blks, hash_right, + new_iblk); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + + ext4_dir_set_dx_csum(ino_ref, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + return ext4_block_set(ino_ref->fs->bdev, &b); + } else { + size_t sz; + /* Copy data from root to child block */ + sz = leaf_count * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e, sz); + + struct ext4_dir_idx_climit *new_climit = (void*)new_en; + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(new_climit, node_limit); + + /* Set values in root node */ + struct ext4_dir_idx_climit *new_root_climit = (void *)e; + + ext4_dir_dx_climit_set_count(new_root_climit, 1); + ext4_dir_dx_entry_set_block(e, new_iblk); + + struct ext4_dir_idx_root *r = (void *)dx_blks[0].b.data; + r->info.indirect_levels = 1; + + /* Add new entry to the path */ + dxb = dx_blks + 1; + dxb->position = dx_blks->position - e + new_en; + dxb->entries = new_en; + dxb->b = b; + *new_dx_block = dxb; + + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + } + } + + return EOK; +} + +int ext4_dir_dx_add_entry(struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, uint32_t name_len) +{ + int rc2 = EOK; + int r; + /* Get direct block 0 (index root) */ + ext4_fsblk_t rblock_addr; + r = ext4_fs_get_inode_dblk_idx(parent, 0, &rblock_addr, false); + if (r != EOK) + return r; + + struct ext4_fs *fs = parent->fs; + struct ext4_block root_blk; + + r = ext4_trans_block_get(fs->bdev, &root_blk, rblock_addr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(parent, (void*)root_blk.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + (uint32_t)0); + } + + /* Initialize hinfo structure (mainly compute hash) */ + struct ext4_hash_info hinfo; + r = ext4_dir_hinfo_init(&hinfo, &root_blk, &fs->sb, name_len, name); + if (r != EOK) { + ext4_block_set(fs->bdev, &root_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index + * tree defined in Linux. + */ + struct ext4_dir_idx_block dx_blks[2]; + struct ext4_dir_idx_block *dx_blk; + struct ext4_dir_idx_block *dx_it; + + r = ext4_dir_dx_get_leaf(&hinfo, parent, &root_blk, &dx_blk, dx_blks); + if (r != EOK) { + r = EXT4_ERR_BAD_DX_DIR; + goto release_index; + } + + /* Try to insert to existing data block */ + uint32_t leaf_block_idx = ext4_dir_dx_entry_get_block(dx_blk->position); + ext4_fsblk_t leaf_block_addr; + r = ext4_fs_get_inode_dblk_idx(parent, leaf_block_idx, + &leaf_block_addr, false); + if (r != EOK) + goto release_index; + + /* + * Check if there is needed to split index node + * (and recursively also parent nodes) + */ + r = ext4_dir_dx_split_index(parent, dx_blks, dx_blk, &dx_blk); + if (r != EOK) + goto release_target_index; + + struct ext4_block target_block; + r = ext4_trans_block_get(fs->bdev, &target_block, leaf_block_addr); + if (r != EOK) + goto release_index; + + if (!ext4_dir_csum_verify(parent,(void *)target_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + leaf_block_idx); + } + + /* Check if insert operation passed */ + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, child, + name, name_len); + if (r == EOK) + goto release_target_index; + + /* Split entries to two blocks (includes sorting by hash value) */ + struct ext4_block new_block; + r = ext4_dir_dx_split_data(parent, &hinfo, &target_block, dx_blk, + &new_block); + if (r != EOK) { + rc2 = r; + goto release_target_index; + } + + /* Where to save new entry */ + uint32_t blk_hash = ext4_dir_dx_entry_get_hash(dx_blk->position + 1); + if (hinfo.hash >= blk_hash) + r = ext4_dir_try_insert_entry(&fs->sb, parent, &new_block, + child, name, name_len); + else + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, + child, name, name_len); + + /* Cleanup */ + r = ext4_block_set(fs->bdev, &new_block); + if (r != EOK) + return r; + +/* Cleanup operations */ + +release_target_index: + rc2 = r; + + r = ext4_block_set(fs->bdev, &target_block); + if (r != EOK) + return r; + +release_index: + if (r != EOK) + rc2 = r; + + dx_it = dx_blks; + + while (dx_it <= dx_blk) { + r = ext4_block_set(fs->bdev, &dx_it->b); + if (r != EOK) + return r; + + dx_it++; + } + + return rc2; +} + +int ext4_dir_dx_reset_parent_inode(struct ext4_inode_ref *dir, + uint32_t parent_inode) +{ + /* Load block 0, where will be index root located */ + ext4_fsblk_t fblock; + int rc = ext4_fs_get_inode_dblk_idx(dir, 0, &fblock, false); + if (rc != EOK) + return rc; + + struct ext4_block block; + rc = ext4_trans_block_get(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(dir, (void *)block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + dir->index, + (uint32_t)0); + } + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + + /* Fill the inode field with a new parent ino. */ + ext4_dx_dot_en_set_inode(&root->dots[1], parent_inode); + + ext4_dir_set_dx_csum(dir, (void *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_extent.c b/clib/lib/lwext4/src/ext4_extent.c new file mode 100644 index 0000000..abac59b --- /dev/null +++ b/clib/lib/lwext4/src/ext4_extent.c @@ -0,0 +1,2140 @@ +/* + * Copyright (c) 2017 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2017 Kaho Ng (ngkaho1234@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if CONFIG_EXTENTS_ENABLE +/* + * used by extent splitting. + */ +#define EXT4_EXT_MARK_UNWRIT1 0x02 /* mark first half unwritten */ +#define EXT4_EXT_MARK_UNWRIT2 0x04 /* mark second half unwritten */ +#define EXT4_EXT_DATA_VALID1 0x08 /* first half contains valid data */ +#define EXT4_EXT_DATA_VALID2 0x10 /* second half contains valid data */ +#define EXT4_EXT_NO_COMBINE 0x20 /* do not combine two extents */ + +#define EXT4_EXT_UNWRITTEN_MASK (1L << 15) + +#define EXT4_EXT_MAX_LEN_WRITTEN (1L << 15) +#define EXT4_EXT_MAX_LEN_UNWRITTEN \ + (EXT4_EXT_MAX_LEN_WRITTEN - 1) + +#define EXT4_EXT_GET_LEN(ex) to_le16((ex)->block_count) +#define EXT4_EXT_GET_LEN_UNWRITTEN(ex) \ + (EXT4_EXT_GET_LEN(ex) & ~(EXT4_EXT_UNWRITTEN_MASK)) +#define EXT4_EXT_SET_LEN(ex, count) \ + ((ex)->block_count = to_le16(count)) + +#define EXT4_EXT_IS_UNWRITTEN(ex) \ + (EXT4_EXT_GET_LEN(ex) > EXT4_EXT_MAX_LEN_WRITTEN) +#define EXT4_EXT_SET_UNWRITTEN(ex) \ + ((ex)->block_count |= to_le16(EXT4_EXT_UNWRITTEN_MASK)) +#define EXT4_EXT_SET_WRITTEN(ex) \ + ((ex)->block_count &= ~(to_le16(EXT4_EXT_UNWRITTEN_MASK))) + +/* + * Array of ext4_ext_path contains path to some extent. + * Creation/lookup routines use it for traversal/splitting/etc. + * Truncate uses it to simulate recursive walking. + */ +struct ext4_extent_path { + ext4_fsblk_t p_block; + struct ext4_block block; + int32_t depth; + int32_t maxdepth; + struct ext4_extent_header *header; + struct ext4_extent_index *index; + struct ext4_extent *extent; +}; + + +#pragma pack(push, 1) + +/* + * This is the extent tail on-disk structure. + * All other extent structures are 12 bytes long. It turns out that + * block_size % 12 >= 4 for at least all powers of 2 greater than 512, which + * covers all valid ext4 block sizes. Therefore, this tail structure can be + * crammed into the end of the block without having to rebalance the tree. + */ +struct ext4_extent_tail +{ + uint32_t et_checksum; /* crc32c(uuid+inum+extent_block) */ +}; + +/* + * This is the extent on-disk structure. + * It's used at the bottom of the tree. + */ +struct ext4_extent { + uint32_t first_block; /* First logical block extent covers */ + uint16_t block_count; /* Number of blocks covered by extent */ + uint16_t start_hi; /* High 16 bits of physical block */ + uint32_t start_lo; /* Low 32 bits of physical block */ +}; + +/* + * This is index on-disk structure. + * It's used at all the levels except the bottom. + */ +struct ext4_extent_index { + uint32_t first_block; /* Index covers logical blocks from 'block' */ + + /** + * Pointer to the physical block of the next + * level. leaf or next index could be there + * high 16 bits of physical block + */ + uint32_t leaf_lo; + uint16_t leaf_hi; + uint16_t padding; +}; + +/* + * Each block (leaves and indexes), even inode-stored has header. + */ +struct ext4_extent_header { + uint16_t magic; + uint16_t entries_count; /* Number of valid entries */ + uint16_t max_entries_count; /* Capacity of store in entries */ + uint16_t depth; /* Has tree real underlying blocks? */ + uint32_t generation; /* generation of the tree */ +}; + +#pragma pack(pop) + + +#define EXT4_EXTENT_MAGIC 0xF30A + +#define EXT4_EXTENT_FIRST(header) \ + ((struct ext4_extent *)(((char *)(header)) + \ + sizeof(struct ext4_extent_header))) + +#define EXT4_EXTENT_FIRST_INDEX(header) \ + ((struct ext4_extent_index *)(((char *)(header)) + \ + sizeof(struct ext4_extent_header))) + +/* + * EXT_INIT_MAX_LEN is the maximum number of blocks we can have in an + * initialized extent. This is 2^15 and not (2^16 - 1), since we use the + * MSB of ee_len field in the extent datastructure to signify if this + * particular extent is an initialized extent or an uninitialized (i.e. + * preallocated). + * EXT_UNINIT_MAX_LEN is the maximum number of blocks we can have in an + * uninitialized extent. + * If ee_len is <= 0x8000, it is an initialized extent. Otherwise, it is an + * uninitialized one. In other words, if MSB of ee_len is set, it is an + * uninitialized extent with only one special scenario when ee_len = 0x8000. + * In this case we can not have an uninitialized extent of zero length and + * thus we make it as a special case of initialized extent with 0x8000 length. + * This way we get better extent-to-group alignment for initialized extents. + * Hence, the maximum number of blocks we can have in an *initialized* + * extent is 2^15 (32768) and in an *uninitialized* extent is 2^15-1 (32767). + */ +#define EXT_INIT_MAX_LEN (1L << 15) +#define EXT_UNWRITTEN_MAX_LEN (EXT_INIT_MAX_LEN - 1) + +#define EXT_EXTENT_SIZE sizeof(struct ext4_extent) +#define EXT_INDEX_SIZE sizeof(struct ext4_extent_idx) + +#define EXT_FIRST_EXTENT(__hdr__) \ + ((struct ext4_extent *)(((char *)(__hdr__)) + \ + sizeof(struct ext4_extent_header))) +#define EXT_FIRST_INDEX(__hdr__) \ + ((struct ext4_extent_index *)(((char *)(__hdr__)) + \ + sizeof(struct ext4_extent_header))) +#define EXT_HAS_FREE_INDEX(__path__) \ + (to_le16((__path__)->header->entries_count) < \ + to_le16((__path__)->header->max_entries_count)) +#define EXT_LAST_EXTENT(__hdr__) \ + (EXT_FIRST_EXTENT((__hdr__)) + to_le16((__hdr__)->entries_count) - 1) +#define EXT_LAST_INDEX(__hdr__) \ + (EXT_FIRST_INDEX((__hdr__)) + to_le16((__hdr__)->entries_count) - 1) +#define EXT_MAX_EXTENT(__hdr__) \ + (EXT_FIRST_EXTENT((__hdr__)) + to_le16((__hdr__)->max_entries_count) - 1) +#define EXT_MAX_INDEX(__hdr__) \ + (EXT_FIRST_INDEX((__hdr__)) + to_le16((__hdr__)->max_entries_count) - 1) + +#define EXT4_EXTENT_TAIL_OFFSET(hdr) \ + (sizeof(struct ext4_extent_header) + \ + (sizeof(struct ext4_extent) * to_le16((hdr)->max_entries_count))) + + +/**@brief Get logical number of the block covered by extent. + * @param extent Extent to load number from + * @return Logical number of the first block covered by extent */ +static inline uint32_t ext4_extent_get_first_block(struct ext4_extent *extent) +{ + return to_le32(extent->first_block); +} + +/**@brief Set logical number of the first block covered by extent. + * @param extent Extent to set number to + * @param iblock Logical number of the first block covered by extent */ +static inline void ext4_extent_set_first_block(struct ext4_extent *extent, + uint32_t iblock) +{ + extent->first_block = to_le32(iblock); +} + +/**@brief Get number of blocks covered by extent. + * @param extent Extent to load count from + * @return Number of blocks covered by extent */ +static inline uint16_t ext4_extent_get_block_count(struct ext4_extent *extent) +{ + if (EXT4_EXT_IS_UNWRITTEN(extent)) + return EXT4_EXT_GET_LEN_UNWRITTEN(extent); + else + return EXT4_EXT_GET_LEN(extent); +} +/**@brief Set number of blocks covered by extent. + * @param extent Extent to load count from + * @param count Number of blocks covered by extent + * @param unwritten Whether the extent is unwritten or not */ +static inline void ext4_extent_set_block_count(struct ext4_extent *extent, + uint16_t count, bool unwritten) +{ + EXT4_EXT_SET_LEN(extent, count); + if (unwritten) + EXT4_EXT_SET_UNWRITTEN(extent); +} + +/**@brief Get physical number of the first block covered by extent. + * @param extent Extent to load number + * @return Physical number of the first block covered by extent */ +static inline uint64_t ext4_extent_get_start(struct ext4_extent *extent) +{ + return ((uint64_t)to_le16(extent->start_hi)) << 32 | + ((uint64_t)to_le32(extent->start_lo)); +} + + +/**@brief Set physical number of the first block covered by extent. + * @param extent Extent to load number + * @param fblock Physical number of the first block covered by extent */ +static inline void ext4_extent_set_start(struct ext4_extent *extent, uint64_t fblock) +{ + extent->start_lo = to_le32((fblock << 32) >> 32); + extent->start_hi = to_le16((uint16_t)(fblock >> 32)); +} + + +/**@brief Get logical number of the block covered by extent index. + * @param index Extent index to load number from + * @return Logical number of the first block covered by extent index */ +static inline uint32_t +ext4_extent_index_get_first_block(struct ext4_extent_index *index) +{ + return to_le32(index->first_block); +} + +/**@brief Set logical number of the block covered by extent index. + * @param index Extent index to set number to + * @param iblock Logical number of the first block covered by extent index */ +static inline void +ext4_extent_index_set_first_block(struct ext4_extent_index *index, + uint32_t iblock) +{ + index->first_block = to_le32(iblock); +} + +/**@brief Get physical number of block where the child node is located. + * @param index Extent index to load number from + * @return Physical number of the block with child node */ +static inline uint64_t +ext4_extent_index_get_leaf(struct ext4_extent_index *index) +{ + return ((uint64_t)to_le16(index->leaf_hi)) << 32 | + ((uint64_t)to_le32(index->leaf_lo)); +} + +/**@brief Set physical number of block where the child node is located. + * @param index Extent index to set number to + * @param fblock Ohysical number of the block with child node */ +static inline void ext4_extent_index_set_leaf(struct ext4_extent_index *index, + uint64_t fblock) +{ + index->leaf_lo = to_le32((fblock << 32) >> 32); + index->leaf_hi = to_le16((uint16_t)(fblock >> 32)); +} + +/**@brief Get magic value from extent header. + * @param header Extent header to load value from + * @return Magic value of extent header */ +static inline uint16_t +ext4_extent_header_get_magic(struct ext4_extent_header *header) +{ + return to_le16(header->magic); +} + +/**@brief Set magic value to extent header. + * @param header Extent header to set value to + * @param magic Magic value of extent header */ +static inline void ext4_extent_header_set_magic(struct ext4_extent_header *header, + uint16_t magic) +{ + header->magic = to_le16(magic); +} + +/**@brief Get number of entries from extent header + * @param header Extent header to get value from + * @return Number of entries covered by extent header */ +static inline uint16_t +ext4_extent_header_get_entries_count(struct ext4_extent_header *header) +{ + return to_le16(header->entries_count); +} + +/**@brief Set number of entries to extent header + * @param header Extent header to set value to + * @param count Number of entries covered by extent header */ +static inline void +ext4_extent_header_set_entries_count(struct ext4_extent_header *header, + uint16_t count) +{ + header->entries_count = to_le16(count); +} + +/**@brief Get maximum number of entries from extent header + * @param header Extent header to get value from + * @return Maximum number of entries covered by extent header */ +static inline uint16_t +ext4_extent_header_get_max_entries_count(struct ext4_extent_header *header) +{ + return to_le16(header->max_entries_count); +} + +/**@brief Set maximum number of entries to extent header + * @param header Extent header to set value to + * @param max_count Maximum number of entries covered by extent header */ +static inline void +ext4_extent_header_set_max_entries_count(struct ext4_extent_header *header, + uint16_t max_count) +{ + header->max_entries_count = to_le16(max_count); +} + +/**@brief Get depth of extent subtree. + * @param header Extent header to get value from + * @return Depth of extent subtree */ +static inline uint16_t +ext4_extent_header_get_depth(struct ext4_extent_header *header) +{ + return to_le16(header->depth); +} + +/**@brief Set depth of extent subtree. + * @param header Extent header to set value to + * @param depth Depth of extent subtree */ +static inline void +ext4_extent_header_set_depth(struct ext4_extent_header *header, uint16_t depth) +{ + header->depth = to_le16(depth); +} + +/**@brief Get generation from extent header + * @param header Extent header to get value from + * @return Generation */ +static inline uint32_t +ext4_extent_header_get_generation(struct ext4_extent_header *header) +{ + return to_le32(header->generation); +} + +/**@brief Set generation to extent header + * @param header Extent header to set value to + * @param generation Generation */ +static inline void +ext4_extent_header_set_generation(struct ext4_extent_header *header, + uint32_t generation) +{ + header->generation = to_le32(generation); +} + +void ext4_extent_tree_init(struct ext4_inode_ref *inode_ref) +{ + /* Initialize extent root header */ + struct ext4_extent_header *header = + ext4_inode_get_extent_header(inode_ref->inode); + ext4_extent_header_set_depth(header, 0); + ext4_extent_header_set_entries_count(header, 0); + ext4_extent_header_set_generation(header, 0); + ext4_extent_header_set_magic(header, EXT4_EXTENT_MAGIC); + + uint16_t max_entries = (EXT4_INODE_BLOCKS * sizeof(uint32_t) - + sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); + + ext4_extent_header_set_max_entries_count(header, max_entries); + inode_ref->dirty = true; +} + + +static struct ext4_extent_tail * +find_ext4_extent_tail(struct ext4_extent_header *eh) +{ + return (struct ext4_extent_tail *)(((char *)eh) + + EXT4_EXTENT_TAIL_OFFSET(eh)); +} + +static struct ext4_extent_header *ext_inode_hdr(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +static struct ext4_extent_header *ext_block_hdr(struct ext4_block *block) +{ + return (struct ext4_extent_header *)block->data; +} + +static uint16_t ext_depth(struct ext4_inode *inode) +{ + return to_le16(ext_inode_hdr(inode)->depth); +} + +static uint16_t ext4_ext_get_actual_len(struct ext4_extent *ext) +{ + return (to_le16(ext->block_count) <= EXT_INIT_MAX_LEN + ? to_le16(ext->block_count) + : (to_le16(ext->block_count) - EXT_INIT_MAX_LEN)); +} + +static void ext4_ext_mark_initialized(struct ext4_extent *ext) +{ + ext->block_count = to_le16(ext4_ext_get_actual_len(ext)); +} + +static void ext4_ext_mark_unwritten(struct ext4_extent *ext) +{ + ext->block_count |= to_le16(EXT_INIT_MAX_LEN); +} + +static int ext4_ext_is_unwritten(struct ext4_extent *ext) +{ + /* Extent with ee_len of 0x8000 is treated as an initialized extent */ + return (to_le16(ext->block_count) > EXT_INIT_MAX_LEN); +} + +/* + * ext4_ext_pblock: + * combine low and high parts of physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_ext_pblock(struct ext4_extent *ex) +{ + ext4_fsblk_t block; + + block = to_le32(ex->start_lo); + block |= ((ext4_fsblk_t)to_le16(ex->start_hi) << 31) << 1; + return block; +} + +/* + * ext4_idx_pblock: + * combine low and high parts of a leaf physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_idx_pblock(struct ext4_extent_index *ix) +{ + ext4_fsblk_t block; + + block = to_le32(ix->leaf_lo); + block |= ((ext4_fsblk_t)to_le16(ix->leaf_hi) << 31) << 1; + return block; +} + +/* + * ext4_ext_store_pblock: + * stores a large physical block number into an extent struct, + * breaking it into parts + */ +static void ext4_ext_store_pblock(struct ext4_extent *ex, ext4_fsblk_t pb) +{ + ex->start_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ex->start_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +/* + * ext4_idx_store_pblock: + * stores a large physical block number into an index struct, + * breaking it into parts + */ +static void ext4_idx_store_pblock(struct ext4_extent_index *ix, ext4_fsblk_t pb) +{ + ix->leaf_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ix->leaf_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +static int ext4_allocate_single_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, ext4_fsblk_t *blockp) +{ + return ext4_balloc_alloc_block(inode_ref, goal, blockp); +} + +static ext4_fsblk_t ext4_new_meta_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + uint32_t flags __unused, + uint32_t *count, int *errp) +{ + ext4_fsblk_t block = 0; + + *errp = ext4_allocate_single_block(inode_ref, goal, &block); + if (count) + *count = 1; + return block; +} + +static void ext4_ext_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, uint32_t count, + uint32_t flags __unused) +{ + ext4_balloc_free_blocks(inode_ref, block, count); +} + +static uint16_t ext4_ext_space_block(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 6) + size = 6; +#endif + return size; +} + +static uint16_t ext4_ext_space_block_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 5) + size = 5; +#endif + return size; +} + +static uint16_t ext4_ext_space_root(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 3) + size = 3; +#endif + return size; +} + +static uint16_t ext4_ext_space_root_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 4) + size = 4; +#endif + return size; +} + +static uint16_t ext4_ext_max_entries(struct ext4_inode_ref *inode_ref, + uint32_t depth) +{ + uint16_t max; + + if (depth == ext_depth(inode_ref->inode)) { + if (depth == 0) + max = ext4_ext_space_root(inode_ref); + else + max = ext4_ext_space_root_idx(inode_ref); + } else { + if (depth == 0) + max = ext4_ext_space_block(inode_ref); + else + max = ext4_ext_space_block_idx(inode_ref); + } + + return max; +} + +static ext4_fsblk_t ext4_ext_find_goal(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + ext4_lblk_t block) +{ + if (path) { + uint32_t depth = path->depth; + struct ext4_extent *ex; + + /* + * Try to predict block placement assuming that we are + * filling in a file which will eventually be + * non-sparse --- i.e., in the case of libbfd writing + * an ELF object sections out-of-order but in a way + * the eventually results in a contiguous object or + * executable file, or some database extending a table + * space file. However, this is actually somewhat + * non-ideal if we are writing a sparse file such as + * qemu or KVM writing a raw image file that is going + * to stay fairly sparse, since it will end up + * fragmenting the file system's free space. Maybe we + * should have some hueristics or some way to allow + * userspace to pass a hint to file system, + * especially if the latter case turns out to be + * common. + */ + ex = path[depth].extent; + if (ex) { + ext4_fsblk_t ext_pblk = ext4_ext_pblock(ex); + ext4_lblk_t ext_block = to_le32(ex->first_block); + + if (block > ext_block) + return ext_pblk + (block - ext_block); + else + return ext_pblk - (ext_block - block); + } + + /* it looks like index is empty; + * try to find starting block from index itself */ + if (path[depth].block.lb_id) + return path[depth].block.lb_id; + } + + /* OK. use inode's group */ + return ext4_fs_inode_to_goal_block(inode_ref); +} + +/* + * Allocation for a meta data block + */ +static ext4_fsblk_t ext4_ext_new_meta_block(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext4_extent *ex, int *err, + uint32_t flags) +{ + ext4_fsblk_t goal, newblock; + + goal = ext4_ext_find_goal(inode_ref, path, to_le32(ex->first_block)); + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, err); + return newblock; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_ext_block_csum(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh) +{ + uint32_t checksum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = + to_le32(ext4_inode_get_generation(inode_ref->inode)); + /* First calculate crc32 checksum against fs uuid */ + checksum = + ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + checksum = ext4_crc32c(checksum, &ino_index, sizeof(ino_index)); + checksum = ext4_crc32c(checksum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against + * the entire extent block up to the checksum field */ + checksum = + ext4_crc32c(checksum, eh, EXT4_EXTENT_TAIL_OFFSET(eh)); + } + return checksum; +} +#else +#define ext4_ext_block_csum(...) 0 +#endif + +static void +ext4_extent_block_csum_set(struct ext4_inode_ref *inode_ref __unused, + struct ext4_extent_header *eh) +{ + struct ext4_extent_tail *tail; + + tail = find_ext4_extent_tail(eh); + tail->et_checksum = to_le32(ext4_ext_block_csum(inode_ref, eh)); +} + +static int ext4_ext_dirty(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + if (path->block.lb_id) + ext4_trans_set_block_dirty(path->block.buf); + else + inode_ref->dirty = true; + + return EOK; +} + +static void ext4_ext_drop_refs(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, bool keep_other) +{ + int32_t depth, i; + + if (!path) + return; + if (keep_other) + depth = 0; + else + depth = path->depth; + + for (i = 0; i <= depth; i++, path++) { + if (path->block.lb_id) { + if (ext4_bcache_test_flag(path->block.buf, BC_DIRTY)) + ext4_extent_block_csum_set(inode_ref, + path->header); + + ext4_block_set(inode_ref->fs->bdev, &path->block); + } + } +} + +/* + * Check that whether the basic information inside the extent header + * is correct or not. + */ +static int ext4_ext_check(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, uint16_t depth, + ext4_fsblk_t pblk __unused) +{ + struct ext4_extent_tail *tail; + struct ext4_sblock *sb = &inode_ref->fs->sb; + const char *error_msg; + (void)error_msg; + + if (to_le16(eh->magic) != EXT4_EXTENT_MAGIC) { + error_msg = "invalid magic"; + goto corrupted; + } + if (to_le16(eh->depth) != depth) { + error_msg = "unexpected eh_depth"; + goto corrupted; + } + if (eh->max_entries_count == 0) { + error_msg = "invalid eh_max"; + goto corrupted; + } + if (to_le16(eh->entries_count) > to_le16(eh->max_entries_count)) { + error_msg = "invalid eh_entries"; + goto corrupted; + } + + tail = find_ext4_extent_tail(eh); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + if (tail->et_checksum != + to_le32(ext4_ext_block_csum(inode_ref, eh))) { + ext4_dbg(DEBUG_EXTENT, + DBG_WARN "Extent block checksum failed." + "Blocknr: %" PRIu64 "\n", + pblk); + } + } + + return EOK; + +corrupted: + ext4_dbg(DEBUG_EXTENT, "Bad extents B+ tree block: %s. " + "Blocknr: %" PRId64 "\n", + error_msg, pblk); + return EIO; +} + +static int read_extent_tree_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t pblk, int32_t depth, + struct ext4_block *bh, + uint32_t flags __unused) +{ + int err; + + err = ext4_trans_block_get(inode_ref->fs->bdev, bh, pblk); + if (err != EOK) + goto errout; + + err = ext4_ext_check(inode_ref, ext_block_hdr(bh), depth, pblk); + if (err != EOK) + goto errout; + + return EOK; +errout: + if (bh->lb_id) + ext4_block_set(inode_ref->fs->bdev, bh); + + return err; +} + +/* + * ext4_ext_binsearch_idx: + * binary search for the closest index of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch_idx(struct ext4_extent_path *path, + ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent_index *r, *l, *m; + + l = EXT_FIRST_INDEX(eh) + 1; + r = EXT_LAST_INDEX(eh); + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->index = l - 1; +} + +/* + * ext4_ext_binsearch: + * binary search for closest extent of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch(struct ext4_extent_path *path, ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent *r, *l, *m; + + if (eh->entries_count == 0) { + /* + * this leaf is empty: + * we get such a leaf in split/add case + */ + return; + } + + l = EXT_FIRST_EXTENT(eh) + 1; + r = EXT_LAST_EXTENT(eh); + + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->extent = l - 1; +} + +static int ext4_find_extent(struct ext4_inode_ref *inode_ref, ext4_lblk_t block, + struct ext4_extent_path **orig_path, uint32_t flags) +{ + struct ext4_extent_header *eh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t buf_block = 0; + struct ext4_extent_path *path = *orig_path; + int32_t depth, ppos = 0; + int32_t i; + int ret; + + eh = ext_inode_hdr(inode_ref->inode); + depth = ext_depth(inode_ref->inode); + + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + if (depth > path[0].maxdepth) { + ext4_free(path); + *orig_path = path = NULL; + } + } + if (!path) { + int32_t path_depth = depth + 1; + /* account possible depth increase */ + path = ext4_calloc(1, sizeof(struct ext4_extent_path) * + (path_depth + 1)); + if (!path) + return ENOMEM; + path[0].maxdepth = path_depth; + } + path[0].header = eh; + path[0].block = bh; + + i = depth; + /* walk through the tree */ + while (i) { + ext4_ext_binsearch_idx(path + ppos, block); + path[ppos].p_block = ext4_idx_pblock(path[ppos].index); + path[ppos].depth = i; + path[ppos].extent = NULL; + buf_block = path[ppos].p_block; + + i--; + ppos++; + if (!path[ppos].block.lb_id || + path[ppos].block.lb_id != buf_block) { + ret = read_extent_tree_block(inode_ref, buf_block, i, + &bh, flags); + if (ret != EOK) { + goto err; + } + if (ppos > depth) { + ext4_block_set(inode_ref->fs->bdev, &bh); + ret = EIO; + goto err; + } + + eh = ext_block_hdr(&bh); + path[ppos].block = bh; + path[ppos].header = eh; + } + } + + path[ppos].depth = i; + path[ppos].extent = NULL; + path[ppos].index = NULL; + + /* find extent */ + ext4_ext_binsearch(path + ppos, block); + /* if not an empty leaf */ + if (path[ppos].extent) + path[ppos].p_block = ext4_ext_pblock(path[ppos].extent); + + *orig_path = path; + + ret = EOK; + return ret; + +err: + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + if (orig_path) + *orig_path = NULL; + return ret; +} + +static void ext4_ext_init_header(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, int32_t depth) +{ + eh->entries_count = 0; + eh->max_entries_count = to_le16(ext4_ext_max_entries(inode_ref, depth)); + eh->magic = to_le16(EXT4_EXTENT_MAGIC); + eh->depth = depth; +} + +static int ext4_ext_insert_index(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + ext4_lblk_t insert_index, + ext4_fsblk_t insert_block, bool set_to_ix) +{ + struct ext4_extent_index *ix; + struct ext4_extent_path *curp = path + at; + int len, err; + struct ext4_extent_header *eh; + + if (curp->index && insert_index == to_le32(curp->index->first_block)) + return EIO; + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) + return EIO; + + eh = curp->header; + if (curp->index == NULL) { + ix = EXT_FIRST_INDEX(eh); + curp->index = ix; + } else if (insert_index > to_le32(curp->index->first_block)) { + /* insert after */ + ix = curp->index + 1; + } else { + /* insert before */ + ix = curp->index; + } + + if (ix > EXT_MAX_INDEX(eh)) + return EIO; + + len = EXT_LAST_INDEX(eh) - ix + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ix + 1, ix, len * sizeof(struct ext4_extent_index)); + + ix->first_block = to_le32(insert_index); + ext4_idx_store_pblock(ix, insert_block); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ix > EXT_LAST_INDEX(eh)) { + err = EIO; + goto out; + } + + err = ext4_ext_dirty(inode_ref, curp); + +out: + if (err == EOK && set_to_ix) { + curp->index = ix; + curp->p_block = ext4_idx_pblock(ix); + } + return err; +} + +static int ext4_ext_split_node(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + struct ext4_extent *newext, + struct ext4_extent_path *npath, + bool *ins_right_leaf) +{ + int i, npath_at, ret; + ext4_lblk_t insert_index; + ext4_fsblk_t newblock = 0; + int depth = ext_depth(inode_ref->inode); + npath_at = depth - at; + + ext4_assert(at > 0); + + if (path[depth].extent != EXT_MAX_EXTENT(path[depth].header)) + insert_index = path[depth].extent[1].first_block; + else + insert_index = newext->first_block; + + for (i = depth; i >= at; i--, npath_at--) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + + /* FIXME: currently we split at the point after the current + * extent. */ + newblock = + ext4_ext_new_meta_block(inode_ref, path, newext, &ret, 0); + if (ret != EOK) + goto cleanup; + + /* For write access.*/ + ret = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, + newblock); + if (ret != EOK) + goto cleanup; + + if (i == depth) { + /* start copy from next extent */ + int m = EXT_MAX_EXTENT(path[i].header) - path[i].extent; + struct ext4_extent_header *neh; + struct ext4_extent *ex; + neh = ext_block_hdr(&bh); + ex = EXT_FIRST_EXTENT(neh); + ext4_ext_init_header(inode_ref, neh, 0); + if (m) { + memmove(ex, path[i].extent + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[i].header->entries_count = to_le16( + to_le16(path[i].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + i); + if (ret != EOK) + goto cleanup; + + npath[npath_at].p_block = ext4_ext_pblock(ex); + npath[npath_at].extent = ex; + } else { + npath[npath_at].p_block = 0; + npath[npath_at].extent = NULL; + } + + npath[npath_at].depth = to_le16(neh->depth); + npath[npath_at].maxdepth = 0; + npath[npath_at].index = NULL; + npath[npath_at].header = neh; + npath[npath_at].block = bh; + + ext4_trans_set_block_dirty(bh.buf); + } else { + int m = EXT_MAX_INDEX(path[i].header) - path[i].index; + struct ext4_extent_header *neh; + struct ext4_extent_index *ix; + neh = ext_block_hdr(&bh); + ix = EXT_FIRST_INDEX(neh); + ext4_ext_init_header(inode_ref, neh, depth - i); + ix->first_block = to_le32(insert_index); + ext4_idx_store_pblock(ix, + npath[npath_at + 1].block.lb_id); + neh->entries_count = to_le16(1); + if (m) { + memmove(ix + 1, path[i].index + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[i].header->entries_count = to_le16( + to_le16(path[i].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + i); + if (ret != EOK) + goto cleanup; + } + + npath[npath_at].p_block = ext4_idx_pblock(ix); + npath[npath_at].depth = to_le16(neh->depth); + npath[npath_at].maxdepth = 0; + npath[npath_at].extent = NULL; + npath[npath_at].index = ix; + npath[npath_at].header = neh; + npath[npath_at].block = bh; + + ext4_trans_set_block_dirty(bh.buf); + } + } + newblock = 0; + + /* + * If newext->first_block can be included into the + * right sub-tree. + */ + if (to_le32(newext->first_block) < insert_index) + *ins_right_leaf = false; + else + *ins_right_leaf = true; + + ret = ext4_ext_insert_index(inode_ref, path, at - 1, insert_index, + npath[0].block.lb_id, *ins_right_leaf); + +cleanup: + if (ret != EOK) { + if (newblock) + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + + npath_at = depth - at; + while (npath_at >= 0) { + if (npath[npath_at].block.lb_id) { + newblock = npath[npath_at].block.lb_id; + ext4_block_set(inode_ref->fs->bdev, + &npath[npath_at].block); + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + memset(&npath[npath_at].block, 0, + sizeof(struct ext4_block)); + } + npath_at--; + } + } + return ret; +} + +/* + * ext4_ext_correct_indexes: + * if leaf gets modified and modified extent is first in the leaf, + * then we have to correct all indexes above. + */ +static int ext4_ext_correct_indexes(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + struct ext4_extent_header *eh; + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex; + uint32_t border; + int32_t k; + int err = EOK; + + eh = path[depth].header; + ex = path[depth].extent; + + if (ex == NULL || eh == NULL) + return EIO; + + if (depth == 0) { + /* there is no tree at all */ + return EOK; + } + + if (ex != EXT_FIRST_EXTENT(eh)) { + /* we correct tree if first leaf got modified only */ + return EOK; + } + + k = depth - 1; + border = path[depth].extent->first_block; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + return err; + + while (k--) { + /* change all left-side indexes */ + if (path[k + 1].index != EXT_FIRST_INDEX(path[k + 1].header)) + break; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + break; + } + + return err; +} + +static inline bool ext4_ext_can_prepend(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex2) + ext4_ext_get_actual_len(ex2) != + ext4_ext_pblock(ex1)) + return 0; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return 0; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return 0; +#endif + + if (to_le32(ex2->first_block) + ext4_ext_get_actual_len(ex2) != + to_le32(ex1->first_block)) + return 0; + + return 1; +} + +static inline bool ext4_ext_can_append(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex1) + ext4_ext_get_actual_len(ex1) != + ext4_ext_pblock(ex2)) + return 0; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return 0; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return 0; +#endif + + if (to_le32(ex1->first_block) + ext4_ext_get_actual_len(ex1) != + to_le32(ex2->first_block)) + return 0; + + return 1; +} + +static int ext4_ext_insert_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + struct ext4_extent *newext, int flags, + bool *need_split) +{ + struct ext4_extent_path *curp = path + at; + struct ext4_extent *ex = curp->extent; + int len, err, unwritten; + struct ext4_extent_header *eh; + + *need_split = false; + + if (curp->extent && + to_le32(newext->first_block) == to_le32(curp->extent->first_block)) + return EIO; + + if (!(flags & EXT4_EXT_NO_COMBINE)) { + if (curp->extent && ext4_ext_can_append(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + + if (curp->extent && + ext4_ext_can_prepend(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->first_block = newext->first_block; + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + } + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) { + err = EIO; + *need_split = true; + goto out; + } else { + eh = curp->header; + if (curp->extent == NULL) { + ex = EXT_FIRST_EXTENT(eh); + curp->extent = ex; + } else if (to_le32(newext->first_block) > + to_le32(curp->extent->first_block)) { + /* insert after */ + ex = curp->extent + 1; + } else { + /* insert before */ + ex = curp->extent; + } + } + + len = EXT_LAST_EXTENT(eh) - ex + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ex + 1, ex, len * sizeof(struct ext4_extent)); + + if (ex > EXT_MAX_EXTENT(eh)) { + err = EIO; + goto out; + } + + ex->first_block = newext->first_block; + ex->block_count = newext->block_count; + ext4_ext_store_pblock(ex, ext4_ext_pblock(newext)); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ex > EXT_LAST_EXTENT(eh)) { + err = EIO; + goto out; + } + + err = ext4_ext_correct_indexes(inode_ref, path); + if (err != EOK) + goto out; + err = ext4_ext_dirty(inode_ref, curp); + +out: + if (err == EOK) { + curp->extent = ex; + curp->p_block = ext4_ext_pblock(ex); + } + + return err; +} + +/* + * ext4_ext_grow_indepth: + * implements tree growing procedure: + * - allocates new block + * - moves top-level data (index block or leaf) into the new block + * - initializes new top-level, creating index that points to the + * just created block + */ +static int ext4_ext_grow_indepth(struct ext4_inode_ref *inode_ref, + uint32_t flags) +{ + struct ext4_extent_header *neh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t newblock, goal = 0; + int err = EOK; + + /* Try to prepend new index to old one */ + if (ext_depth(inode_ref->inode)) + goal = ext4_idx_pblock( + EXT_FIRST_INDEX(ext_inode_hdr(inode_ref->inode))); + else + goal = ext4_fs_inode_to_goal_block(inode_ref); + + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, &err); + if (newblock == 0) + return err; + + /* # */ + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, newblock); + if (err != EOK) { + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + return err; + } + + /* move top-level index/leaf into new block */ + memmove(bh.data, inode_ref->inode->blocks, + sizeof(inode_ref->inode->blocks)); + + /* set size of new block */ + neh = ext_block_hdr(&bh); + /* old root could have indexes or leaves + * so calculate e_max right way */ + if (ext_depth(inode_ref->inode)) + neh->max_entries_count = + to_le16(ext4_ext_space_block_idx(inode_ref)); + else + neh->max_entries_count = + to_le16(ext4_ext_space_block(inode_ref)); + + neh->magic = to_le16(EXT4_EXTENT_MAGIC); + ext4_extent_block_csum_set(inode_ref, neh); + + /* Update top-level index: num,max,pointer */ + neh = ext_inode_hdr(inode_ref->inode); + neh->entries_count = to_le16(1); + ext4_idx_store_pblock(EXT_FIRST_INDEX(neh), newblock); + if (neh->depth == 0) { + /* Root extent block becomes index block */ + neh->max_entries_count = + to_le16(ext4_ext_space_root_idx(inode_ref)); + EXT_FIRST_INDEX(neh) + ->first_block = EXT_FIRST_EXTENT(neh)->first_block; + } + neh->depth = to_le16(to_le16(neh->depth) + 1); + + ext4_trans_set_block_dirty(bh.buf); + inode_ref->dirty = true; + ext4_block_set(inode_ref->fs->bdev, &bh); + + return err; +} + +static inline void ext4_ext_replace_path(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext4_extent_path *newpath, + int at) +{ + ext4_ext_drop_refs(inode_ref, path + at, 1); + path[at] = *newpath; + memset(newpath, 0, sizeof(struct ext4_extent_path)); +} + +int ext4_ext_insert_extent(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + struct ext4_extent *newext, int flags) +{ + int depth, level = 0, ret = 0; + struct ext4_extent_path *path = *ppath; + struct ext4_extent_path *npath = NULL; + bool ins_right_leaf = false; + bool need_split; + +again: + depth = ext_depth(inode_ref->inode); + ret = ext4_ext_insert_leaf(inode_ref, path, depth, newext, flags, + &need_split); + if (ret == EIO && need_split == true) { + int i; + for (i = depth, level = 0; i >= 0; i--, level++) + if (EXT_HAS_FREE_INDEX(path + i)) + break; + + /* Do we need to grow the tree? */ + if (i < 0) { + ret = ext4_ext_grow_indepth(inode_ref, 0); + if (ret != EOK) + goto out; + + ret = ext4_find_extent( + inode_ref, to_le32(newext->first_block), ppath, 0); + if (ret != EOK) + goto out; + + path = *ppath; + /* + * After growing the tree, there should be free space in + * the only child node of the root. + */ + level--; + depth++; + } + + i = depth - (level - 1); + /* We split from leaf to the i-th node */ + if (level > 0) { + npath = ext4_calloc(1, sizeof(struct ext4_extent_path) * + (level)); + if (!npath) { + ret = ENOMEM; + goto out; + } + ret = ext4_ext_split_node(inode_ref, path, i, newext, + npath, &ins_right_leaf); + if (ret != EOK) + goto out; + + while (--level >= 0) { + if (ins_right_leaf) + ext4_ext_replace_path(inode_ref, path, + &npath[level], + i + level); + else if (npath[level].block.lb_id) + ext4_ext_drop_refs(inode_ref, + npath + level, 1); + } + } + goto again; + } + +out: + if (ret != EOK) { + if (path) + ext4_ext_drop_refs(inode_ref, path, 0); + + while (--level >= 0 && npath) { + if (npath[level].block.lb_id) { + ext4_fsblk_t block = npath[level].block.lb_id; + ext4_ext_free_blocks(inode_ref, block, 1, 0); + ext4_ext_drop_refs(inode_ref, npath + level, 1); + } + } + } + if (npath) + ext4_free(npath); + + return ret; +} + +static void ext4_ext_remove_blocks(struct ext4_inode_ref *inode_ref, + struct ext4_extent *ex, ext4_lblk_t from, + ext4_lblk_t to) +{ + ext4_lblk_t len = to - from + 1; + ext4_lblk_t num; + ext4_fsblk_t start; + num = from - to_le32(ex->first_block); + start = ext4_ext_pblock(ex) + num; + ext4_dbg(DEBUG_EXTENT, + "Freeing %" PRIu32 " at %" PRIu64 ", %" PRIu32 "\n", from, + start, len); + + ext4_ext_free_blocks(inode_ref, start, len, 0); +} + +static int ext4_ext_remove_idx(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int32_t depth) +{ + int err = EOK; + int32_t i = depth; + ext4_fsblk_t leaf; + + /* free index block */ + leaf = ext4_idx_pblock(path[i].index); + + if (path[i].index != EXT_LAST_INDEX(path[i].header)) { + ptrdiff_t len = EXT_LAST_INDEX(path[i].header) - path[i].index; + memmove(path[i].index, path[i].index + 1, + len * sizeof(struct ext4_extent_index)); + } + + path[i].header->entries_count = + to_le16(to_le16(path[i].header->entries_count) - 1); + err = ext4_ext_dirty(inode_ref, path + i); + if (err != EOK) + return err; + + ext4_dbg(DEBUG_EXTENT, "IDX: Freeing %" PRIu32 " at %" PRIu64 ", %d\n", + to_le32(path[i].index->first_block), leaf, 1); + ext4_ext_free_blocks(inode_ref, leaf, 1, 0); + + /* + * We may need to correct the paths after the first extents/indexes in + * a node being modified. + * + * We do not need to consider whether there's any extents presenting or + * not, as garbage will be cleared soon. + */ + while (i > 0) { + if (path[i].index != EXT_FIRST_INDEX(path[i].header)) + break; + + path[i - 1].index->first_block = path[i].index->first_block; + err = ext4_ext_dirty(inode_ref, path + i - 1); + if (err != EOK) + break; + + i--; + } + return err; +} + +static int ext4_ext_remove_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, ext4_lblk_t from, + ext4_lblk_t to) +{ + + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex = path[depth].extent; + struct ext4_extent *start_ex, *ex2 = NULL; + struct ext4_extent_header *eh = path[depth].header; + int32_t len; + int err = EOK; + uint16_t new_entries; + + start_ex = ex; + new_entries = to_le16(eh->entries_count); + while (ex <= EXT_LAST_EXTENT(path[depth].header) && + to_le32(ex->first_block) <= to) { + int32_t new_len = 0; + int unwritten; + ext4_lblk_t start, new_start; + ext4_fsblk_t newblock; + new_start = start = to_le32(ex->first_block); + len = ext4_ext_get_actual_len(ex); + newblock = ext4_ext_pblock(ex); + /* + * The 1st case: + * The position that we start truncation is inside the range of an + * extent. Here we should calculate the new length of that extent and + * may start the removal from the next extent. + */ + if (start < from) { + len -= from - start; + new_len = from - start; + start = from; + start_ex++; + } else { + /* + * The second case: + * The last block to be truncated is inside the range of an + * extent. We need to calculate the new length and the new + * start of the extent. + */ + if (start + len - 1 > to) { + new_len = start + len - 1 - to; + len -= new_len; + new_start = to + 1; + newblock += to + 1 - start; + ex2 = ex; + } + } + + ext4_ext_remove_blocks(inode_ref, ex, start, start + len - 1); + /* + * Set the first block of the extent if it is presented. + */ + ex->first_block = to_le32(new_start); + + /* + * If the new length of the current extent we are working on is + * zero, remove it. + */ + if (!new_len) + new_entries--; + else { + unwritten = ext4_ext_is_unwritten(ex); + ex->block_count = to_le16(new_len); + ext4_ext_store_pblock(ex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(ex); + } + + ex += 1; + } + + if (ex2 == NULL) + ex2 = ex; + + /* + * Move any remaining extents to the starting position of the node. + */ + if (ex2 <= EXT_LAST_EXTENT(eh)) + memmove(start_ex, ex2, (EXT_LAST_EXTENT(eh) - ex2 + 1) * + sizeof(struct ext4_extent)); + + eh->entries_count = to_le16(new_entries); + ext4_ext_dirty(inode_ref, path + depth); + + /* + * If the extent pointer is pointed to the first extent of the node, and + * there's still extents presenting, we may need to correct the indexes + * of the paths. + */ + if (path[depth].extent == EXT_FIRST_EXTENT(eh) && eh->entries_count) { + err = ext4_ext_correct_indexes(inode_ref, path); + if (err != EOK) + return err; + } + + /* if this leaf is free, then we should + * remove it from index block above */ + if (eh->entries_count == 0 && path[depth].block.lb_id) + err = ext4_ext_remove_idx(inode_ref, path, depth - 1); + else if (depth > 0) + path[depth - 1].index++; + + return err; +} + +/* + * Check if there's more to remove at a specific level. + */ +static bool ext4_ext_more_to_rm(struct ext4_extent_path *path, ext4_lblk_t to) +{ + if (!to_le16(path->header->entries_count)) + return false; + + if (path->index > EXT_LAST_INDEX(path->header)) + return false; + + if (to_le32(path->index->first_block) > to) + return false; + + return true; +} + +int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from, + ext4_lblk_t to) +{ + struct ext4_extent_path *path = NULL; + int ret = EOK; + int32_t depth = ext_depth(inode_ref->inode); + int32_t i; + + ret = ext4_find_extent(inode_ref, from, &path, 0); + if (ret != EOK) + goto out; + + if (!path[depth].extent) { + ret = EOK; + goto out; + } + + bool in_range = IN_RANGE(from, to_le32(path[depth].extent->first_block), + ext4_ext_get_actual_len(path[depth].extent)); + + if (!in_range) { + ret = EOK; + goto out; + } + + /* If we do remove_space inside the range of an extent */ + if ((to_le32(path[depth].extent->first_block) < from) && + (to < to_le32(path[depth].extent->first_block) + + ext4_ext_get_actual_len(path[depth].extent) - 1)) { + + struct ext4_extent *ex = path[depth].extent, newex; + int unwritten = ext4_ext_is_unwritten(ex); + ext4_lblk_t ee_block = to_le32(ex->first_block); + int32_t len = ext4_ext_get_actual_len(ex); + ext4_fsblk_t newblock = to + 1 - ee_block + ext4_ext_pblock(ex); + + ex->block_count = to_le16(from - ee_block); + if (unwritten) + ext4_ext_mark_unwritten(ex); + + ext4_ext_dirty(inode_ref, path + depth); + + newex.first_block = to_le32(to + 1); + newex.block_count = to_le16(ee_block + len - 1 - to); + ext4_ext_store_pblock(&newex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(&newex); + + ret = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + goto out; + } + + i = depth; + while (i >= 0) { + if (i == depth) { + struct ext4_extent_header *eh; + struct ext4_extent *first_ex, *last_ex; + ext4_lblk_t leaf_from, leaf_to; + eh = path[i].header; + ext4_assert(to_le16(eh->entries_count) > 0); + first_ex = EXT_FIRST_EXTENT(eh); + last_ex = EXT_LAST_EXTENT(eh); + leaf_from = to_le32(first_ex->first_block); + leaf_to = to_le32(last_ex->first_block) + + ext4_ext_get_actual_len(last_ex) - 1; + if (leaf_from < from) + leaf_from = from; + + if (leaf_to > to) + leaf_to = to; + + ext4_ext_remove_leaf(inode_ref, path, leaf_from, + leaf_to); + ext4_ext_drop_refs(inode_ref, path + i, 0); + i--; + continue; + } + + struct ext4_extent_header *eh; + eh = path[i].header; + if (ext4_ext_more_to_rm(path + i, to)) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + if (path[i + 1].block.lb_id) + ext4_ext_drop_refs(inode_ref, path + i + 1, 0); + + ret = read_extent_tree_block( + inode_ref, ext4_idx_pblock(path[i].index), + depth - i - 1, &bh, 0); + if (ret != EOK) + goto out; + + path[i].p_block = ext4_idx_pblock(path[i].index); + path[i + 1].block = bh; + path[i + 1].header = ext_block_hdr(&bh); + path[i + 1].depth = depth - i - 1; + if (i + 1 == depth) + path[i + 1].extent = + EXT_FIRST_EXTENT(path[i + 1].header); + else + path[i + 1].index = + EXT_FIRST_INDEX(path[i + 1].header); + + i++; + } else { + if (i > 0) { + /* + * Garbage entries will finally be cleared here. + */ + if (!eh->entries_count) + ret = ext4_ext_remove_idx(inode_ref, + path, i - 1); + else + path[i - 1].index++; + } + + if (i) + ext4_block_set(inode_ref->fs->bdev, + &path[i].block); + + i--; + } + } + + /* TODO: flexible tree reduction should be here */ + if (path->header->entries_count == 0) { + /* + * truncate to zero freed all the tree, + * so we need to correct eh_depth + */ + ext_inode_hdr(inode_ref->inode)->depth = 0; + ext_inode_hdr(inode_ref->inode)->max_entries_count = + to_le16(ext4_ext_space_root(inode_ref)); + ret = ext4_ext_dirty(inode_ref, path); + } + +out: + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + path = NULL; + return ret; +} + +static int ext4_ext_split_extent_at(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t split_flag) +{ + struct ext4_extent *ex, newex; + ext4_fsblk_t newblock; + ext4_lblk_t ee_block; + int32_t ee_len; + int32_t depth = ext_depth(inode_ref->inode); + int err = EOK; + + ex = (*ppath)[depth].extent; + ee_block = to_le32(ex->first_block); + ee_len = ext4_ext_get_actual_len(ex); + newblock = split - ee_block + ext4_ext_pblock(ex); + + if (split == ee_block) { + /* + * case b: block @split is the block that the extent begins with + * then we just change the state of the extent, and splitting + * is not needed. + */ + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(ex); + else + ext4_ext_mark_initialized(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + goto out; + } + + ex->block_count = to_le16(split - ee_block); + if (split_flag & EXT4_EXT_MARK_UNWRIT1) + ext4_ext_mark_unwritten(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + if (err != EOK) + goto out; + + newex.first_block = to_le32(split); + newex.block_count = to_le16(ee_len - (split - ee_block)); + ext4_ext_store_pblock(&newex, newblock); + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(&newex); + err = ext4_ext_insert_extent(inode_ref, ppath, &newex, + EXT4_EXT_NO_COMBINE); + if (err != EOK) + goto restore_extent_len; + +out: + return err; +restore_extent_len: + ex->block_count = to_le16(ee_len); + err = ext4_ext_dirty(inode_ref, *ppath + depth); + return err; +} + +static int ext4_ext_convert_to_initialized(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t blocks) +{ + int32_t depth = ext_depth(inode_ref->inode), err = EOK; + struct ext4_extent *ex = (*ppath)[depth].extent; + + ext4_assert(to_le32(ex->first_block) <= split); + + if (split + blocks == + to_le32(ex->first_block) + ext4_ext_get_actual_len(ex)) { + /* split and initialize right part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } else if (to_le32(ex->first_block) == split) { + /* split and initialize left part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT2); + } else { + /* split 1 extent to 3 and initialize the 2nd */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT1 | + EXT4_EXT_MARK_UNWRIT2); + if (err == EOK) { + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } + } + + return err; +} + +static ext4_lblk_t ext4_ext_next_allocated_block(struct ext4_extent_path *path) +{ + int32_t depth; + + depth = path->depth; + + if (depth == 0 && path->extent == NULL) + return EXT_MAX_BLOCKS; + + while (depth >= 0) { + if (depth == path->depth) { + /* leaf */ + if (path[depth].extent && + path[depth].extent != + EXT_LAST_EXTENT(path[depth].header)) + return to_le32( + path[depth].extent[1].first_block); + } else { + /* index */ + if (path[depth].index != + EXT_LAST_INDEX(path[depth].header)) + return to_le32( + path[depth].index[1].first_block); + } + depth--; + } + + return EXT_MAX_BLOCKS; +} + +static int ext4_ext_zero_unwritten_range(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, + uint32_t blocks_count) +{ + int err = EOK; + uint32_t i; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + for (i = 0; i < blocks_count; i++) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, + block + i); + if (err != EOK) + break; + + memset(bh.data, 0, block_size); + ext4_trans_set_block_dirty(bh.buf); + err = ext4_block_set(inode_ref->fs->bdev, &bh); + if (err != EOK) + break; + } + return err; +} + +__unused static void print_path(struct ext4_extent_path *path) +{ + int32_t i = path->depth; + while (i >= 0) { + + ptrdiff_t a = + (path->extent) + ? (path->extent - EXT_FIRST_EXTENT(path->header)) + : 0; + ptrdiff_t b = + (path->index) + ? (path->index - EXT_FIRST_INDEX(path->header)) + : 0; + + (void)a; + (void)b; + ext4_dbg(DEBUG_EXTENT, + "depth %" PRId32 ", p_block: %" PRIu64 "," + "p_ext offset: %td, p_idx offset: %td\n", + i, path->p_block, a, b); + i--; + path++; + } +} + +int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_lblk_t iblock, + uint32_t max_blocks, ext4_fsblk_t *result, + bool create, uint32_t *blocks_count) +{ + struct ext4_extent_path *path = NULL; + struct ext4_extent newex, *ex; + ext4_fsblk_t goal; + int err = EOK; + int32_t depth; + uint32_t allocated = 0; + ext4_lblk_t next; + ext4_fsblk_t newblock; + + if (result) + *result = 0; + + if (blocks_count) + *blocks_count = 0; + + /* find extent for this block */ + err = ext4_find_extent(inode_ref, iblock, &path, 0); + if (err != EOK) { + path = NULL; + goto out2; + } + + depth = ext_depth(inode_ref->inode); + + /* + * consistent leaf must not be empty + * this situations is possible, though, _during_ tree modification + * this is why assert can't be put in ext4_ext_find_extent() + */ + ex = path[depth].extent; + if (ex) { + ext4_lblk_t ee_block = to_le32(ex->first_block); + ext4_fsblk_t ee_start = ext4_ext_pblock(ex); + uint16_t ee_len = ext4_ext_get_actual_len(ex); + /* if found exent covers block, simple return it */ + if (IN_RANGE(iblock, ee_block, ee_len)) { + /* number of remain blocks in the extent */ + allocated = ee_len - (iblock - ee_block); + + if (!ext4_ext_is_unwritten(ex)) { + newblock = iblock - ee_block + ee_start; + goto out; + } + + if (!create) { + newblock = 0; + goto out; + } + + uint32_t zero_range; + zero_range = allocated; + if (zero_range > max_blocks) + zero_range = max_blocks; + + newblock = iblock - ee_block + ee_start; + err = ext4_ext_zero_unwritten_range(inode_ref, newblock, + zero_range); + if (err != EOK) + goto out2; + + err = ext4_ext_convert_to_initialized( + inode_ref, &path, iblock, zero_range); + if (err != EOK) + goto out2; + + goto out; + } + } + + /* + * requested block isn't allocated yet + * we couldn't try to create block if create flag is zero + */ + if (!create) { + goto out2; + } + + /* find next allocated block so that we know how many + * blocks we can allocate without ovelapping next extent */ + next = ext4_ext_next_allocated_block(path); + allocated = next - iblock; + if (allocated > max_blocks) + allocated = max_blocks; + + /* allocate new block */ + goal = ext4_ext_find_goal(inode_ref, path, iblock); + newblock = ext4_new_meta_blocks(inode_ref, goal, 0, &allocated, &err); + if (!newblock) + goto out2; + + /* try to insert new extent into found leaf and return */ + newex.first_block = to_le32(iblock); + ext4_ext_store_pblock(&newex, newblock); + newex.block_count = to_le16(allocated); + err = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + if (err != EOK) { + /* free data blocks we just allocated */ + ext4_ext_free_blocks(inode_ref, ext4_ext_pblock(&newex), + to_le16(newex.block_count), 0); + goto out2; + } + + /* previous routine could use block we allocated */ + newblock = ext4_ext_pblock(&newex); + +out: + if (allocated > max_blocks) + allocated = max_blocks; + + if (result) + *result = newblock; + + if (blocks_count) + *blocks_count = allocated; + +out2: + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + } + + return err; +} +#endif diff --git a/clib/lib/lwext4/src/ext4_fs.c b/clib/lib/lwext4/src/ext4_fs.c new file mode 100644 index 0000000..c7a99e7 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_fs.c @@ -0,0 +1,1750 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_fs.c + * @brief More complex filesystem functions. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int ext4_fs_init(struct ext4_fs *fs, struct ext4_blockdev *bdev, + bool read_only) +{ + int r, i; + uint16_t tmp; + uint32_t bsize; + + ext4_assert(fs && bdev); + + fs->bdev = bdev; + + fs->read_only = read_only; + + r = ext4_sb_read(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + if (!ext4_sb_check(&fs->sb)) + return ENOTSUP; + + bsize = ext4_sb_get_block_size(&fs->sb); + if (bsize > EXT4_MAX_BLOCK_SIZE) + return ENXIO; + + r = ext4_fs_check_features(fs, &read_only); + if (r != EOK) + return r; + + if (read_only) + fs->read_only = read_only; + + /* Compute limits for indirect block levels */ + uint32_t blocks_id = bsize / sizeof(uint32_t); + + fs->inode_block_limits[0] = EXT4_INODE_DIRECT_BLOCK_COUNT; + fs->inode_blocks_per_level[0] = 1; + + for (i = 1; i < 4; i++) { + fs->inode_blocks_per_level[i] = + fs->inode_blocks_per_level[i - 1] * blocks_id; + fs->inode_block_limits[i] = fs->inode_block_limits[i - 1] + + fs->inode_blocks_per_level[i]; + } + + /*Validate FS*/ + tmp = ext4_get16(&fs->sb, state); + if (tmp & EXT4_SUPERBLOCK_STATE_ERROR_FS) + ext4_dbg(DEBUG_FS, DBG_WARN + "last umount error: superblock fs_error flag\n"); + + + if (!fs->read_only) { + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_ERROR_FS); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, ext4_get16(&fs->sb, mount_count) + 1); + } + + return r; +} + +int ext4_fs_fini(struct ext4_fs *fs) +{ + ext4_assert(fs); + + /*Set superblock state*/ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_VALID_FS); + + if (!fs->read_only) + return ext4_sb_write(fs->bdev, &fs->sb); + + return EOK; +} + +static void ext4_fs_debug_features_inc(uint32_t features_incompatible) +{ + if (features_incompatible & EXT4_FINCOM_COMPRESSION) + ext4_dbg(DEBUG_FS, DBG_NONE "compression\n"); + if (features_incompatible & EXT4_FINCOM_FILETYPE) + ext4_dbg(DEBUG_FS, DBG_NONE "filetype\n"); + if (features_incompatible & EXT4_FINCOM_RECOVER) + ext4_dbg(DEBUG_FS, DBG_NONE "recover\n"); + if (features_incompatible & EXT4_FINCOM_JOURNAL_DEV) + ext4_dbg(DEBUG_FS, DBG_NONE "journal_dev\n"); + if (features_incompatible & EXT4_FINCOM_META_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_bg\n"); + if (features_incompatible & EXT4_FINCOM_EXTENTS) + ext4_dbg(DEBUG_FS, DBG_NONE "extents\n"); + if (features_incompatible & EXT4_FINCOM_64BIT) + ext4_dbg(DEBUG_FS, DBG_NONE "64bit\n"); + if (features_incompatible & EXT4_FINCOM_MMP) + ext4_dbg(DEBUG_FS, DBG_NONE "mnp\n"); + if (features_incompatible & EXT4_FINCOM_FLEX_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "flex_bg\n"); + if (features_incompatible & EXT4_FINCOM_EA_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "ea_inode\n"); + if (features_incompatible & EXT4_FINCOM_DIRDATA) + ext4_dbg(DEBUG_FS, DBG_NONE "dirdata\n"); + if (features_incompatible & EXT4_FINCOM_BG_USE_META_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_csum\n"); + if (features_incompatible & EXT4_FINCOM_LARGEDIR) + ext4_dbg(DEBUG_FS, DBG_NONE "largedir\n"); + if (features_incompatible & EXT4_FINCOM_INLINE_DATA) + ext4_dbg(DEBUG_FS, DBG_NONE "inline_data\n"); +} +static void ext4_fs_debug_features_comp(uint32_t features_compatible) +{ + if (features_compatible & EXT4_FCOM_DIR_PREALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_prealloc\n"); + if (features_compatible & EXT4_FCOM_IMAGIC_INODES) + ext4_dbg(DEBUG_FS, DBG_NONE "imagic_inodes\n"); + if (features_compatible & EXT4_FCOM_HAS_JOURNAL) + ext4_dbg(DEBUG_FS, DBG_NONE "has_journal\n"); + if (features_compatible & EXT4_FCOM_EXT_ATTR) + ext4_dbg(DEBUG_FS, DBG_NONE "ext_attr\n"); + if (features_compatible & EXT4_FCOM_RESIZE_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "resize_inode\n"); + if (features_compatible & EXT4_FCOM_DIR_INDEX) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_index\n"); +} + +static void ext4_fs_debug_features_ro(uint32_t features_ro) +{ + if (features_ro & EXT4_FRO_COM_SPARSE_SUPER) + ext4_dbg(DEBUG_FS, DBG_NONE "sparse_super\n"); + if (features_ro & EXT4_FRO_COM_LARGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "large_file\n"); + if (features_ro & EXT4_FRO_COM_BTREE_DIR) + ext4_dbg(DEBUG_FS, DBG_NONE "btree_dir\n"); + if (features_ro & EXT4_FRO_COM_HUGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "huge_file\n"); + if (features_ro & EXT4_FRO_COM_GDT_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "gtd_csum\n"); + if (features_ro & EXT4_FRO_COM_DIR_NLINK) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_nlink\n"); + if (features_ro & EXT4_FRO_COM_EXTRA_ISIZE) + ext4_dbg(DEBUG_FS, DBG_NONE "extra_isize\n"); + if (features_ro & EXT4_FRO_COM_QUOTA) + ext4_dbg(DEBUG_FS, DBG_NONE "quota\n"); + if (features_ro & EXT4_FRO_COM_BIGALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "bigalloc\n"); + if (features_ro & EXT4_FRO_COM_METADATA_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "metadata_csum\n"); +} + +int ext4_fs_check_features(struct ext4_fs *fs, bool *read_only) +{ + ext4_assert(fs && read_only); + uint32_t v; + if (ext4_get32(&fs->sb, rev_level) == 0) { + *read_only = false; + return EOK; + } + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_incompatible:\n"); + ext4_fs_debug_features_inc(ext4_get32(&fs->sb, features_incompatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_compatible:\n"); + ext4_fs_debug_features_comp(ext4_get32(&fs->sb, features_compatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_read_only:\n"); + ext4_fs_debug_features_ro(ext4_get32(&fs->sb, features_read_only)); + + /*Check features_incompatible*/ + v = (ext4_get32(&fs->sb, features_incompatible) & + (~CONFIG_SUPPORTED_FINCOM)); + if (v) { + ext4_dbg(DEBUG_FS, DBG_ERROR + "sblock has unsupported features incompatible:\n"); + ext4_fs_debug_features_inc(v); + return ENOTSUP; + } + + /*Check features_read_only*/ + v = ext4_get32(&fs->sb, features_read_only); + v &= ~CONFIG_SUPPORTED_FRO_COM; + if (v) { + ext4_dbg(DEBUG_FS, DBG_WARN + "sblock has unsupported features read only:\n"); + ext4_fs_debug_features_ro(v); + *read_only = true; + return EOK; + } + *read_only = false; + + return EOK; +} + +/**@brief Determine whether the block is inside the group. + * @param baddr block address + * @param bgid block group id + * @return Error code + */ +static bool ext4_block_in_group(struct ext4_sblock *s, ext4_fsblk_t baddr, + uint32_t bgid) +{ + uint32_t actual_bgid; + actual_bgid = ext4_balloc_get_bgid_of_block(s, baddr); + if (actual_bgid == bgid) + return true; + return false; +} + +/**@brief To avoid calling the atomic setbit hundreds or thousands of times, we only + * need to use it within a single byte (to ensure we get endianness right). + * We can use memset for the rest of the bitmap as there are no other users. + */ +static void ext4_fs_mark_bitmap_end(int start_bit, int end_bit, void *bitmap) +{ + int i; + + if (start_bit >= end_bit) + return; + + for (i = start_bit; (unsigned)i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(bitmap, i); + + if (i < end_bit) + memset((char *)bitmap + (i >> 3), 0xff, (end_bit - i) >> 3); +} + +/**@brief Initialize block bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_block_bitmap(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + int rc; + + uint32_t bit, bit_max; + uint32_t group_blocks; + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + ext4_fsblk_t i; + ext4_fsblk_t bmp_blk = ext4_bg_get_block_bitmap(bg, sb); + ext4_fsblk_t bmp_inode = ext4_bg_get_inode_bitmap(bg, sb); + ext4_fsblk_t inode_table = ext4_bg_get_inode_table_first_block(bg, sb); + ext4_fsblk_t first_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref->index); + + uint32_t dsc_per_block = block_size / ext4_sb_get_desc_size(sb); + + bool flex_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG); + bool meta_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_META_BG); + + uint32_t inode_table_bcnt = inodes_per_group * inode_size / block_size; + + struct ext4_block block_bitmap; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &block_bitmap, bmp_blk); + if (rc != EOK) + return rc; + + memset(block_bitmap.data, 0, block_size); + bit_max = ext4_sb_is_super_in_bg(sb, bg_ref->index); + + uint32_t count = ext4_sb_first_meta_bg(sb) * dsc_per_block; + if (!meta_bg || bg_ref->index < count) { + if (bit_max) { + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + bit_max += ext4_get16(sb, s_reserved_gdt_blocks); + } + } else { /* For META_BG_BLOCK_GROUPS */ + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + } + for (bit = 0; bit < bit_max; bit++) + ext4_bmap_bit_set(block_bitmap.data, bit); + + if (bg_ref->index == ext4_block_group_cnt(sb) - 1) { + /* + * Even though mke2fs always initialize first and last group + * if some other tool enabled the EXT4_BG_BLOCK_UNINIT we need + * to make sure we calculate the right free blocks + */ + + group_blocks = (uint32_t)(ext4_sb_get_blocks_cnt(sb) - + ext4_get32(sb, first_data_block) - + ext4_get32(sb, blocks_per_group) * + (ext4_block_group_cnt(sb) - 1)); + } else { + group_blocks = ext4_get32(sb, blocks_per_group); + } + + bool in_bg; + in_bg = ext4_block_in_group(sb, bmp_blk, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_blk - first_bg)); + + in_bg = ext4_block_in_group(sb, bmp_inode, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_inode - first_bg)); + + for (i = inode_table; i < inode_table + inode_table_bcnt; i++) { + in_bg = ext4_block_in_group(sb, i, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(i - first_bg)); + } + /* + * Also if the number of blocks within the group is + * less than the blocksize * 8 ( which is the size + * of bitmap ), set rest of the block bitmap to 1 + */ + ext4_fs_mark_bitmap_end(group_blocks, block_size * 8, block_bitmap.data); + ext4_trans_set_block_dirty(block_bitmap.buf); + + ext4_balloc_set_bitmap_csum(sb, bg_ref->block_group, block_bitmap.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &block_bitmap); +} + +/**@brief Initialize i-node bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_bitmap(struct ext4_block_group_ref *bg_ref) +{ + int rc; + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + /* Load bitmap */ + ext4_fsblk_t bitmap_block_addr = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + /* Initialize all bitmap bits to zero */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + memset(b.data, 0, (inodes_per_group + 7) / 8); + + uint32_t start_bit = inodes_per_group; + uint32_t end_bit = block_size * 8; + + uint32_t i; + for (i = start_bit; i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(b.data, i); + + if (i < end_bit) + memset(b.data + (i >> 3), 0xff, (end_bit - i) >> 3); + + ext4_trans_set_block_dirty(b.buf); + + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &b); +} + +/**@brief Initialize i-node table in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_table(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + uint32_t inode_size = ext4_get16(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_block = block_size / inode_size; + uint32_t inodes_in_group = ext4_inodes_in_group_cnt(sb, bg_ref->index); + uint32_t table_blocks = inodes_in_group / inodes_per_block; + ext4_fsblk_t fblock; + + if (inodes_in_group % inodes_per_block) + table_blocks++; + + /* Compute initialization bounds */ + ext4_fsblk_t first_block = ext4_bg_get_inode_table_first_block(bg, sb); + + ext4_fsblk_t last_block = first_block + table_blocks - 1; + + /* Initialization of all itable blocks */ + for (fblock = first_block; fblock <= last_block; ++fblock) { + struct ext4_block b; + int rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, fblock); + if (rc != EOK) + return rc; + + memset(b.data, 0, block_size); + ext4_trans_set_block_dirty(b.buf); + + rc = ext4_block_set(bg_ref->fs->bdev, &b); + if (rc != EOK) + return rc; + } + + return EOK; +} + +static ext4_fsblk_t ext4_fs_get_descriptor_block(struct ext4_sblock *s, + uint32_t bgid, + uint32_t dsc_per_block) +{ + uint32_t first_meta_bg, dsc_id; + int has_super = 0; + dsc_id = bgid / dsc_per_block; + first_meta_bg = ext4_sb_first_meta_bg(s); + + bool meta_bg = ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG); + + if (!meta_bg || dsc_id < first_meta_bg) + return ext4_get32(s, first_data_block) + dsc_id + 1; + + if (ext4_sb_is_super_in_bg(s, bgid)) + has_super = 1; + + return (has_super + ext4_fs_first_bg_block_no(s, bgid)); +} + +/**@brief Compute checksum of block group descriptor. + * @param sb Superblock + * @param bgid Index of block group in the filesystem + * @param bg Block group to compute checksum for + * @return Checksum value + */ +static uint16_t ext4_fs_bg_checksum(struct ext4_sblock *sb, uint32_t bgid, + struct ext4_bgroup *bg) +{ + /* If checksum not supported, 0 will be returned */ + uint16_t crc = 0; +#if CONFIG_META_CSUM_ENABLE + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + /* Use metadata_csum algorithm instead */ + uint32_t le32_bgid = to_le32(bgid); + uint32_t orig_checksum, checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = bg->checksum; + bg->checksum = 0; + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against bgid */ + checksum = ext4_crc32c(checksum, &le32_bgid, sizeof(bgid)); + /* Finally calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bg, ext4_sb_get_desc_size(sb)); + bg->checksum = orig_checksum; + + crc = checksum & 0xFFFF; + return crc; + } +#endif + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_GDT_CSUM)) { + uint8_t *base = (uint8_t *)bg; + uint8_t *checksum = (uint8_t *)&bg->checksum; + + uint32_t offset = (uint32_t)(checksum - base); + + /* Convert block group index to little endian */ + uint32_t group = to_le32(bgid); + + /* Initialization */ + crc = ext4_bg_crc16(~0, sb->uuid, sizeof(sb->uuid)); + + /* Include index of block group */ + crc = ext4_bg_crc16(crc, (uint8_t *)&group, sizeof(group)); + + /* Compute crc from the first part (stop before checksum field) + */ + crc = ext4_bg_crc16(crc, (uint8_t *)bg, offset); + + /* Skip checksum */ + offset += sizeof(bg->checksum); + + /* Checksum of the rest of block group descriptor */ + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_64BIT)) && + (offset < ext4_sb_get_desc_size(sb))) { + + const uint8_t *start = ((uint8_t *)bg) + offset; + size_t len = ext4_sb_get_desc_size(sb) - offset; + crc = ext4_bg_crc16(crc, start, len); + } + } + return crc; +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_bg_csum(struct ext4_sblock *sb, + uint32_t bgid, + struct ext4_bgroup *bg) +{ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_fs_bg_checksum(sb, bgid, bg) == to_le16(bg->checksum); +} +#else +#define ext4_fs_verify_bg_csum(...) true +#endif + +int ext4_fs_get_block_group_ref(struct ext4_fs *fs, uint32_t bgid, + struct ext4_block_group_ref *ref) +{ + /* Compute number of descriptors, that fits in one data block */ + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t dsc_cnt = block_size / ext4_sb_get_desc_size(&fs->sb); + + /* Block group descriptor table starts at the next block after + * superblock */ + uint64_t block_id = ext4_fs_get_descriptor_block(&fs->sb, bgid, dsc_cnt); + + uint32_t offset = (bgid % dsc_cnt) * ext4_sb_get_desc_size(&fs->sb); + + int rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) + return rc; + + ref->block_group = (void *)(ref->block.data + offset); + ref->fs = fs; + ref->index = bgid; + ref->dirty = false; + struct ext4_bgroup *bg = ref->block_group; + + if (!ext4_fs_verify_bg_csum(&fs->sb, bgid, bg)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Block group descriptor checksum failed." + "Block group index: %" PRIu32"\n", + bgid); + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT)) { + rc = ext4_fs_init_block_bitmap(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT); + ref->dirty = true; + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT)) { + rc = ext4_fs_init_inode_bitmap(ref); + if (rc != EOK) { + ext4_block_set(ref->fs->bdev, &ref->block); + return rc; + } + + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT); + + if (!ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED)) { + rc = ext4_fs_init_inode_table(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + + ext4_bg_set_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED); + } + + ref->dirty = true; + } + + return EOK; +} + +int ext4_fs_put_block_group_ref(struct ext4_block_group_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Compute new checksum of block group */ + uint16_t cs; + cs = ext4_fs_bg_checksum(&ref->fs->sb, ref->index, + ref->block_group); + ref->block_group->checksum = to_le16(cs); + + /* Mark block dirty for writing changes to physical device */ + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains block group descriptor */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_fs_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + uint32_t checksum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint16_t inode_size = ext4_get16(sb, inode_size); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = + to_le32(ext4_inode_get_generation(inode_ref->inode)); + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = ext4_inode_get_csum(sb, inode_ref->inode); + ext4_inode_set_csum(sb, inode_ref->inode, 0); + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + checksum = ext4_crc32c(checksum, &ino_index, sizeof(ino_index)); + checksum = ext4_crc32c(checksum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against + * the entire inode */ + checksum = ext4_crc32c(checksum, inode_ref->inode, inode_size); + ext4_inode_set_csum(sb, inode_ref->inode, orig_checksum); + + /* If inode size is not large enough to hold the + * upper 16bit of the checksum */ + if (inode_size == EXT4_GOOD_OLD_INODE_SIZE) + checksum &= 0xFFFF; + + } + return checksum; +} +#else +#define ext4_fs_inode_checksum(...) 0 +#endif + +static void ext4_fs_set_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + uint32_t csum = ext4_fs_inode_checksum(inode_ref); + ext4_inode_set_csum(sb, inode_ref->inode, csum); +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_inode_csum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_inode_get_csum(sb, inode_ref->inode) == + ext4_fs_inode_checksum(inode_ref); +} +#else +#define ext4_fs_verify_inode_csum(...) true +#endif + +static int +__ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref, + bool initialized) +{ + /* Compute number of i-nodes, that fits in one data block */ + uint32_t inodes_per_group = ext4_get32(&fs->sb, inodes_per_group); + + /* + * Inode numbers are 1-based, but it is simpler to work with 0-based + * when computing indices + */ + index -= 1; + uint32_t block_group = index / inodes_per_group; + uint32_t offset_in_group = index % inodes_per_group; + + /* Load block group, where i-node is located */ + struct ext4_block_group_ref bg_ref; + + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) { + return rc; + } + + /* Load block address, where i-node table is located */ + ext4_fsblk_t inode_table_start = + ext4_bg_get_inode_table_first_block(bg_ref.block_group, &fs->sb); + + /* Put back block group reference (not needed more) */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the block group */ + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t byte_offset_in_group = offset_in_group * inode_size; + + /* Compute block address */ + ext4_fsblk_t block_id = + inode_table_start + (byte_offset_in_group / block_size); + + rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the data block */ + uint32_t offset_in_block = byte_offset_in_group % block_size; + ref->inode = (struct ext4_inode *)(ref->block.data + offset_in_block); + + /* We need to store the original value of index in the reference */ + ref->index = index + 1; + ref->fs = fs; + ref->dirty = false; + + if (initialized && !ext4_fs_verify_inode_csum(ref)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Inode checksum failed." + "Inode: %" PRIu32"\n", + ref->index); + } + + return EOK; +} + +int ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref) +{ + return __ext4_fs_get_inode_ref(fs, index, ref, true); +} + +int ext4_fs_put_inode_ref(struct ext4_inode_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Mark block dirty for writing changes to physical device */ + ext4_fs_set_inode_checksum(ref); + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains i-node */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +void ext4_fs_inode_blocks_init(struct ext4_fs *fs, + struct ext4_inode_ref *inode_ref) +{ + struct ext4_inode *inode = inode_ref->inode; + + /* Reset blocks array. For inode which is not directory or file, just + * fill in blocks with 0 */ + switch (ext4_inode_type(&fs->sb, inode_ref->inode)) { + case EXT4_INODE_MODE_FILE: + case EXT4_INODE_MODE_DIRECTORY: + break; + default: + return; + } + +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Initialize extents if needed */ + if (ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) { + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_EXTENTS); + + /* Initialize extent root header */ + ext4_extent_tree_init(inode_ref); + } + + inode_ref->dirty = true; +#endif +} + +uint32_t ext4_fs_correspond_inode_mode(int filetype) +{ + switch (filetype) { + case EXT4_DE_DIR: + return EXT4_INODE_MODE_DIRECTORY; + case EXT4_DE_REG_FILE: + return EXT4_INODE_MODE_FILE; + case EXT4_DE_SYMLINK: + return EXT4_INODE_MODE_SOFTLINK; + case EXT4_DE_CHRDEV: + return EXT4_INODE_MODE_CHARDEV; + case EXT4_DE_BLKDEV: + return EXT4_INODE_MODE_BLOCKDEV; + case EXT4_DE_FIFO: + return EXT4_INODE_MODE_FIFO; + case EXT4_DE_SOCK: + return EXT4_INODE_MODE_SOCKET; + } + /* FIXME: unsupported filetype */ + return EXT4_INODE_MODE_FILE; +} + +int ext4_fs_alloc_inode(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + int filetype) +{ + /* Check if newly allocated i-node will be a directory */ + bool is_dir; + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + + is_dir = (filetype == EXT4_DE_DIR); + + /* Allocate inode by allocation algorithm */ + uint32_t index; + int rc = ext4_ialloc_alloc_inode(fs, &index, is_dir); + if (rc != EOK) + return rc; + + /* Load i-node from on-disk i-node table */ + rc = __ext4_fs_get_inode_ref(fs, index, inode_ref, false); + if (rc != EOK) { + ext4_ialloc_free_inode(fs, index, is_dir); + return rc; + } + + /* Initialize i-node */ + struct ext4_inode *inode = inode_ref->inode; + + memset(inode, 0, inode_size); + + uint32_t mode; + if (is_dir) { + /* + * Default directory permissions to be compatible with other + * systems + * 0777 (octal) == rwxrwxrwx + */ + + mode = 0777; + mode |= EXT4_INODE_MODE_DIRECTORY; + } else if (filetype == EXT4_DE_SYMLINK) { + /* + * Default symbolic link permissions to be compatible with other systems + * 0777 (octal) == rwxrwxrwx + */ + + mode = 0777; + mode |= EXT4_INODE_MODE_SOFTLINK; + } else { + /* + * Default file permissions to be compatible with other systems + * 0666 (octal) == rw-rw-rw- + */ + + mode = 0666; + mode |= ext4_fs_correspond_inode_mode(filetype); + } + ext4_inode_set_mode(&fs->sb, inode, mode); + + ext4_inode_set_links_cnt(inode, 0); + ext4_inode_set_uid(inode, 0); + ext4_inode_set_gid(inode, 0); + ext4_inode_set_size(inode, 0); + ext4_inode_set_access_time(inode, 0); + ext4_inode_set_change_inode_time(inode, 0); + ext4_inode_set_modif_time(inode, 0); + ext4_inode_set_del_time(inode, 0); + ext4_inode_set_blocks_count(&fs->sb, inode, 0); + ext4_inode_set_flags(inode, 0); + ext4_inode_set_generation(inode, 0); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) { + uint16_t size = ext4_get16(&fs->sb, want_extra_isize); + ext4_inode_set_extra_isize(&fs->sb, inode, size); + } + + memset(inode->blocks, 0, sizeof(inode->blocks)); + inode_ref->dirty = true; + + return EOK; +} + +int ext4_fs_free_inode(struct ext4_inode_ref *inode_ref) +{ + struct ext4_fs *fs = inode_ref->fs; + uint32_t offset; + uint32_t suboff; + int rc; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* For extents must be data block destroyed by other way */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Data structures are released during truncate operation... */ + goto finish; + } +#endif + /* Release all indirect (no data) blocks */ + + /* 1) Single indirect */ + ext4_fsblk_t fblock = ext4_inode_get_indirect_block(inode_ref->inode, 0); + if (fblock != 0) { + int rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 0, 0); + } + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t count = block_size / sizeof(uint32_t); + + struct ext4_block block; + + /* 2) Double indirect */ + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 1); + if (fblock != 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 1, 0); + } + + /* 3) Tripple indirect */ + struct ext4_block subblock; + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 2); + if (fblock == 0) + goto finish; + rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_trans_block_get(fs->bdev, &subblock, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + ext4_fsblk_t ind_subblk; + for (suboff = 0; suboff < count; ++suboff) { + ind_subblk = to_le32(((uint32_t *)subblock.data)[suboff]); + + if (ind_subblk == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_subblk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &subblock); + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &subblock); + + rc = ext4_balloc_free_block(inode_ref, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 2, 0); +finish: + /* Mark inode dirty for writing to the physical device */ + inode_ref->dirty = true; + + /* Free block with extended attributes if present */ + ext4_fsblk_t xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + if (xattr_block) { + int rc = ext4_balloc_free_block(inode_ref, xattr_block); + if (rc != EOK) + return rc; + + ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, 0); + } + + /* Free inode by allocator */ + if (ext4_inode_is_type(&fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) + rc = ext4_ialloc_free_inode(fs, inode_ref->index, true); + else + rc = ext4_ialloc_free_inode(fs, inode_ref->index, false); + + return rc; +} + + +/**@brief Release data block from i-node + * @param inode_ref I-node to release block from + * @param iblock Logical block to be released + * @return Error code + */ +static int ext4_fs_release_inode_block(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock) +{ + ext4_fsblk_t fblock; + + struct ext4_fs *fs = inode_ref->fs; + + /* Extents are handled otherwise = there is not support in this function + */ + ext4_assert(!( + ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)))); + + struct ext4_inode *inode = inode_ref->inode; + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + fblock = ext4_inode_get_direct_block(inode, iblock); + + /* Sparse file */ + if (fblock == 0) + return EOK; + + ext4_inode_set_direct_block(inode, iblock, 0); + return ext4_balloc_free_block(inode_ref, fblock); + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int level = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + level = i; + break; + } + } + + if (level == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t block_offset_in_level = + (uint32_t)(iblock - fs->inode_block_limits[level - 1]); + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode, level - 1); + uint32_t offset_in_block = + (uint32_t)(block_offset_in_level / fs->inode_blocks_per_level[level - 1]); + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + struct ext4_block block; + + while (level > 0) { + + /* Sparse check */ + if (current_block == 0) + return EOK; + + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = + to_le32(((uint32_t *)block.data)[offset_in_block]); + + /* Set zero if physical data block address found */ + if (level == 1) { + ((uint32_t *)block.data)[offset_in_block] = to_le32(0); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + level--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (level == 0) + break; + + /* Visit the next level */ + block_offset_in_level %= fs->inode_blocks_per_level[level]; + offset_in_block = (uint32_t)(block_offset_in_level / + fs->inode_blocks_per_level[level - 1]); + } + + fblock = current_block; + if (fblock == 0) + return EOK; + + /* Physical block is not referenced, it can be released */ + return ext4_balloc_free_block(inode_ref, fblock); +} + +int ext4_fs_truncate_inode(struct ext4_inode_ref *inode_ref, uint64_t new_size) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t i; + int r; + bool v; + + /* Check flags, if i-node can be truncated */ + if (!ext4_inode_can_truncate(sb, inode_ref->inode)) + return EINVAL; + + /* If sizes are equal, nothing has to be done. */ + uint64_t old_size = ext4_inode_get_size(sb, inode_ref->inode); + if (old_size == new_size) + return EOK; + + /* It's not supported to make the larger file by truncate operation */ + if (old_size < new_size) + return EINVAL; + + /* For symbolic link which is small enough */ + v = ext4_inode_is_type(sb, inode_ref->inode, EXT4_INODE_MODE_SOFTLINK); + if (v && old_size < sizeof(inode_ref->inode->blocks) && + !ext4_inode_get_blocks_count(sb, inode_ref->inode)) { + char *content = (char *)inode_ref->inode->blocks + new_size; + memset(content, 0, + sizeof(inode_ref->inode->blocks) - (uint32_t)new_size); + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; + } + + i = ext4_inode_type(sb, inode_ref->inode); + if (i == EXT4_INODE_MODE_CHARDEV || + i == EXT4_INODE_MODE_BLOCKDEV || + i == EXT4_INODE_MODE_SOCKET) { + inode_ref->inode->blocks[0] = 0; + inode_ref->inode->blocks[1] = 0; + + inode_ref->dirty = true; + return EOK; + } + + /* Compute how many blocks will be released */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t new_blocks_cnt = (uint32_t)((new_size + block_size - 1) / block_size); + uint32_t old_blocks_cnt = (uint32_t)((old_size + block_size - 1) / block_size); + uint32_t diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + /* Extents require special operation */ + if (diff_blocks_cnt) { + r = ext4_extent_remove_space(inode_ref, new_blocks_cnt, + EXT_MAX_BLOCKS); + if (r != EOK) + return r; + + } + } else +#endif + { + /* Release data blocks from the end of file */ + + /* Starting from 1 because of logical blocks are numbered from 0 + */ + for (i = 0; i < diff_blocks_cnt; ++i) { + r = ext4_fs_release_inode_block(inode_ref, + new_blocks_cnt + i); + if (r != EOK) + return r; + } + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; +} + +/**@brief Compute 'goal' for inode index + * @param inode_ref Reference to inode, to allocate block for + * @return goal + */ +ext4_fsblk_t ext4_fs_inode_to_goal_block(struct ext4_inode_ref *inode_ref) +{ + uint32_t grp_inodes = ext4_get32(&inode_ref->fs->sb, inodes_per_group); + return (inode_ref->index - 1) / grp_inodes; +} + +/**@brief Compute 'goal' for allocation algorithm (For blockmap). + * @param inode_ref Reference to inode, to allocate block for + * @return error code + */ +int ext4_fs_indirect_find_goal(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *goal) +{ + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + *goal = 0; + + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t iblock_cnt = (uint32_t)(inode_size / block_size); + + if (inode_size % block_size != 0) + iblock_cnt++; + + /* If inode has some blocks, get last block address + 1 */ + if (iblock_cnt > 0) { + r = ext4_fs_get_inode_dblk_idx(inode_ref, iblock_cnt - 1, + goal, false); + if (r != EOK) + return r; + + if (*goal != 0) { + (*goal)++; + return r; + } + + /* If goal == 0, sparse file -> continue */ + } + + /* Identify block group of inode */ + + uint32_t inodes_per_bg = ext4_get32(sb, inodes_per_group); + uint32_t block_group = (inode_ref->index - 1) / inodes_per_bg; + block_size = ext4_sb_get_block_size(sb); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + r = ext4_fs_get_block_group_ref(inode_ref->fs, block_group, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Compute indexes */ + uint32_t bg_count = ext4_block_group_cnt(sb); + ext4_fsblk_t itab_first_block = ext4_bg_get_inode_table_first_block(bg, sb); + uint16_t itab_item_size = ext4_get16(sb, inode_size); + uint32_t itab_bytes; + + /* Check for last block group */ + if (block_group < bg_count - 1) { + itab_bytes = inodes_per_bg * itab_item_size; + } else { + /* Last block group could be smaller */ + uint32_t inodes_cnt = ext4_get32(sb, inodes_count); + + itab_bytes = (inodes_cnt - ((bg_count - 1) * inodes_per_bg)); + itab_bytes *= itab_item_size; + } + + ext4_fsblk_t inode_table_blocks = itab_bytes / block_size; + + if (itab_bytes % block_size) + inode_table_blocks++; + + *goal = itab_first_block + inode_table_blocks; + + return ext4_fs_put_block_group_ref(&bg_ref); +} + +static int ext4_fs_get_inode_dblk_idx_internal(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool extent_create, + bool support_unwritten __unused) +{ + struct ext4_fs *fs = inode_ref->fs; + + /* For empty file is situation simple */ + if (ext4_inode_get_size(&fs->sb, inode_ref->inode) == 0) { + *fblock = 0; + return EOK; + } + + ext4_fsblk_t current_block; + + (void)extent_create; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle i-node using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + ext4_fsblk_t current_fsblk; + int rc = ext4_extent_get_blocks(inode_ref, iblock, 1, + ¤t_fsblk, extent_create, NULL); + if (rc != EOK) + return rc; + + current_block = current_fsblk; + *fblock = current_block; + + ext4_assert(*fblock || support_unwritten); + return EOK; + } +#endif + + struct ext4_inode *inode = inode_ref->inode; + + /* Direct block are read directly from array in i-node structure */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + current_block = + ext4_inode_get_direct_block(inode, (uint32_t)iblock); + *fblock = current_block; + return EOK; + } + + /* Determine indirection level of the target block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = (uint32_t)(iblock - fs->inode_block_limits[l - 1]); + current_block = ext4_inode_get_indirect_block(inode, l - 1); + uint32_t off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + + /* Sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + struct ext4_block block; + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + /* Load indirect block */ + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + /* Read block address from indirect block */ + current_block = + to_le32(((uint32_t *)block.data)[off_in_blk]); + + /* Put back indirect block untouched */ + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + /* Check for sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + /* Jump to the next level */ + l--; + + /* Termination condition - we have address of data block loaded + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + } + + *fblock = current_block; + + return EOK; +} + + +int ext4_fs_get_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool support_unwritten) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + false, support_unwritten); +} + +int ext4_fs_init_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + true, true); +} + +static int ext4_fs_set_inode_data_block_index(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t fblock) +{ + struct ext4_fs *fs = inode_ref->fs; + +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle inode using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Not reachable */ + return ENOTSUP; + } +#endif + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + ext4_inode_set_direct_block(inode_ref->inode, (uint32_t)iblock, + (uint32_t)fblock); + inode_ref->dirty = true; + + return EOK; + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = (uint32_t)(iblock - fs->inode_block_limits[l - 1]); + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode_ref->inode, l - 1); + uint32_t off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + + ext4_fsblk_t new_blk; + + struct ext4_block block; + struct ext4_block new_block; + + /* Is needed to allocate indirect block on the i-node level */ + if (current_block == 0) { + /* Allocate new indirect block */ + ext4_fsblk_t goal; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) + return rc; + + /* Update i-node */ + ext4_inode_set_indirect_block(inode_ref->inode, l - 1, + (uint32_t)new_blk); + inode_ref->dirty = true; + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, new_blk); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, new_blk); + return rc; + } + + /* Initialize new block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + /* Put back the allocated block */ + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) + return rc; + + current_block = new_blk; + } + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = to_le32(((uint32_t *)block.data)[off_in_blk]); + if ((l > 1) && (current_block == 0)) { + ext4_fsblk_t goal; + rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Allocate new block */ + rc = + ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, + new_blk); + + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Initialize allocated block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Write block address to the parent */ + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)new_blk); + ext4_trans_set_block_dirty(block.buf); + current_block = new_blk; + } + + /* Will be finished, write the fblock address */ + if (l == 1) { + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)fblock); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + l--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + } + + return EOK; +} + + +int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *fblock, ext4_lblk_t *iblock) +{ +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle extents separately */ + if ((ext4_sb_feature_incom(&inode_ref->fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + int rc; + ext4_fsblk_t current_fsblk; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + *iblock = (uint32_t)((inode_size + block_size - 1) / block_size); + + rc = ext4_extent_get_blocks(inode_ref, *iblock, 1, + ¤t_fsblk, true, NULL); + if (rc != EOK) + return rc; + + *fblock = current_fsblk; + ext4_assert(*fblock); + + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + + return rc; + } +#endif + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute next block index and allocate data block */ + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Align size i-node size */ + if ((inode_size % block_size) != 0) + inode_size += block_size - (inode_size % block_size); + + /* Logical blocks are numbered from 0 */ + uint32_t new_block_idx = (uint32_t)(inode_size / block_size); + + /* Allocate new physical block */ + ext4_fsblk_t goal, phys_block; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &phys_block); + if (rc != EOK) + return rc; + + /* Add physical block address to the i-node */ + rc = ext4_fs_set_inode_data_block_index(inode_ref, new_block_idx, + phys_block); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, phys_block); + return rc; + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + *fblock = phys_block; + *iblock = new_block_idx; + + return EOK; +} + +void ext4_fs_inode_links_count_inc(struct ext4_inode_ref *inode_ref) +{ + uint16_t link; + bool is_dx; + link = ext4_inode_get_links_cnt(inode_ref->inode); + link++; + ext4_inode_set_links_cnt(inode_ref->inode, link); + + is_dx = ext4_sb_feature_com(&inode_ref->fs->sb, EXT4_FCOM_DIR_INDEX) && + ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_INDEX); + + if (is_dx && link > 1) { + if (link >= EXT4_LINK_MAX || link == 2) { + ext4_inode_set_links_cnt(inode_ref->inode, 1); + + uint32_t v; + v = ext4_get32(&inode_ref->fs->sb, features_read_only); + v |= EXT4_FRO_COM_DIR_NLINK; + ext4_set32(&inode_ref->fs->sb, features_read_only, v); + } + } +} + +void ext4_fs_inode_links_count_dec(struct ext4_inode_ref *inode_ref) +{ + uint16_t links = ext4_inode_get_links_cnt(inode_ref->inode); + if (!ext4_inode_is_type(&inode_ref->fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + if (links > 0) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); + return; + } + + if (links > 2) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_hash.c b/clib/lib/lwext4/src/ext4_hash.c new file mode 100644 index 0000000..ff6d031 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_hash.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * FreeBSD: + * Copyright (c) 2010, 2013 Zheng Liu + * Copyright (c) 2012, Vyacheslav Matyushin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * The following notice applies to the code in ext2_half_md4(): + * + * Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD4 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD4 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_hash.c + * @brief Directory indexing hash functions. + */ + +#include +#include +#include +#include +#include + +#include + +/* F, G, and H are MD4 functions */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* + * FF, GG, and HH are transformations for rounds 1, 2, and 3. + * Rotation is separated from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s) \ + { \ + (a) += F((b), (c), (d)) + (x); \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define GG(a, b, c, d, x, s) \ + { \ + (a) += G((b), (c), (d)) + (x) + (uint32_t)0x5A827999; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define HH(a, b, c, d, x, s) \ + { \ + (a) += H((b), (c), (d)) + (x) + (uint32_t)0x6ED9EBA1; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +/* + * MD4 basic transformation. It transforms state based on block. + * + * This is a half md4 algorithm since Linux uses this algorithm for dir + * index. This function is derived from the RSA Data Security, Inc. MD4 + * Message-Digest Algorithm and was modified as necessary. + * + * The return value of this function is uint32_t in Linux, but actually we don't + * need to check this value, so in our version this function doesn't return any + * value. + */ +static void ext2_half_md4(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t a = hash[0], b = hash[1], c = hash[2], d = hash[3]; + + /* Round 1 */ + FF(a, b, c, d, data[0], 3); + FF(d, a, b, c, data[1], 7); + FF(c, d, a, b, data[2], 11); + FF(b, c, d, a, data[3], 19); + FF(a, b, c, d, data[4], 3); + FF(d, a, b, c, data[5], 7); + FF(c, d, a, b, data[6], 11); + FF(b, c, d, a, data[7], 19); + + /* Round 2 */ + GG(a, b, c, d, data[1], 3); + GG(d, a, b, c, data[3], 5); + GG(c, d, a, b, data[5], 9); + GG(b, c, d, a, data[7], 13); + GG(a, b, c, d, data[0], 3); + GG(d, a, b, c, data[2], 5); + GG(c, d, a, b, data[4], 9); + GG(b, c, d, a, data[6], 13); + + /* Round 3 */ + HH(a, b, c, d, data[3], 3); + HH(d, a, b, c, data[7], 9); + HH(c, d, a, b, data[2], 11); + HH(b, c, d, a, data[6], 15); + HH(a, b, c, d, data[1], 3); + HH(d, a, b, c, data[5], 9); + HH(c, d, a, b, data[0], 11); + HH(b, c, d, a, data[4], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +/* + * Tiny Encryption Algorithm. + */ +static void ext2_tea(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t tea_delta = 0x9E3779B9; + uint32_t sum; + uint32_t x = hash[0], y = hash[1]; + int n = 16; + int i = 1; + + while (n-- > 0) { + sum = i * tea_delta; + x += ((y << 4) + data[0]) ^ (y + sum) ^ ((y >> 5) + data[1]); + y += ((x << 4) + data[2]) ^ (x + sum) ^ ((x >> 5) + data[3]); + i++; + } + + hash[0] += x; + hash[1] += y; +} + +static uint32_t ext2_legacy_hash(const char *name, int len, int unsigned_char) +{ + uint32_t h0, h1 = 0x12A3FE2D, h2 = 0x37ABE8F9; + uint32_t multi = 0x6D22F5; + const unsigned char *uname = (const unsigned char *)name; + const signed char *sname = (const signed char *)name; + int val, i; + + for (i = 0; i < len; i++) { + if (unsigned_char) + val = (unsigned int)*uname++; + else + val = (int)*sname++; + + h0 = h2 + (h1 ^ (val * multi)); + if (h0 & 0x80000000) + h0 -= 0x7FFFFFFF; + h2 = h1; + h1 = h0; + } + + return (h1 << 1); +} + +static void ext2_prep_hashbuf(const char *src, uint32_t slen, uint32_t *dst, + int dlen, int unsigned_char) +{ + uint32_t padding = slen | (slen << 8) | (slen << 16) | (slen << 24); + uint32_t buf_val; + int len, i; + int buf_byte; + const unsigned char *ubuf = (const unsigned char *)src; + const signed char *sbuf = (const signed char *)src; + + if (slen > (uint32_t)dlen) + len = dlen; + else + len = slen; + + buf_val = padding; + + for (i = 0; i < len; i++) { + if (unsigned_char) + buf_byte = (unsigned int)ubuf[i]; + else + buf_byte = (int)sbuf[i]; + + if ((i % 4) == 0) + buf_val = padding; + + buf_val <<= 8; + buf_val += buf_byte; + + if ((i % 4) == 3) { + *dst++ = buf_val; + dlen -= sizeof(uint32_t); + buf_val = padding; + } + } + + dlen -= sizeof(uint32_t); + if (dlen >= 0) + *dst++ = buf_val; + + dlen -= sizeof(uint32_t); + while (dlen >= 0) { + *dst++ = padding; + dlen -= sizeof(uint32_t); + } +} + +int ext2_htree_hash(const char *name, int len, const uint32_t *hash_seed, + int hash_version, uint32_t *hash_major, + uint32_t *hash_minor) +{ + uint32_t hash[4]; + uint32_t data[8]; + uint32_t major = 0, minor = 0; + int unsigned_char = 0; + + if (!name || !hash_major) + return (-1); + + if (len < 1 || len > 255) + goto error; + + hash[0] = 0x67452301; + hash[1] = 0xEFCDAB89; + hash[2] = 0x98BADCFE; + hash[3] = 0x10325476; + + if (hash_seed) + memcpy(hash, hash_seed, sizeof(hash)); + + switch (hash_version) { + case EXT2_HTREE_TEA_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_TEA: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 16, unsigned_char); + ext2_tea(hash, data); + len -= 16; + name += 16; + } + major = hash[0]; + minor = hash[1]; + break; + case EXT2_HTREE_LEGACY_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_LEGACY: + major = ext2_legacy_hash(name, len, unsigned_char); + break; + case EXT2_HTREE_HALF_MD4_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_HALF_MD4: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 32, unsigned_char); + ext2_half_md4(hash, data); + len -= 32; + name += 32; + } + major = hash[1]; + minor = hash[2]; + break; + default: + goto error; + } + + major &= ~1; + if (major == (EXT2_HTREE_EOF << 1)) + major = (EXT2_HTREE_EOF - 1) << 1; + *hash_major = major; + if (hash_minor) + *hash_minor = minor; + + return EOK; + +error: + *hash_major = 0; + if (hash_minor) + *hash_minor = 0; + return ENOTSUP; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_ialloc.c b/clib/lib/lwext4/src/ext4_ialloc.c new file mode 100644 index 0000000..f2c796f --- /dev/null +++ b/clib/lib/lwext4/src/ext4_ialloc.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_ialloc.c + * @brief Inode allocation procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**@brief Convert i-node number to relative index in block group. + * @param sb Superblock + * @param inode I-node number to be converted + * @return Index of the i-node in the block group + */ +static uint32_t ext4_ialloc_inode_to_bgidx(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) % inodes_per_group; +} + +/**@brief Convert relative index of i-node to absolute i-node number. + * @param sb Superblock + * @param index Index to be converted + * @return Absolute number of the i-node + * + */ +static uint32_t ext4_ialloc_bgidx_to_inode(struct ext4_sblock *sb, + uint32_t index, uint32_t bgid) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return bgid * inodes_per_group + (index + 1); +} + +/**@brief Compute block group number from the i-node number. + * @param sb Superblock + * @param inode I-node number to be found the block group for + * @return Block group number computed from i-node number + */ +static uint32_t ext4_ialloc_get_bgid_of_inode(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) / inodes_per_group; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_ialloc_bitmap_csum(struct ext4_sblock *sb, void *bitmap) +{ + uint32_t csum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t inodes_per_group = + ext4_get32(sb, inodes_per_group); + + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode bitmap */ + csum = ext4_crc32c(csum, bitmap, (inodes_per_group + 7) / 8); + } + return csum; +} +#else +#define ext4_ialloc_bitmap_csum(...) 0 +#endif + +void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->inode_bitmap_csum_lo = lo_csum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_bitmap_csum_hi = hi_csum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_ialloc_verify_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->inode_bitmap_csum_lo != lo_csum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->inode_bitmap_csum_hi != hi_csum) + return false; + + return true; +} +#else +#define ext4_ialloc_verify_bitmap_csum(...) true +#endif + +int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + /* Compute index of block group and load it */ + uint32_t block_group = ext4_ialloc_get_bgid_of_inode(sb, index); + + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load i-node bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Free i-node in the bitmap */ + uint32_t index_in_group = ext4_ialloc_inode_to_bgidx(sb, index); + ext4_bmap_bit_clr(b.data, index_in_group); + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + + /* Put back the block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If released i-node is a directory, decrement used directories count + */ + if (is_dir) { + uint32_t bg_used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + bg_used_dirs--; + ext4_bg_set_used_dirs_count(bg, sb, bg_used_dirs); + } + + /* Update block group free inodes count */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + free_inodes++; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + bg_ref.dirty = true; + + /* Put back the modified block group */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock free inodes count */ + ext4_set32(sb, free_inodes_count, + ext4_get32(sb, free_inodes_count) + 1); + + return EOK; +} + +int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + uint32_t bgid = fs->last_inode_bg_id; + uint32_t bg_count = ext4_block_group_cnt(sb); + uint32_t sb_free_inodes = ext4_get32(sb, free_inodes_count); + bool rewind = false; + + /* Try to find free i-node in all block groups */ + while (bgid <= bg_count) { + + if (bgid == bg_count) { + if (rewind) + break; + bg_count = fs->last_inode_bg_id; + bgid = 0; + rewind = true; + continue; + } + + /* Load block group to check */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bgid, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Read necessary values for algorithm */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + uint32_t used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + + /* Check if this block group is good candidate for allocation */ + if (free_inodes > 0) { + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_add = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_add); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Try to allocate i-node in the bitmap */ + uint32_t inodes_in_bg; + uint32_t idx_in_bg; + + inodes_in_bg = ext4_inodes_in_group_cnt(sb, bgid); + rc = ext4_bmap_bit_find_clr(b.data, 0, inodes_in_bg, + &idx_in_bg); + /* Block group has not any free i-node */ + if (rc == ENOSPC) { + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + continue; + } + + ext4_bmap_bit_set(b.data, idx_in_bg); + + /* Free i-node found, save the bitmap */ + ext4_ialloc_set_bitmap_csum(sb,bg, + b.data); + ext4_trans_set_block_dirty(b.buf); + + ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* Modify filesystem counters */ + free_inodes--; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + /* Increment used directories counter */ + if (is_dir) { + used_dirs++; + ext4_bg_set_used_dirs_count(bg, sb, used_dirs); + } + + /* Decrease unused inodes count */ + uint32_t unused = + ext4_bg_get_itable_unused(bg, sb); + + uint32_t free = inodes_in_bg - unused; + + if (idx_in_bg >= free) { + unused = inodes_in_bg - (idx_in_bg + 1); + ext4_bg_set_itable_unused(bg, sb, unused); + } + + /* Save modified block group */ + bg_ref.dirty = true; + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock */ + sb_free_inodes--; + ext4_set32(sb, free_inodes_count, sb_free_inodes); + + /* Compute the absolute i-nodex number */ + *idx = ext4_ialloc_bgidx_to_inode(sb, idx_in_bg, bgid); + + fs->last_inode_bg_id = bgid; + + return EOK; + } + + /* Block group not modified, put it and jump to the next block + * group */ + ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + ++bgid; + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_inode.c b/clib/lib/lwext4/src/ext4_inode.c new file mode 100644 index 0000000..ff3c234 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_inode.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_inode.c + * @brief Inode handle functions + */ + +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Compute number of bits for block count. + * @param block_size Filesystem block_size + * @return Number of bits + */ +static uint32_t ext4_inode_block_bits_count(uint32_t block_size) +{ + uint32_t bits = 8; + uint32_t size = block_size; + + do { + bits++; + size = size >> 1; + } while (size > 256); + + return bits; +} + +uint32_t ext4_inode_get_mode(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint32_t v = to_le16(inode->mode); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) { + v |= ((uint32_t)to_le16(inode->osd2.hurd2.mode_high)) << 16; + } + + return v; +} + +void ext4_inode_set_mode(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t mode) +{ + inode->mode = to_le16((mode << 16) >> 16); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) + inode->osd2.hurd2.mode_high = to_le16(mode >> 16); +} + +uint32_t ext4_inode_get_uid(struct ext4_inode *inode) +{ + return to_le32(inode->uid); +} + +void ext4_inode_set_uid(struct ext4_inode *inode, uint32_t uid) +{ + inode->uid = to_le32(uid); +} + +uint64_t ext4_inode_get_size(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint64_t v = to_le32(inode->size_lo); + + if ((ext4_get32(sb, rev_level) > 0) && + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE))) + v |= ((uint64_t)to_le32(inode->size_hi)) << 32; + + return v; +} + +void ext4_inode_set_size(struct ext4_inode *inode, uint64_t size) +{ + inode->size_lo = to_le32((size << 32) >> 32); + inode->size_hi = to_le32(size >> 32); +} + +uint32_t ext4_inode_get_csum(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t v = to_le16(inode->osd2.linux2.checksum_lo); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + v |= ((uint32_t)to_le16(inode->checksum_hi)) << 16; + + return v; +} + +void ext4_inode_set_csum(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t checksum) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + inode->osd2.linux2.checksum_lo = + to_le16((checksum << 16) >> 16); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + inode->checksum_hi = to_le16(checksum >> 16); + +} + +uint32_t ext4_inode_get_access_time(struct ext4_inode *inode) +{ + return to_le32(inode->access_time); +} +void ext4_inode_set_access_time(struct ext4_inode *inode, uint32_t time) +{ + inode->access_time = to_le32(time); +} + +uint32_t ext4_inode_get_change_inode_time(struct ext4_inode *inode) +{ + return to_le32(inode->change_inode_time); +} +void ext4_inode_set_change_inode_time(struct ext4_inode *inode, uint32_t time) +{ + inode->change_inode_time = to_le32(time); +} + +uint32_t ext4_inode_get_modif_time(struct ext4_inode *inode) +{ + return to_le32(inode->modification_time); +} + +void ext4_inode_set_modif_time(struct ext4_inode *inode, uint32_t time) +{ + inode->modification_time = to_le32(time); +} + +uint32_t ext4_inode_get_del_time(struct ext4_inode *inode) +{ + return to_le32(inode->deletion_time); +} + +void ext4_inode_set_del_time(struct ext4_inode *inode, uint32_t time) +{ + inode->deletion_time = to_le32(time); +} + +uint32_t ext4_inode_get_gid(struct ext4_inode *inode) +{ + return to_le32(inode->gid); +} +void ext4_inode_set_gid(struct ext4_inode *inode, uint32_t gid) +{ + inode->gid = to_le32(gid); +} + +uint16_t ext4_inode_get_links_cnt(struct ext4_inode *inode) +{ + return to_le16(inode->links_count); +} +void ext4_inode_set_links_cnt(struct ext4_inode *inode, uint16_t cnt) +{ + inode->links_count = to_le16(cnt); +} + +uint64_t ext4_inode_get_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode) +{ + uint64_t cnt = to_le32(inode->blocks_count_lo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) { + + /* 48-bit field */ + cnt |= (uint64_t)to_le16(inode->osd2.linux2.blocks_high) << 32; + + if (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_HUGE_FILE)) { + + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t b = ext4_inode_block_bits_count(block_count); + return cnt << (b - 9); + } + } + + return cnt; +} + +int ext4_inode_set_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode, uint64_t count) +{ + /* 32-bit maximum */ + uint64_t max = 0; + max = ~max >> 32; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = 0; + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + + return EOK; + } + + /* Check if there can be used huge files (many blocks) */ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) + return EINVAL; + + /* 48-bit maximum */ + max = 0; + max = ~max >> 16; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + } else { + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t block_bits =ext4_inode_block_bits_count(block_count); + + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + count = count >> (block_bits - 9); + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + } + + return EOK; +} + +uint32_t ext4_inode_get_flags(struct ext4_inode *inode) +{ + return to_le32(inode->flags); +} +void ext4_inode_set_flags(struct ext4_inode *inode, uint32_t flags) +{ + inode->flags = to_le32(flags); +} + +uint32_t ext4_inode_get_generation(struct ext4_inode *inode) +{ + return to_le32(inode->generation); +} +void ext4_inode_set_generation(struct ext4_inode *inode, uint32_t gen) +{ + inode->generation = to_le32(gen); +} + +uint16_t ext4_inode_get_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + return to_le16(inode->extra_isize); + else + return 0; +} + +void ext4_inode_set_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode, + uint16_t size) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + inode->extra_isize = to_le16(size); +} + +uint64_t ext4_inode_get_file_acl(struct ext4_inode *inode, + struct ext4_sblock *sb) +{ + uint64_t v = to_le32(inode->file_acl_lo); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + v |= (uint32_t)to_le16(inode->osd2.linux2.file_acl_high) << 16; + + return v; +} + +void ext4_inode_set_file_acl(struct ext4_inode *inode, struct ext4_sblock *sb, + uint64_t acl) +{ + inode->file_acl_lo = to_le32((acl << 32) >> 32); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + inode->osd2.linux2.file_acl_high = to_le16((uint16_t)(acl >> 32)); +} + +uint32_t ext4_inode_get_direct_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx]); +} +void ext4_inode_set_direct_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx] = to_le32(block); +} + +uint32_t ext4_inode_get_indirect_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK]); +} + +void ext4_inode_set_indirect_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK] = to_le32(block); +} + +uint32_t ext4_inode_get_dev(struct ext4_inode *inode) +{ + uint32_t dev_0, dev_1; + dev_0 = ext4_inode_get_direct_block(inode, 0); + dev_1 = ext4_inode_get_direct_block(inode, 1); + + if (dev_0) + return dev_0; + else + return dev_1; +} + +void ext4_inode_set_dev(struct ext4_inode *inode, uint32_t dev) +{ + if (dev & ~0xFFFF) + ext4_inode_set_direct_block(inode, 1, dev); + else + ext4_inode_set_direct_block(inode, 0, dev); +} + +uint32_t ext4_inode_type(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + return (ext4_inode_get_mode(sb, inode) & EXT4_INODE_MODE_TYPE_MASK); +} + +bool ext4_inode_is_type(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t type) +{ + return ext4_inode_type(sb, inode) == type; +} + +bool ext4_inode_has_flag(struct ext4_inode *inode, uint32_t f) +{ + return ext4_inode_get_flags(inode) & f; +} + +void ext4_inode_clear_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags & (~f); + ext4_inode_set_flags(inode, flags); +} + +void ext4_inode_set_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags | f; + ext4_inode_set_flags(inode, flags); +} + +bool ext4_inode_can_truncate(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + if ((ext4_inode_has_flag(inode, EXT4_INODE_FLAG_APPEND)) || + (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_IMMUTABLE))) + return false; + + if ((ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_DIRECTORY)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_SOFTLINK))) + return true; + + return false; +} + +struct ext4_extent_header * +ext4_inode_get_extent_header(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_journal.c b/clib/lib/lwext4/src/ext4_journal.c new file mode 100644 index 0000000..7874f58 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_journal.c @@ -0,0 +1,2291 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_journal.c + * @brief Journal handle functions + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Revoke entry during journal replay.*/ +struct revoke_entry { + /**@brief Block number not to be replayed.*/ + ext4_fsblk_t block; + + /**@brief For any transaction id smaller + * than trans_id, records of @block + * in those transactions should not + * be replayed.*/ + uint32_t trans_id; + + /**@brief Revoke tree node.*/ + RB_ENTRY(revoke_entry) revoke_node; +}; + +/**@brief Valid journal replay information.*/ +struct recover_info { + /**@brief Starting transaction id.*/ + uint32_t start_trans_id; + + /**@brief Ending transaction id.*/ + uint32_t last_trans_id; + + /**@brief Used as internal argument.*/ + uint32_t this_trans_id; + + /**@brief No of transactions went through.*/ + uint32_t trans_cnt; + + /**@brief RB-Tree storing revoke entries.*/ + RB_HEAD(jbd_revoke, revoke_entry) revoke_root; +}; + +/**@brief Journal replay internal arguments.*/ +struct replay_arg { + /**@brief Journal replay information.*/ + struct recover_info *info; + + /**@brief Current block we are on.*/ + uint32_t *this_block; + + /**@brief Current trans_id we are on.*/ + uint32_t this_trans_id; +}; + +/* Make sure we wrap around the log correctly! */ +#define wrap(sb, var) \ +do { \ + if (var >= jbd_get32((sb), maxlen)) \ + var -= (jbd_get32((sb), maxlen) - jbd_get32((sb), first)); \ +} while (0) + +static inline int32_t +trans_id_diff(uint32_t x, uint32_t y) +{ + int32_t diff = x - y; + return diff; +} + +static int +jbd_revoke_entry_cmp(struct revoke_entry *a, struct revoke_entry *b) +{ + if (a->block > b->block) + return 1; + else if (a->block < b->block) + return -1; + return 0; +} + +static int +jbd_block_rec_cmp(struct jbd_block_rec *a, struct jbd_block_rec *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +static int +jbd_revoke_rec_cmp(struct jbd_revoke_rec *a, struct jbd_revoke_rec *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(jbd_revoke, revoke_entry, revoke_node, + jbd_revoke_entry_cmp, static inline) +RB_GENERATE_INTERNAL(jbd_block, jbd_block_rec, block_rec_node, + jbd_block_rec_cmp, static inline) +RB_GENERATE_INTERNAL(jbd_revoke_tree, jbd_revoke_rec, revoke_node, + jbd_revoke_rec_cmp, static inline) + +#define jbd_alloc_revoke_entry() ext4_calloc(1, sizeof(struct revoke_entry)) +#define jbd_free_revoke_entry(addr) ext4_free(addr) + +static int jbd_has_csum(struct jbd_sb *jbd_sb) +{ + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V2)) + return 2; + + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V3)) + return 3; + + return 0; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_sb_csum(struct jbd_sb *jbd_sb) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(jbd_sb)) { + uint32_t orig_checksum = jbd_sb->checksum; + jbd_set32(jbd_sb, checksum, 0); + /* Calculate crc32c checksum against tho whole superblock */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_sb, + JBD_SUPERBLOCK_SIZE); + jbd_sb->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_sb_csum(...) 0 +#endif + +static void jbd_sb_csum_set(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return; + + jbd_set32(jbd_sb, checksum, jbd_sb_csum(jbd_sb)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_sb_csum(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return true; + + return jbd_sb_csum(jbd_sb) == jbd_get32(jbd_sb, checksum); +} +#else +#define jbd_verify_sb_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = + (struct jbd_block_tail *)((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + uint32_t orig_checksum = tail->checksum; + tail->checksum = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, bhdr, + block_size); + tail->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_meta_csum(...) 0 +#endif + +static void jbd_meta_csum_set(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + tail->checksum = to_be32(jbd_meta_csum(jbd_fs, bhdr)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return jbd_meta_csum(jbd_fs, bhdr) == to_be32(tail->checksum); +} +#else +#define jbd_verify_meta_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint8_t orig_checksum_type = header->chksum_type, + orig_checksum_size = header->chksum_size; + uint32_t orig_checksum = header->chksum[0]; + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, header, + block_size); + + header->chksum_type = orig_checksum_type; + header->chksum_size = orig_checksum_size; + header->chksum[0] = orig_checksum; + } + return checksum; +} +#else +#define jbd_commit_csum(...) 0 +#endif + +static void jbd_commit_csum_set(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = jbd_commit_csum(jbd_fs, header); +} + +#if CONFIG_META_CSUM_ENABLE +static bool jbd_verify_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return header->chksum[0] == to_be32(jbd_commit_csum(jbd_fs, + header)); +} +#else +#define jbd_verify_commit_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +/* + * NOTE: We only make use of @csum parameter when + * JBD_FEATURE_COMPAT_CHECKSUM is enabled. + */ +static uint32_t jbd_block_csum(struct jbd_fs *jbd_fs, const void *buf, + uint32_t csum, + uint32_t sequence) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Then calculate crc32c checksum against sequence no. */ + checksum = ext4_crc32c(checksum, &sequence, + sizeof(uint32_t)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, buf, + block_size); + } else if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32(csum, buf, + block_size); + } + return checksum; +} +#else +#define jbd_block_csum(...) 0 +#endif + +static void jbd_block_tag_csum_set(struct jbd_fs *jbd_fs, void *__tag, + uint32_t checksum) +{ + int ver = jbd_has_csum(&jbd_fs->sb); + if (!ver) + return; + + if (ver == 2) { + struct jbd_block_tag *tag = __tag; + tag->checksum = (uint16_t)to_be32(checksum); + } else { + struct jbd_block_tag3 *tag = __tag; + tag->checksum = to_be32(checksum); + } +} + +/**@brief Write jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_write(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + jbd_sb_csum_set(s); + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_writebytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Read jbd superblock from disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_read(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_readbytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Verify jbd superblock. + * @param sb jbd superblock + * @return true if jbd superblock is valid */ +static bool jbd_verify_sb(struct jbd_sb *sb) +{ + struct jbd_bhdr *header = &sb->header; + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) + return false; + + if (jbd_get32(header, blocktype) != JBD_SUPERBLOCK && + jbd_get32(header, blocktype) != JBD_SUPERBLOCK_V2) + return false; + + return jbd_verify_sb_csum(sb); +} + +/**@brief Write back dirty jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +static int jbd_write_sb(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + if (jbd_fs->dirty) { + rc = jbd_sb_write(jbd_fs, &jbd_fs->sb); + if (rc != EOK) + return rc; + + jbd_fs->dirty = false; + } + return rc; +} + +/**@brief Get reference to jbd filesystem. + * @param fs Filesystem to load journal of + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_get_fs(struct ext4_fs *fs, + struct jbd_fs *jbd_fs) +{ + int rc; + uint32_t journal_ino; + + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + /* See if there is journal inode on this filesystem.*/ + /* FIXME: detection on existance ofbkejournal bdev is + * missing.*/ + journal_ino = ext4_get32(&fs->sb, journal_inode_number); + + rc = ext4_fs_get_inode_ref(fs, + journal_ino, + &jbd_fs->inode_ref); + if (rc != EOK) + return rc; + + rc = jbd_sb_read(jbd_fs, &jbd_fs->sb); + if (rc != EOK) + goto Error; + + if (!jbd_verify_sb(&jbd_fs->sb)) { + rc = EIO; + goto Error; + } + + if (rc == EOK) + jbd_fs->bdev = fs->bdev; + + return rc; +Error: + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + + return rc; +} + +/**@brief Put reference of jbd filesystem. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_put_fs(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + rc = jbd_write_sb(jbd_fs); + + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + return rc; +} + +/**@brief Data block lookup helper. + * @param jbd_fs jbd filesystem + * @param iblock block index + * @param fblock logical block address + * @return standard error code*/ +int jbd_inode_bmap(struct jbd_fs *jbd_fs, + ext4_lblk_t iblock, + ext4_fsblk_t *fblock) +{ + int rc = ext4_fs_get_inode_dblk_idx( + &jbd_fs->inode_ref, + iblock, + fblock, + false); + return rc; +} + +/**@brief jbd block get function (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + struct ext4_blockdev *bdev = jbd_fs->bdev; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + + /* Lookup the logical block address of + * fblock.*/ + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + rc = ext4_block_get(bdev, block, fblock); + + /* If succeeded, mark buffer as BC_FLUSH to indicate + * that data should be written to disk immediately.*/ + if (rc == EOK) { + ext4_bcache_set_flag(block->buf, BC_FLUSH); + /* As we don't want to occupy too much space + * in block cache, we set this buffer BC_TMP.*/ + ext4_bcache_set_flag(block->buf, BC_TMP); + } + + return rc; +} + +/**@brief jbd block get function (through cache, don't read). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get_noread(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + struct ext4_blockdev *bdev = jbd_fs->bdev; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + rc = ext4_block_get_noread(bdev, block, fblock); + if (rc == EOK) + ext4_bcache_set_flag(block->buf, BC_FLUSH); + + return rc; +} + +/**@brief jbd block set procedure (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @return standard error code*/ +static int jbd_block_set(struct jbd_fs *jbd_fs, + struct ext4_block *block) +{ + struct ext4_blockdev *bdev = jbd_fs->bdev; + return ext4_block_set(bdev, block); +} + +/**@brief helper functions to calculate + * block tag size, not including UUID part. + * @param jbd_fs jbd filesystem + * @return tag size in bytes*/ +static int jbd_tag_bytes(struct jbd_fs *jbd_fs) +{ + int size; + + /* It is very easy to deal with the case which + * JBD_FEATURE_INCOMPAT_CSUM_V3 is enabled.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + return sizeof(struct jbd_block_tag3); + + size = sizeof(struct jbd_block_tag); + + /* If JBD_FEATURE_INCOMPAT_CSUM_V2 is enabled, + * add 2 bytes to size.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2)) + size += sizeof(uint16_t); + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + return size; + + /* If block number is 4 bytes in size, + * minus 4 bytes from size */ + return size - sizeof(uint32_t); +} + +/**@brief Tag information. */ +struct tag_info { + /**@brief Tag size in bytes, including UUID part.*/ + int tag_bytes; + + /**@brief block number stored in this tag.*/ + ext4_fsblk_t block; + + /**@brief Is the first 4 bytes of block equals to + * JBD_MAGIC_NUMBER? */ + bool is_escape; + + /**@brief whether UUID part exists or not.*/ + bool uuid_exist; + + /**@brief UUID content if UUID part exists.*/ + uint8_t uuid[UUID_SIZE]; + + /**@brief Is this the last tag? */ + bool last_tag; + + /**@brief crc32c checksum. */ + uint32_t checksum; +}; + +/**@brief Extract information from a block tag. + * @param __tag pointer to the block tag + * @param tag_bytes block tag size of this jbd filesystem + * @param remain_buf_size size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_extract_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int tag_bytes, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + tag_info->tag_bytes = tag_bytes; + tag_info->uuid_exist = false; + tag_info->last_tag = false; + tag_info->is_escape = false; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get32(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->is_escape = true; + + if (!(jbd_get32(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get32(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } else { + struct jbd_block_tag *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get16(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->is_escape = true; + + if (!(jbd_get16(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get16(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } + return EOK; +} + +/**@brief Write information to a block tag. + * @param __tag pointer to the block tag + * @param remain_buf_size size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_write_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + int tag_bytes = jbd_tag_bytes(jbd_fs); + + tag_info->tag_bytes = tag_bytes; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag3)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_LAST_TAG); + + if (tag_info->is_escape) + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_ESCAPE); + + } else { + struct jbd_block_tag *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_LAST_TAG); + + + if (tag_info->is_escape) + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_ESCAPE); + + } + return EOK; +} + +/**@brief Iterate all block tags in a block. + * @param jbd_fs jbd filesystem + * @param __tag_start pointer to the block + * @param tag_tbl_size size of the block + * @param func callback routine to indicate that + * a block tag is found + * @param arg additional argument to be passed to func */ +static void +jbd_iterate_block_table(struct jbd_fs *jbd_fs, + void *__tag_start, + int32_t tag_tbl_size, + void (*func)(struct jbd_fs * jbd_fs, + struct tag_info *tag_info, + void *arg), + void *arg) +{ + char *tag_start, *tag_ptr; + int tag_bytes = jbd_tag_bytes(jbd_fs); + tag_start = __tag_start; + tag_ptr = tag_start; + + /* Cut off the size of block tail storing checksum. */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2) || + JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + while (tag_tbl_size) { + struct tag_info tag_info; + int rc = jbd_extract_block_tag(jbd_fs, + tag_ptr, + tag_bytes, + tag_tbl_size, + &tag_info); + if (rc != EOK) + break; + + if (func) + func(jbd_fs, &tag_info, arg); + + /* Stop the iteration when we reach the last tag. */ + if (tag_info.last_tag) + break; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + } +} + +static void jbd_display_block_tags(struct jbd_fs *jbd_fs, + struct tag_info *tag_info, + void *arg) +{ + uint32_t *iblock = arg; + ext4_dbg(DEBUG_JBD, "Block in block_tag: %" PRIu64 "\n", tag_info->block); + (*iblock)++; + wrap(&jbd_fs->sb, *iblock); + (void)jbd_fs; + return; +} + +static struct revoke_entry * +jbd_revoke_entry_lookup(struct recover_info *info, ext4_fsblk_t block) +{ + struct revoke_entry tmp = { + .block = block + }; + + return RB_FIND(jbd_revoke, &info->revoke_root, &tmp); +} + +/**@brief Replay a block in a transaction. + * @param jbd_fs jbd filesystem + * @param tag_info tag_info of the logged block.*/ +static void jbd_replay_block_tags(struct jbd_fs *jbd_fs, + struct tag_info *tag_info, + void *__arg) +{ + int r; + struct replay_arg *arg = __arg; + struct recover_info *info = arg->info; + uint32_t *this_block = arg->this_block; + struct revoke_entry *revoke_entry; + struct ext4_block journal_block, ext4_block; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + + (*this_block)++; + wrap(&jbd_fs->sb, *this_block); + + /* We replay this block only if the current transaction id + * is equal or greater than that in revoke entry.*/ + revoke_entry = jbd_revoke_entry_lookup(info, tag_info->block); + if (revoke_entry && + trans_id_diff(arg->this_trans_id, revoke_entry->trans_id) <= 0) + return; + + ext4_dbg(DEBUG_JBD, + "Replaying block in block_tag: %" PRIu64 "\n", + tag_info->block); + + r = jbd_block_get(jbd_fs, &journal_block, *this_block); + if (r != EOK) + return; + + /* We need special treatment for ext4 superblock. */ + if (tag_info->block) { + r = ext4_block_get_noread(fs->bdev, &ext4_block, tag_info->block); + if (r != EOK) { + jbd_block_set(jbd_fs, &journal_block); + return; + } + + memcpy(ext4_block.data, + journal_block.data, + jbd_get32(&jbd_fs->sb, blocksize)); + + if (tag_info->is_escape) + ((struct jbd_bhdr *)ext4_block.data)->magic = + to_be32(JBD_MAGIC_NUMBER); + + ext4_bcache_set_dirty(ext4_block.buf); + ext4_block_set(fs->bdev, &ext4_block); + } else { + uint16_t mount_count, state; + mount_count = ext4_get16(&fs->sb, mount_count); + state = ext4_get16(&fs->sb, state); + + memcpy(&fs->sb, + journal_block.data + EXT4_SUPERBLOCK_OFFSET, + EXT4_SUPERBLOCK_SIZE); + + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, state); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, mount_count); + } + + jbd_block_set(jbd_fs, &journal_block); + + return; +} + +/**@brief Add block address to revoke tree, along with + * its transaction id. + * @param info journal replay info + * @param block block address to be replayed.*/ +static void jbd_add_revoke_block_tags(struct recover_info *info, + ext4_fsblk_t block) +{ + struct revoke_entry *revoke_entry; + + ext4_dbg(DEBUG_JBD, "Add block %" PRIu64 " to revoke tree\n", block); + /* If the revoke entry with respect to the block address + * exists already, update its transaction id.*/ + revoke_entry = jbd_revoke_entry_lookup(info, block); + if (revoke_entry) { + revoke_entry->trans_id = info->this_trans_id; + return; + } + + revoke_entry = jbd_alloc_revoke_entry(); + ext4_assert(revoke_entry); + revoke_entry->block = block; + revoke_entry->trans_id = info->this_trans_id; + RB_INSERT(jbd_revoke, &info->revoke_root, revoke_entry); + + return; +} + +static void jbd_destroy_revoke_tree(struct recover_info *info) +{ + while (!RB_EMPTY(&info->revoke_root)) { + struct revoke_entry *revoke_entry = + RB_MIN(jbd_revoke, &info->revoke_root); + ext4_assert(revoke_entry); + RB_REMOVE(jbd_revoke, &info->revoke_root, revoke_entry); + jbd_free_revoke_entry(revoke_entry); + } +} + + +#define ACTION_SCAN 0 +#define ACTION_REVOKE 1 +#define ACTION_RECOVER 2 + +/**@brief Add entries in a revoke block to revoke tree. + * @param jbd_fs jbd filesystem + * @param header revoke block header + * @param info journal replay info*/ +static void jbd_build_revoke_tree(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct recover_info *info) +{ + char *blocks_entry; + struct jbd_revoke_header *revoke_hdr = + (struct jbd_revoke_header *)header; + uint32_t i, nr_entries, record_len = 4; + + /* If we are working on a 64bit jbd filesystem, */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + nr_entries = (jbd_get32(revoke_hdr, count) - + sizeof(struct jbd_revoke_header)) / + record_len; + + blocks_entry = (char *)(revoke_hdr + 1); + + for (i = 0;i < nr_entries;i++) { + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be64(*blocks)); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be32(*blocks)); + } + blocks_entry += record_len; + } +} + +static void jbd_debug_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + uint32_t *iblock) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_display_block_tags, + iblock); +} + +static void jbd_replay_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct replay_arg *arg) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_replay_block_tags, + arg); +} + +/**@brief The core routine of journal replay. + * @param jbd_fs jbd filesystem + * @param info journal replay info + * @param action action needed to be taken + * @return standard error code*/ +static int jbd_iterate_log(struct jbd_fs *jbd_fs, + struct recover_info *info, + int action) +{ + int r = EOK; + bool log_end = false; + struct jbd_sb *sb = &jbd_fs->sb; + uint32_t start_trans_id, this_trans_id; + uint32_t start_block, this_block; + + /* We start iterating valid blocks in the whole journal.*/ + start_trans_id = this_trans_id = jbd_get32(sb, sequence); + start_block = this_block = jbd_get32(sb, start); + if (action == ACTION_SCAN) + info->trans_cnt = 0; + else if (!info->trans_cnt) + log_end = true; + + ext4_dbg(DEBUG_JBD, "Start of journal at trans id: %" PRIu32 "\n", + start_trans_id); + + while (!log_end) { + struct ext4_block block; + struct jbd_bhdr *header; + /* If we are not scanning for the last + * valid transaction in the journal, + * we will stop when we reach the end of + * the journal.*/ + if (action != ACTION_SCAN) + if (trans_id_diff(this_trans_id, info->last_trans_id) > 0) { + log_end = true; + continue; + } + + r = jbd_block_get(jbd_fs, &block, this_block); + if (r != EOK) + break; + + header = (struct jbd_bhdr *)block.data; + /* This block does not have a valid magic number, + * so we have reached the end of the journal.*/ + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) { + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + /* If the transaction id we found is not expected, + * we may have reached the end of the journal. + * + * If we are not scanning the journal, something + * bad might have taken place. :-( */ + if (jbd_get32(header, sequence) != this_trans_id) { + if (action != ACTION_SCAN) + r = EIO; + + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + switch (jbd_get32(header, blocktype)) { + case JBD_DESCRIPTOR_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Descriptor block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Descriptor block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_RECOVER) { + struct replay_arg replay_arg; + replay_arg.info = info; + replay_arg.this_block = &this_block; + replay_arg.this_trans_id = this_trans_id; + + jbd_replay_descriptor_block(jbd_fs, + header, &replay_arg); + } else + jbd_debug_descriptor_block(jbd_fs, + header, &this_block); + + break; + case JBD_COMMIT_BLOCK: + if (!jbd_verify_commit_csum(jbd_fs, + (struct jbd_commit_header *)header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Commit block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Commit block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + /* + * This is the end of a transaction, + * we may now proceed to the next transaction. + */ + this_trans_id++; + if (action == ACTION_SCAN) + info->trans_cnt++; + break; + case JBD_REVOKE_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Revoke block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Revoke block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_REVOKE) { + info->this_trans_id = this_trans_id; + jbd_build_revoke_tree(jbd_fs, + header, info); + } + break; + default: + log_end = true; + break; + } + jbd_block_set(jbd_fs, &block); + this_block++; + wrap(sb, this_block); + if (this_block == start_block) + log_end = true; + + } + ext4_dbg(DEBUG_JBD, "End of journal.\n"); + if (r == EOK && action == ACTION_SCAN) { + /* We have finished scanning the journal. */ + info->start_trans_id = start_trans_id; + if (trans_id_diff(this_trans_id, start_trans_id) > 0) + info->last_trans_id = this_trans_id - 1; + else + info->last_trans_id = this_trans_id; + } + + return r; +} + +/**@brief Replay journal. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_recover(struct jbd_fs *jbd_fs) +{ + int r; + struct recover_info info; + struct jbd_sb *sb = &jbd_fs->sb; + if (!sb->start) + return EOK; + + RB_INIT(&info.revoke_root); + + r = jbd_iterate_log(jbd_fs, &info, ACTION_SCAN); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_REVOKE); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_RECOVER); + if (r == EOK) { + /* If we successfully replay the journal, + * clear EXT4_FINCOM_RECOVER flag on the + * ext4 superblock, and set the start of + * journal to 0.*/ + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + jbd_set32(&jbd_fs->sb, start, 0); + jbd_set32(&jbd_fs->sb, sequence, info.last_trans_id); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + jbd_fs->dirty = true; + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + } + jbd_destroy_revoke_tree(&info); + return r; +} + +static void jbd_journal_write_sb(struct jbd_journal *journal) +{ + struct jbd_fs *jbd_fs = journal->jbd_fs; + jbd_set32(&jbd_fs->sb, start, journal->start); + jbd_set32(&jbd_fs->sb, sequence, journal->trans_id); + jbd_fs->dirty = true; +} + +/**@brief Start accessing the journal. + * @param jbd_fs jbd filesystem + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_start(struct jbd_fs *jbd_fs, + struct jbd_journal *journal) +{ + int r; + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + features_incompatible |= EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->first = jbd_get32(&jbd_fs->sb, first); + journal->start = journal->first; + journal->last = journal->first; + /* + * To invalidate any stale records we need to start from + * the checkpoint transaction ID of the previous journalling session + * plus 1. + */ + journal->trans_id = jbd_get32(&jbd_fs->sb, sequence) + 1; + journal->alloc_trans_id = journal->trans_id; + + journal->block_size = jbd_get32(&jbd_fs->sb, blocksize); + + TAILQ_INIT(&journal->cp_queue); + RB_INIT(&journal->block_rec_root); + journal->jbd_fs = jbd_fs; + jbd_journal_write_sb(journal); + r = jbd_write_sb(jbd_fs); + if (r != EOK) + return r; + + jbd_fs->bdev->journal = journal; + return EOK; +} + +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf __unused, + int res, + void *arg); + +/* + * This routine is only suitable to committed transactions. */ +static void jbd_journal_flush_trans(struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_journal *journal = trans->journal; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + void *tmp_data = ext4_malloc(journal->block_size); + ext4_assert(tmp_data); + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_buf *buf; + struct ext4_block block; + /* The buffer is not yet flushed. */ + buf = ext4_bcache_find_get(fs->bdev->bc, &block, + jbd_buf->block_rec->lba); + if (!(buf && ext4_bcache_test_flag(buf, BC_UPTODATE) && + jbd_buf->block_rec->trans == trans)) { + int r; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(); + r = jbd_block_get(journal->jbd_fs, + &jbd_block, + jbd_buf->jbd_lba); + ext4_assert(r == EOK); + memcpy(tmp_data, jbd_block.data, + journal->block_size); + ext4_block_set(fs->bdev, &jbd_block); + r = ext4_blocks_set_direct(fs->bdev, tmp_data, + jbd_buf->block_rec->lba, 1); + jbd_trans_end_write(fs->bdev->bc, buf, r, jbd_buf); + } else + ext4_block_flush_buf(fs->bdev, buf); + + if (buf) + ext4_block_set(fs->bdev, &block); + } + + ext4_free(tmp_data); +} + +static void +jbd_journal_skip_pure_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_free_trans(journal, + trans, false); + jbd_journal_write_sb(journal); +} + +void +jbd_journal_purge_cp_trans(struct jbd_journal *journal, + bool flush, + bool once) +{ + struct jbd_trans *trans; + while ((trans = TAILQ_FIRST(&journal->cp_queue))) { + if (!trans->data_cnt) { + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_skip_pure_revoke(journal, trans); + } else { + if (trans->data_cnt == + trans->written_cnt) { + journal->start = + trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_free_trans(journal, + trans, + false); + jbd_journal_write_sb(journal); + } else if (!flush) { + journal->start = + trans->start_iblock; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id; + jbd_journal_write_sb(journal); + break; + } else + jbd_journal_flush_trans(trans); + } + if (once) + break; + } +} + +/**@brief Stop accessing the journal. + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_stop(struct jbd_journal *journal) +{ + int r; + struct jbd_fs *jbd_fs = journal->jbd_fs; + uint32_t features_incompatible; + + /* Make sure that journalled content have reached + * the disk.*/ + jbd_journal_purge_cp_trans(journal, true, false); + + /* There should be no block record in this journal + * session. */ + if (!RB_EMPTY(&journal->block_rec_root)) + ext4_dbg(DEBUG_JBD, + DBG_WARN "There are still block records " + "in this journal session!\n"); + + features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->start = 0; + journal->trans_id = 0; + jbd_journal_write_sb(journal); + return jbd_write_sb(journal->jbd_fs); +} + +/**@brief Allocate a block in the journal. + * @param journal current journal session + * @param trans transaction + * @return allocated block address*/ +static uint32_t jbd_journal_alloc_block(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + uint32_t start_block; + + start_block = journal->last++; + trans->alloc_blocks++; + wrap(&journal->jbd_fs->sb, journal->last); + + /* If there is no space left, flush just one journalled + * transaction.*/ + if (journal->last == journal->start) { + jbd_journal_purge_cp_trans(journal, true, true); + ext4_assert(journal->last != journal->start); + } + + return start_block; +} + +static struct jbd_block_rec * +jbd_trans_block_rec_lookup(struct jbd_journal *journal, + ext4_fsblk_t lba) +{ + struct jbd_block_rec tmp = { + .lba = lba + }; + + return RB_FIND(jbd_block, + &journal->block_rec_root, + &tmp); +} + +static void +jbd_trans_change_ownership(struct jbd_block_rec *block_rec, + struct jbd_trans *new_trans) +{ + LIST_REMOVE(block_rec, tbrec_node); + if (new_trans) { + /* Now this block record belongs to this transaction. */ + LIST_INSERT_HEAD(&new_trans->tbrec_list, block_rec, tbrec_node); + } + block_rec->trans = new_trans; +} + +static inline struct jbd_block_rec * +jbd_trans_insert_block_rec(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_block_rec *block_rec; + block_rec = jbd_trans_block_rec_lookup(trans->journal, lba); + if (block_rec) { + jbd_trans_change_ownership(block_rec, trans); + return block_rec; + } + block_rec = ext4_calloc(1, sizeof(struct jbd_block_rec)); + if (!block_rec) + return NULL; + + block_rec->lba = lba; + block_rec->trans = trans; + TAILQ_INIT(&block_rec->dirty_buf_queue); + LIST_INSERT_HEAD(&trans->tbrec_list, block_rec, tbrec_node); + RB_INSERT(jbd_block, &trans->journal->block_rec_root, block_rec); + return block_rec; +} + +/* + * This routine will do the dirty works. + */ +static void +jbd_trans_finish_callback(struct jbd_journal *journal, + const struct jbd_trans *trans, + struct jbd_block_rec *block_rec, + bool abort, + bool revoke) +{ + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + if (block_rec->trans != trans) + return; + + if (!abort) { + struct jbd_buf *jbd_buf, *tmp; + TAILQ_FOREACH_SAFE(jbd_buf, + &block_rec->dirty_buf_queue, + dirty_buf_node, + tmp) { + jbd_trans_end_write(fs->bdev->bc, + NULL, + EOK, + jbd_buf); + } + } else { + /* + * We have to roll back data if the block is going to be + * aborted. + */ + struct jbd_buf *jbd_buf; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(), + block = EXT4_BLOCK_ZERO(); + jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + if (jbd_buf) { + if (!revoke) { + int r; + r = ext4_block_get_noread(fs->bdev, + &block, + block_rec->lba); + ext4_assert(r == EOK); + r = jbd_block_get(journal->jbd_fs, + &jbd_block, + jbd_buf->jbd_lba); + ext4_assert(r == EOK); + memcpy(block.data, jbd_block.data, + journal->block_size); + + jbd_trans_change_ownership(block_rec, + jbd_buf->trans); + + block.buf->end_write = jbd_trans_end_write; + block.buf->end_write_arg = jbd_buf; + + ext4_bcache_set_flag(jbd_block.buf, BC_TMP); + ext4_bcache_set_dirty(block.buf); + + ext4_block_set(fs->bdev, &jbd_block); + ext4_block_set(fs->bdev, &block); + return; + } else { + /* The revoked buffer is yet written. */ + jbd_trans_change_ownership(block_rec, + jbd_buf->trans); + } + } + } +} + +static inline void +jbd_trans_remove_block_rec(struct jbd_journal *journal, + struct jbd_block_rec *block_rec, + struct jbd_trans *trans) +{ + /* If this block record doesn't belong to this transaction, + * give up.*/ + if (block_rec->trans == trans) { + LIST_REMOVE(block_rec, tbrec_node); + RB_REMOVE(jbd_block, + &journal->block_rec_root, + block_rec); + ext4_free(block_rec); + } +} + +/**@brief Add block to a transaction and mark it dirty. + * @param trans transaction + * @param block block descriptor + * @return standard error code*/ +int jbd_trans_set_block_dirty(struct jbd_trans *trans, + struct ext4_block *block) +{ + struct jbd_buf *jbd_buf; + struct jbd_revoke_rec *rec, tmp_rec = { + .lba = block->lb_id + }; + struct jbd_block_rec *block_rec; + + if (block->buf->end_write == jbd_trans_end_write) { + jbd_buf = block->buf->end_write_arg; + if (jbd_buf && jbd_buf->trans == trans) + return EOK; + } + jbd_buf = ext4_calloc(1, sizeof(struct jbd_buf)); + if (!jbd_buf) + return ENOMEM; + + if ((block_rec = jbd_trans_insert_block_rec(trans, + block->lb_id)) == NULL) { + ext4_free(jbd_buf); + return ENOMEM; + } + + TAILQ_INSERT_TAIL(&block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block_rec = block_rec; + jbd_buf->trans = trans; + jbd_buf->block = *block; + ext4_bcache_inc_ref(block->buf); + + /* If the content reach the disk, notify us + * so that we may do a checkpoint. */ + block->buf->end_write = jbd_trans_end_write; + block->buf->end_write_arg = jbd_buf; + + trans->data_cnt++; + TAILQ_INSERT_HEAD(&trans->buf_queue, jbd_buf, buf_node); + + ext4_bcache_set_dirty(block->buf); + rec = RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec); + if (rec) { + RB_REMOVE(jbd_revoke_tree, &trans->revoke_root, + rec); + ext4_free(rec); + } + + return EOK; +} + +/**@brief Add block to be revoked to a transaction + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_revoke_rec tmp_rec = { + .lba = lba + }, *rec; + rec = RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec); + if (rec) + return EOK; + + rec = ext4_calloc(1, sizeof(struct jbd_revoke_rec)); + if (!rec) + return ENOMEM; + + rec->lba = lba; + RB_INSERT(jbd_revoke_tree, &trans->revoke_root, rec); + return EOK; +} + +/**@brief Try to add block to be revoked to a transaction. + * If @lba still remains in an transaction on checkpoint + * queue, add @lba as a revoked block to the transaction. + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_try_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_journal *journal = trans->journal; + struct jbd_block_rec *block_rec = + jbd_trans_block_rec_lookup(journal, lba); + + if (block_rec) { + if (block_rec->trans == trans) { + struct jbd_buf *jbd_buf = + TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + /* If there are still unwritten buffers. */ + if (TAILQ_FIRST(&block_rec->dirty_buf_queue) != + jbd_buf) + jbd_trans_revoke_block(trans, lba); + + } else + jbd_trans_revoke_block(trans, lba); + } + + return EOK; +} + +/**@brief Free a transaction + * @param journal current journal session + * @param trans transaction + * @param abort discard all the modifications on the block?*/ +void jbd_journal_free_trans(struct jbd_journal *journal, + struct jbd_trans *trans, + bool abort) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_revoke_rec *rec, *tmp2; + struct jbd_block_rec *block_rec, *tmp3; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + block_rec = jbd_buf->block_rec; + if (abort) { + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + ext4_bcache_clear_dirty(jbd_buf->block.buf); + ext4_block_set(fs->bdev, &jbd_buf->block); + } + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + jbd_trans_finish_callback(journal, + trans, + block_rec, + abort, + false); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + } + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp2) { + RB_REMOVE(jbd_revoke_tree, &trans->revoke_root, rec); + ext4_free(rec); + } + LIST_FOREACH_SAFE(block_rec, &trans->tbrec_list, tbrec_node, + tmp3) { + jbd_trans_remove_block_rec(journal, block_rec, trans); + } + + ext4_free(trans); +} + +/**@brief Write commit block for a transaction + * @param trans transaction + * @return standard error code*/ +static int jbd_trans_write_commit_block(struct jbd_trans *trans) +{ + int rc; + struct ext4_block block; + struct jbd_commit_header *header; + uint32_t commit_iblock; + struct jbd_journal *journal = trans->journal; + + commit_iblock = jbd_journal_alloc_block(journal, trans); + + rc = jbd_block_get_noread(journal->jbd_fs, &block, commit_iblock); + if (rc != EOK) + return rc; + + header = (struct jbd_commit_header *)block.data; + jbd_set32(&header->header, magic, JBD_MAGIC_NUMBER); + jbd_set32(&header->header, blocktype, JBD_COMMIT_BLOCK); + jbd_set32(&header->header, sequence, trans->trans_id); + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + header->chksum_type = JBD_CRC32_CHKSUM; + header->chksum_size = JBD_CRC32_CHKSUM_SIZE; + jbd_set32(header, chksum[0], trans->data_csum); + } + jbd_commit_csum_set(journal->jbd_fs, header); + ext4_bcache_set_dirty(block.buf); + ext4_bcache_set_flag(block.buf, BC_TMP); + rc = jbd_block_set(journal->jbd_fs, &block); + return rc; +} + +/**@brief Write descriptor block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int jbd_journal_prepare(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + struct ext4_block desc_block = EXT4_BLOCK_ZERO(), + data_block = EXT4_BLOCK_ZERO(); + int32_t tag_tbl_size = 0; + uint32_t desc_iblock = 0; + uint32_t data_iblock = 0; + char *tag_start = NULL, *tag_ptr = NULL; + struct jbd_buf *jbd_buf, *tmp; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + uint32_t checksum = EXT4_CRC32_INIT; + struct jbd_bhdr *bhdr = NULL; + void *data; + + /* Try to remove any non-dirty buffers from the tail of + * buf_queue. */ + TAILQ_FOREACH_REVERSE_SAFE(jbd_buf, &trans->buf_queue, + jbd_trans_buf, buf_node, tmp) { + struct jbd_revoke_rec tmp_rec = { + .lba = jbd_buf->block_rec->lba + }; + /* We stop the iteration when we find a dirty buffer. */ + if (ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) + break; + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true, + RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec)); + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + } + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, tmp) { + struct tag_info tag_info; + bool uuid_exist = false; + bool is_escape = false; + struct jbd_revoke_rec tmp_rec = { + .lba = jbd_buf->block_rec->lba + }; + if (!ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) { + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + + /* The buffer has not been modified, just release + * that jbd_buf. */ + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true, + RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec)); + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + continue; + } + checksum = jbd_block_csum(journal->jbd_fs, + jbd_buf->block.data, + checksum, + trans->trans_id); + if (((struct jbd_bhdr *)jbd_buf->block.data)->magic == + to_be32(JBD_MAGIC_NUMBER)) + is_escape = true; + +again: + if (!desc_iblock) { + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &desc_block, desc_iblock); + if (rc != EOK) + break; + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_DESCRIPTOR_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + tag_start = (char *)(bhdr + 1); + tag_ptr = tag_start; + uuid_exist = true; + tag_tbl_size = journal->block_size - + sizeof(struct jbd_bhdr); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + ext4_bcache_set_dirty(desc_block.buf); + ext4_bcache_set_flag(desc_block.buf, BC_TMP); + } + tag_info.block = jbd_buf->block.lb_id; + tag_info.uuid_exist = uuid_exist; + tag_info.is_escape = is_escape; + if (i == trans->data_cnt - 1) + tag_info.last_tag = true; + else + tag_info.last_tag = false; + + tag_info.checksum = checksum; + + if (uuid_exist) + memcpy(tag_info.uuid, journal->jbd_fs->sb.uuid, + UUID_SIZE); + + rc = jbd_write_block_tag(journal->jbd_fs, + tag_ptr, + tag_tbl_size, + &tag_info); + if (rc != EOK) { + jbd_meta_csum_set(journal->jbd_fs, bhdr); + desc_iblock = 0; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + if (rc != EOK) + break; + + goto again; + } + + data_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &data_block, data_iblock); + if (rc != EOK) { + desc_iblock = 0; + ext4_bcache_clear_dirty(desc_block.buf); + jbd_block_set(journal->jbd_fs, &desc_block); + break; + } + + data = data_block.data; + memcpy(data, jbd_buf->block.data, + journal->block_size); + if (is_escape) + ((struct jbd_bhdr *)data)->magic = 0; + + ext4_bcache_set_dirty(data_block.buf); + ext4_bcache_set_flag(data_block.buf, BC_TMP); + rc = jbd_block_set(journal->jbd_fs, &data_block); + if (rc != EOK) { + desc_iblock = 0; + ext4_bcache_clear_dirty(desc_block.buf); + jbd_block_set(journal->jbd_fs, &desc_block); + break; + } + jbd_buf->jbd_lba = data_iblock; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + + i++; + } + if (rc == EOK && desc_iblock) { + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)bhdr); + trans->data_csum = checksum; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Write revoke block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int +jbd_journal_prepare_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + struct ext4_block desc_block = EXT4_BLOCK_ZERO(); + int32_t tag_tbl_size = 0; + uint32_t desc_iblock = 0; + char *blocks_entry = NULL; + struct jbd_revoke_rec *rec, *tmp; + struct jbd_revoke_header *header = NULL; + int32_t record_len = 4; + struct jbd_bhdr *bhdr = NULL; + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp) { +again: + if (!desc_iblock) { + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &desc_block, + desc_iblock); + if (rc != EOK) + break; + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_REVOKE_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + header = (struct jbd_revoke_header *)bhdr; + blocks_entry = (char *)(header + 1); + tag_tbl_size = journal->block_size - + sizeof(struct jbd_revoke_header); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + ext4_bcache_set_dirty(desc_block.buf); + ext4_bcache_set_flag(desc_block.buf, BC_TMP); + } + + if (tag_tbl_size < record_len) { + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + jbd_meta_csum_set(journal->jbd_fs, bhdr); + bhdr = NULL; + desc_iblock = 0; + header = NULL; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + if (rc != EOK) + break; + + goto again; + } + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + *blocks = to_be64(rec->lba); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + *blocks = to_be32((uint32_t)rec->lba); + } + blocks_entry += record_len; + tag_tbl_size -= record_len; + + i++; + } + if (rc == EOK && desc_iblock) { + if (header != NULL) + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + + jbd_meta_csum_set(journal->jbd_fs, bhdr); + rc = jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Put references of block descriptors in a transaction. + * @param journal current journal session + * @param trans transaction*/ +void jbd_journal_cp_trans(struct jbd_journal *journal, struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_block block = jbd_buf->block; + ext4_block_set(fs->bdev, &block); + } +} + +/**@brief Update the start block of the journal when + * all the contents in a transaction reach the disk.*/ +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf, + int res, + void *arg) +{ + struct jbd_buf *jbd_buf = arg; + struct jbd_trans *trans = jbd_buf->trans; + struct jbd_block_rec *block_rec = jbd_buf->block_rec; + struct jbd_journal *journal = trans->journal; + bool first_in_queue = + trans == TAILQ_FIRST(&journal->cp_queue); + if (res != EOK) + trans->error = res; + + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + TAILQ_REMOVE(&block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + false, + false); + if (block_rec->trans == trans && buf) { + /* Clear the end_write and end_write_arg fields. */ + buf->end_write = NULL; + buf->end_write_arg = NULL; + } + + ext4_free(jbd_buf); + + trans->written_cnt++; + if (trans->written_cnt == trans->data_cnt) { + /* If it is the first transaction on checkpoint queue, + * we will shift the start of the journal to the next + * transaction, and remove subsequent written + * transactions from checkpoint queue until we find + * an unwritten one. */ + if (first_in_queue) { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, trans, trans_node); + jbd_journal_free_trans(journal, trans, false); + + jbd_journal_purge_cp_trans(journal, false, false); + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + } + } +} + +/**@brief Commit a transaction to the journal immediately. + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int __jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK; + uint32_t last = journal->last; + struct jbd_revoke_rec *rec, *tmp; + + trans->trans_id = journal->alloc_trans_id; + rc = jbd_journal_prepare(journal, trans); + if (rc != EOK) + goto Finish; + + rc = jbd_journal_prepare_revoke(journal, trans); + if (rc != EOK) + goto Finish; + + if (TAILQ_EMPTY(&trans->buf_queue) && + RB_EMPTY(&trans->revoke_root)) { + /* Since there are no entries in both buffer list + * and revoke entry list, we do not consider trans as + * complete transaction and just return EOK.*/ + jbd_journal_free_trans(journal, trans, false); + goto Finish; + } + + rc = jbd_trans_write_commit_block(trans); + if (rc != EOK) + goto Finish; + + journal->alloc_trans_id++; + + /* Complete the checkpoint of buffers which are revoked. */ + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp) { + struct jbd_block_rec *block_rec = + jbd_trans_block_rec_lookup(journal, rec->lba); + struct jbd_buf *jbd_buf = NULL; + if (block_rec) + jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + if (jbd_buf) { + struct ext4_buf *buf; + struct ext4_block block = EXT4_BLOCK_ZERO(); + /* + * We do this to reset the ext4_buf::end_write and + * ext4_buf::end_write_arg fields so that the checkpoint + * callback won't be triggered again. + */ + buf = ext4_bcache_find_get(journal->jbd_fs->bdev->bc, + &block, + jbd_buf->block_rec->lba); + jbd_trans_end_write(journal->jbd_fs->bdev->bc, + buf, + EOK, + jbd_buf); + if (buf) + ext4_block_set(journal->jbd_fs->bdev, &block); + } + } + + if (TAILQ_EMPTY(&journal->cp_queue)) { + /* + * This transaction is going to be the first object in the + * checkpoint queue. + * When the first transaction in checkpoint queue is completely + * written to disk, we shift the tail of the log to right. + */ + if (trans->data_cnt) { + journal->start = trans->start_iblock; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id; + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + jbd_journal_cp_trans(journal, trans); + } else { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_write_sb(journal); + jbd_journal_free_trans(journal, trans, false); + } + } else { + /* No need to do anything to the JBD superblock. */ + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + if (trans->data_cnt) + jbd_journal_cp_trans(journal, trans); + } +Finish: + if (rc != EOK && rc != ENOSPC) { + journal->last = last; + jbd_journal_free_trans(journal, trans, true); + } + return rc; +} + +/**@brief Allocate a new transaction + * @param journal current journal session + * @return transaction allocated*/ +struct jbd_trans * +jbd_journal_new_trans(struct jbd_journal *journal) +{ + struct jbd_trans *trans = NULL; + trans = ext4_calloc(1, sizeof(struct jbd_trans)); + if (!trans) + return NULL; + + /* We will assign a trans_id to this transaction, + * once it has been committed.*/ + trans->journal = journal; + trans->data_csum = EXT4_CRC32_INIT; + trans->error = EOK; + TAILQ_INIT(&trans->buf_queue); + return trans; +} + +/**@brief Commit a transaction to the journal immediately. + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +int jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int r = EOK; + r = __jbd_journal_commit_trans(journal, trans); + return r; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_mbr.c b/clib/lib/lwext4/src/ext4_mbr.c new file mode 100644 index 0000000..0376545 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_mbr.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mbr.c + * @brief Master boot record parser + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define MBR_SIGNATURE 0xAA55 + +#pragma pack(push, 1) + +struct ext4_part_entry { + uint8_t status; + uint8_t chs1[3]; + uint8_t type; + uint8_t chs2[3]; + uint32_t first_lba; + uint32_t sectors; +}; + +struct ext4_mbr { + uint8_t bootstrap[442]; + uint32_t disk_id; + struct ext4_part_entry part_entry[4]; + uint16_t signature; +}; + +#pragma pack(pop) + +int ext4_mbr_scan(struct ext4_blockdev *parent, struct ext4_mbr_bdevs *bdevs) +{ + int r; + size_t i; + + ext4_dbg(DEBUG_MBR, DBG_INFO "ext4_mbr_scan\n"); + memset(bdevs, 0, sizeof(struct ext4_mbr_bdevs)); + r = ext4_block_init(parent); + if (r != EOK) + return r; + + r = ext4_block_readbytes(parent, 0, parent->bdif->ph_bbuf, 512); + if (r != EOK) { + goto blockdev_fini; + } + + const struct ext4_mbr *mbr = (void *)parent->bdif->ph_bbuf; + + if (to_le16(mbr->signature) != MBR_SIGNATURE) { + ext4_dbg(DEBUG_MBR, DBG_ERROR "ext4_mbr_scan: unknown " + "signature: 0x%x\n", to_le16(mbr->signature)); + r = ENOENT; + goto blockdev_fini; + } + + /*Show bootstrap code*/ + ext4_dbg(DEBUG_MBR, "mbr_part: bootstrap:"); + for (i = 0; i < sizeof(mbr->bootstrap); ++i) { + if (!(i & 0xF)) + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n"); + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "%02x, ", mbr->bootstrap[i]); + } + + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n\n"); + for (i = 0; i < 4; ++i) { + const struct ext4_part_entry *pe = &mbr->part_entry[i]; + ext4_dbg(DEBUG_MBR, "mbr_part: %d\n", (int)i); + ext4_dbg(DEBUG_MBR, "\tstatus: 0x%x\n", pe->status); + ext4_dbg(DEBUG_MBR, "\ttype 0x%x:\n", pe->type); + ext4_dbg(DEBUG_MBR, "\tfirst_lba: 0x%"PRIx32"\n", pe->first_lba); + ext4_dbg(DEBUG_MBR, "\tsectors: 0x%"PRIx32"\n", pe->sectors); + + if (!pe->sectors) + continue; /*Empty entry*/ + + if (pe->type != 0x83) + continue; /*Unsupported entry. 0x83 - linux native*/ + + bdevs->partitions[i].bdif = parent->bdif; + bdevs->partitions[i].part_offset = + (uint64_t)pe->first_lba * parent->bdif->ph_bsize; + bdevs->partitions[i].part_size = + (uint64_t)pe->sectors * parent->bdif->ph_bsize; + } + + blockdev_fini: + ext4_block_fini(parent); + return r; +} + +int ext4_mbr_write(struct ext4_blockdev *parent, struct ext4_mbr_parts *parts, uint32_t disk_id) +{ + int r; + uint64_t disk_size; + uint32_t division_sum = parts->division[0] + parts->division[1] + + parts->division[2] + parts->division[3]; + + if (division_sum > 100) + return EINVAL; + + ext4_dbg(DEBUG_MBR, DBG_INFO "ext4_mbr_write\n"); + r = ext4_block_init(parent); + if (r != EOK) + return r; + + disk_size = parent->part_size; + + /*Calculate CHS*/ + uint32_t k = 16; + while ((k < 256) && ((disk_size / k / 63) > 1024)) + k *= 2; + + if (k == 256) + --k; + + const uint32_t cyl_size = 63 * k; + const uint32_t cyl_count = disk_size / cyl_size; + + struct ext4_mbr *mbr = (void *)parent->bdif->ph_bbuf; + memset(mbr, 0, sizeof(struct ext4_mbr)); + + mbr->disk_id = disk_id; + + uint32_t cyl_it = 0; + for (int i = 0; i < 4; ++i) { + uint32_t cyl_part = cyl_count * parts->division[i] / 100; + if (!cyl_part) + continue; + + uint32_t part_start = cyl_it * cyl_size; + uint32_t part_size = cyl_part * cyl_size; + + if (i == 0) { + part_start += 63; + part_size -= 63 * parent->bdif->ph_bsize; + } + + uint32_t cyl_end = cyl_part + cyl_it - 1; + + mbr->part_entry[i].status = 0; + mbr->part_entry[i].chs1[0] = i ? 0 : 1;; + mbr->part_entry[i].chs1[1] = (cyl_it >> 2) + 1; + mbr->part_entry[i].chs1[2] = cyl_it; + mbr->part_entry[i].type = 0x83; + mbr->part_entry[i].chs2[0] = k - 1; + mbr->part_entry[i].chs2[1] = (cyl_end >> 2) + 63; + mbr->part_entry[i].chs2[2] = cyl_end; + + mbr->part_entry[i].first_lba = part_start; + mbr->part_entry[i].sectors = part_size / parent->bdif->ph_bsize; + + cyl_it += cyl_part; + } + + mbr->signature = MBR_SIGNATURE; + r = ext4_block_writebytes(parent, 0, parent->bdif->ph_bbuf, 512); + if (r != EOK) + goto blockdev_fini; + + + blockdev_fini: + ext4_block_fini(parent); + return r; +} + +/** + * @} + */ + diff --git a/clib/lib/lwext4/src/ext4_mkfs.c b/clib/lib/lwext4/src/ext4_mkfs.c new file mode 100644 index 0000000..0dfc91f --- /dev/null +++ b/clib/lib/lwext4/src/ext4_mkfs.c @@ -0,0 +1,865 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mkfs.c + * @brief + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct fs_aux_info { + struct ext4_sblock *sb; + uint8_t *bg_desc_blk; + struct xattr_list_element *xattrs; + uint32_t first_data_block; + uint64_t len_blocks; + uint32_t inode_table_blocks; + uint32_t groups; + uint32_t bg_desc_blocks; + uint32_t default_i_flags; + uint32_t blocks_per_ind; + uint32_t blocks_per_dind; + uint32_t blocks_per_tind; +}; + +static inline int log_2(int j) +{ + int i; + + for (i = 0; j > 0; i++) + j >>= 1; + + return i - 1; +} + +static int sb2info(struct ext4_sblock *sb, struct ext4_mkfs_info *info) +{ + if (to_le16(sb->magic) != EXT4_SUPERBLOCK_MAGIC) + return EINVAL; + + info->block_size = 1024 << to_le32(sb->log_block_size); + info->blocks_per_group = to_le32(sb->blocks_per_group); + info->inodes_per_group = to_le32(sb->inodes_per_group); + info->inode_size = to_le16(sb->inode_size); + info->inodes = to_le32(sb->inodes_count); + info->feat_ro_compat = to_le32(sb->features_read_only); + info->feat_compat = to_le32(sb->features_compatible); + info->feat_incompat = to_le32(sb->features_incompatible); + info->bg_desc_reserve_blocks = to_le16(sb->s_reserved_gdt_blocks); + info->label = sb->volume_name; + info->len = (uint64_t)info->block_size * ext4_sb_get_blocks_cnt(sb); + info->dsc_size = to_le16(sb->desc_size); + memcpy(info->uuid, sb->uuid, UUID_SIZE); + + return EOK; +} + +static uint32_t compute_blocks_per_group(struct ext4_mkfs_info *info) +{ + return info->block_size * 8; +} + +static uint32_t compute_inodes(struct ext4_mkfs_info *info) +{ + return (uint32_t)EXT4_DIV_ROUND_UP(info->len, info->block_size) / 4; +} + +static uint32_t compute_inodes_per_group(struct ext4_mkfs_info *info) +{ + uint32_t blocks = (uint32_t)EXT4_DIV_ROUND_UP(info->len, info->block_size); + uint32_t block_groups = EXT4_DIV_ROUND_UP(blocks, info->blocks_per_group); + uint32_t inodes = EXT4_DIV_ROUND_UP(info->inodes, block_groups); + inodes = EXT4_ALIGN(inodes, (info->block_size / info->inode_size)); + + /* After properly rounding up the number of inodes/group, + * make sure to update the total inodes field in the info struct. + */ + info->inodes = inodes * block_groups; + + return inodes; +} + + +static uint32_t compute_journal_blocks(struct ext4_mkfs_info *info) +{ + uint32_t journal_blocks = (uint32_t)EXT4_DIV_ROUND_UP(info->len, + info->block_size) / 64; + if (journal_blocks < 1024) + journal_blocks = 1024; + if (journal_blocks > 32768) + journal_blocks = 32768; + return journal_blocks; +} + +static bool has_superblock(struct ext4_mkfs_info *info, uint32_t bgid) +{ + if (!(info->feat_ro_compat & EXT4_FRO_COM_SPARSE_SUPER)) + return true; + + return ext4_sb_sparse(bgid); +} + +static int create_fs_aux_info(struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + aux_info->first_data_block = (info->block_size > 1024) ? 0 : 1; + aux_info->len_blocks = info->len / info->block_size; + aux_info->inode_table_blocks = EXT4_DIV_ROUND_UP(info->inodes_per_group * + info->inode_size, info->block_size); + aux_info->groups = (uint32_t)EXT4_DIV_ROUND_UP(aux_info->len_blocks - + aux_info->first_data_block, info->blocks_per_group); + aux_info->blocks_per_ind = info->block_size / sizeof(uint32_t); + aux_info->blocks_per_dind = + aux_info->blocks_per_ind * aux_info->blocks_per_ind; + aux_info->blocks_per_tind = + aux_info->blocks_per_dind * aux_info->blocks_per_dind; + + aux_info->bg_desc_blocks = + EXT4_DIV_ROUND_UP(aux_info->groups * info->dsc_size, + info->block_size); + + aux_info->default_i_flags = EXT4_INODE_FLAG_NOATIME; + + uint32_t last_group_size = aux_info->len_blocks % info->blocks_per_group; + uint32_t last_header_size = 2 + aux_info->inode_table_blocks; + if (has_superblock(info, aux_info->groups - 1)) + last_header_size += 1 + aux_info->bg_desc_blocks + + info->bg_desc_reserve_blocks; + + if (last_group_size > 0 && last_group_size < last_header_size) { + aux_info->groups--; + aux_info->len_blocks -= last_group_size; + } + + aux_info->sb = ext4_calloc(1, EXT4_SUPERBLOCK_SIZE); + if (!aux_info->sb) + return ENOMEM; + + aux_info->bg_desc_blk = ext4_calloc(1, info->block_size); + if (!aux_info->bg_desc_blk) + return ENOMEM; + + aux_info->xattrs = NULL; + + + ext4_dbg(DEBUG_MKFS, DBG_INFO "create_fs_aux_info\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "first_data_block: %"PRIu32"\n", + aux_info->first_data_block); + ext4_dbg(DEBUG_MKFS, DBG_NONE "len_blocks: %"PRIu64"\n", + aux_info->len_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "inode_table_blocks: %"PRIu32"\n", + aux_info->inode_table_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "groups: %"PRIu32"\n", + aux_info->groups); + ext4_dbg(DEBUG_MKFS, DBG_NONE "bg_desc_blocks: %"PRIu32"\n", + aux_info->bg_desc_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "default_i_flags: %"PRIu32"\n", + aux_info->default_i_flags); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_ind: %"PRIu32"\n", + aux_info->blocks_per_ind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_dind: %"PRIu32"\n", + aux_info->blocks_per_dind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_tind: %"PRIu32"\n", + aux_info->blocks_per_tind); + + return EOK; +} + +static void release_fs_aux_info(struct fs_aux_info *aux_info) +{ + if (aux_info->sb) + ext4_free(aux_info->sb); + if (aux_info->bg_desc_blk) + ext4_free(aux_info->bg_desc_blk); +} + + +/* Fill in the superblock memory buffer based on the filesystem parameters */ +static void fill_sb(struct fs_aux_info *aux_info, struct ext4_mkfs_info *info) +{ + struct ext4_sblock *sb = aux_info->sb; + + sb->inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + ext4_sb_set_blocks_cnt(sb, aux_info->len_blocks); + ext4_sb_set_free_blocks_cnt(sb, aux_info->len_blocks); + sb->free_inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + sb->reserved_blocks_count_lo = to_le32(0); + sb->first_data_block = to_le32(aux_info->first_data_block); + sb->log_block_size = to_le32(log_2(info->block_size / 1024)); + sb->log_cluster_size = to_le32(log_2(info->block_size / 1024)); + sb->blocks_per_group = to_le32(info->blocks_per_group); + sb->frags_per_group = to_le32(info->blocks_per_group); + sb->inodes_per_group = to_le32(info->inodes_per_group); + sb->mount_time = to_le32(0); + sb->write_time = to_le32(0); + sb->mount_count = to_le16(0); + sb->max_mount_count = to_le16(0xFFFF); + sb->magic = to_le16(EXT4_SUPERBLOCK_MAGIC); + sb->state = to_le16(EXT4_SUPERBLOCK_STATE_VALID_FS); + sb->errors = to_le16(EXT4_SUPERBLOCK_ERRORS_RO); + sb->minor_rev_level = to_le16(0); + sb->last_check_time = to_le32(0); + sb->check_interval = to_le32(0); + sb->creator_os = to_le32(EXT4_SUPERBLOCK_OS_LINUX); + sb->rev_level = to_le32(1); + sb->def_resuid = to_le16(0); + sb->def_resgid = to_le16(0); + + sb->first_inode = to_le32(EXT4_GOOD_OLD_FIRST_INO); + sb->inode_size = to_le16(info->inode_size); + sb->block_group_index = to_le16(0); + + sb->features_compatible = to_le32(info->feat_compat); + sb->features_incompatible = to_le32(info->feat_incompat); + sb->features_read_only = to_le32(info->feat_ro_compat); + + memcpy(sb->uuid, info->uuid, UUID_SIZE); + + memset(sb->volume_name, 0, sizeof(sb->volume_name)); + strncpy(sb->volume_name, info->label, sizeof(sb->volume_name)); + memset(sb->last_mounted, 0, sizeof(sb->last_mounted)); + + sb->algorithm_usage_bitmap = to_le32(0); + sb->s_prealloc_blocks = 0; + sb->s_prealloc_dir_blocks = 0; + sb->s_reserved_gdt_blocks = to_le16(info->bg_desc_reserve_blocks); + + if (info->feat_compat & EXT4_FCOM_HAS_JOURNAL) + sb->journal_inode_number = to_le32(EXT4_JOURNAL_INO); + + sb->journal_backup_type = 1; + sb->journal_dev = to_le32(0); + sb->last_orphan = to_le32(0); + sb->hash_seed[0] = to_le32(0x11111111); + sb->hash_seed[1] = to_le32(0x22222222); + sb->hash_seed[2] = to_le32(0x33333333); + sb->hash_seed[3] = to_le32(0x44444444); + sb->default_hash_version = EXT2_HTREE_HALF_MD4; + sb->checksum_type = 1; + sb->desc_size = to_le16(info->dsc_size); + sb->default_mount_opts = to_le32(0); + sb->first_meta_bg = to_le32(0); + sb->mkfs_time = to_le32(0); + + sb->reserved_blocks_count_hi = to_le32(0); + sb->min_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->want_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->flags = to_le32(EXT4_SUPERBLOCK_FLAGS_SIGNED_HASH); +} + + +static int write_bgroup_block(struct ext4_blockdev *bd, + struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info, + uint32_t blk) +{ + int r = EOK; + uint32_t j; + struct ext4_block b; + + uint32_t block_size = ext4_sb_get_block_size(aux_info->sb); + + for (j = 0; j < aux_info->groups; j++) { + uint64_t bg_start_block = aux_info->first_data_block + + j * info->blocks_per_group; + uint32_t blk_off = 0; + + blk_off += aux_info->bg_desc_blocks; + if (has_superblock(info, j)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + } + + uint64_t dsc_blk = bg_start_block + blk; + + r = ext4_block_get_noread(bd, &b, dsc_blk); + if (r != EOK) + return r; + + memcpy(b.data, aux_info->bg_desc_blk, block_size); + + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + } + + return r; +} + +static int write_bgroups(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + int r = EOK; + + struct ext4_block b; + struct ext4_bgroup *bg_desc; + + uint32_t i; + uint32_t bg_free_blk = 0; + uint64_t sb_free_blk = 0; + uint32_t block_size = ext4_sb_get_block_size(aux_info->sb); + uint32_t dsc_size = ext4_sb_get_desc_size(aux_info->sb); + uint32_t dsc_per_block = block_size / dsc_size; + uint32_t k = 0; + + for (i = 0; i < aux_info->groups; i++) { + uint64_t bg_start_block = aux_info->first_data_block + + aux_info->first_data_block + i * info->blocks_per_group; + uint32_t blk_off = 0; + + bg_desc = (void *)(aux_info->bg_desc_blk + k * dsc_size); + bg_free_blk = info->blocks_per_group - + aux_info->inode_table_blocks; + + bg_free_blk -= 2; + blk_off += aux_info->bg_desc_blocks; + + if (i == (aux_info->groups - 1)) + bg_free_blk -= aux_info->first_data_block; + + if (has_superblock(info, i)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + bg_free_blk -= info->bg_desc_reserve_blocks + 1; + bg_free_blk -= aux_info->bg_desc_blocks; + } + + ext4_bg_set_block_bitmap(bg_desc, aux_info->sb, + bg_start_block + blk_off + 1); + + ext4_bg_set_inode_bitmap(bg_desc, aux_info->sb, + bg_start_block + blk_off + 2); + + ext4_bg_set_inode_table_first_block(bg_desc, + aux_info->sb, + bg_start_block + blk_off + 3); + + ext4_bg_set_free_blocks_count(bg_desc, aux_info->sb, + bg_free_blk); + + ext4_bg_set_free_inodes_count(bg_desc, + aux_info->sb, to_le32(aux_info->sb->inodes_per_group)); + + ext4_bg_set_used_dirs_count(bg_desc, aux_info->sb, 0); + + ext4_bg_set_flag(bg_desc, + EXT4_BLOCK_GROUP_BLOCK_UNINIT | + EXT4_BLOCK_GROUP_INODE_UNINIT); + + sb_free_blk += bg_free_blk; + + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 1); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 2); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + + if (++k != dsc_per_block) + continue; + + k = 0; + r = write_bgroup_block(bd, aux_info, info, i / dsc_per_block); + if (r != EOK) + return r; + + } + + r = write_bgroup_block(bd, aux_info, info, i / dsc_per_block); + if (r != EOK) + return r; + + ext4_sb_set_free_blocks_cnt(aux_info->sb, sb_free_blk); + return r; +} + +static int write_sblocks(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + uint64_t offset; + uint32_t i; + int r; + + /* write out the backup superblocks */ + for (i = 1; i < aux_info->groups; i++) { + if (has_superblock(info, i)) { + offset = info->block_size * (aux_info->first_data_block + + i * info->blocks_per_group); + + aux_info->sb->block_group_index = to_le16(i); + r = ext4_block_writebytes(bd, offset, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); + if (r != EOK) + return r; + } + } + + /* write out the primary superblock */ + aux_info->sb->block_group_index = to_le16(0); + return ext4_block_writebytes(bd, 1024, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); +} + + +int ext4_mkfs_read_info(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct ext4_sblock *sb = NULL; + r = ext4_block_init(bd); + if (r != EOK) + return r; + + sb = ext4_malloc(EXT4_SUPERBLOCK_SIZE); + if (!sb) + goto Finish; + + + r = ext4_sb_read(bd, sb); + if (r != EOK) + goto Finish; + + r = sb2info(sb, info); + +Finish: + if (sb) + ext4_free(sb); + ext4_block_fini(bd); + return r; +} + +static int mkfs_init(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct fs_aux_info aux_info; + memset(&aux_info, 0, sizeof(struct fs_aux_info)); + + r = create_fs_aux_info(&aux_info, info); + if (r != EOK) + goto Finish; + + fill_sb(&aux_info, info); + + r = write_bgroups(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + r = write_sblocks(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + Finish: + release_fs_aux_info(&aux_info); + return r; +} + +static int init_bgs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_block_group_ref ref; + uint32_t i; + uint32_t bg_count = ext4_block_group_cnt(&fs->sb); + for (i = 0; i < bg_count; ++i) { + r = ext4_fs_get_block_group_ref(fs, i, &ref); + if (r != EOK) + break; + + r = ext4_fs_put_block_group_ref(&ref); + if (r != EOK) + break; + } + return r; +} + +static int alloc_inodes(struct ext4_fs *fs) +{ + int r = EOK; + int i; + struct ext4_inode_ref inode_ref; + for (i = 1; i < 12; ++i) { + int filetype = EXT4_DE_REG_FILE; + + switch (i) { + case EXT4_ROOT_INO: + case EXT4_GOOD_OLD_FIRST_INO: + filetype = EXT4_DE_DIR; + break; + default: + break; + } + + r = ext4_fs_alloc_inode(fs, &inode_ref, filetype); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, inode_ref.inode, 0); + + switch (i) { + case EXT4_ROOT_INO: + case EXT4_JOURNAL_INO: + ext4_fs_inode_blocks_init(fs, &inode_ref); + break; + } + + ext4_fs_put_inode_ref(&inode_ref); + } + + return r; +} + +static int create_dirs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_inode_ref root; + struct ext4_inode_ref child; + + r = ext4_fs_get_inode_ref(fs, EXT4_ROOT_INO, &root); + if (r != EOK) + return r; + + r = ext4_fs_get_inode_ref(fs, EXT4_GOOD_OLD_FIRST_INO, &child); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, child.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + + ext4_inode_set_mode(&fs->sb, root.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&fs->sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(&root, &root); + if (r != EOK) + return r; + + r = ext4_dir_dx_init(&child, &root); + if (r != EOK) + return r; + + ext4_inode_set_flag(root.inode, EXT4_INODE_FLAG_INDEX); + ext4_inode_set_flag(child.inode, EXT4_INODE_FLAG_INDEX); + } else +#endif + { + r = ext4_dir_add_entry(&root, ".", strlen("."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&root, "..", strlen(".."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, ".", strlen("."), &child); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, "..", strlen(".."), &root); + if (r != EOK) + return r; + } + + r = ext4_dir_add_entry(&root, "lost+found", strlen("lost+found"), &child); + if (r != EOK) + return r; + + ext4_inode_set_links_cnt(root.inode, 3); + ext4_inode_set_links_cnt(child.inode, 2); + + child.dirty = true; + root.dirty = true; + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&root); + return r; +} + +static int create_journal_inode(struct ext4_fs *fs, + struct ext4_mkfs_info *info) +{ + int ret; + struct ext4_inode_ref inode_ref; + uint64_t blocks_count; + + if (!info->journal) + return EOK; + + ret = ext4_fs_get_inode_ref(fs, EXT4_JOURNAL_INO, &inode_ref); + if (ret != EOK) + return ret; + + struct ext4_inode *inode = inode_ref.inode; + + ext4_inode_set_mode(&fs->sb, inode, EXT4_INODE_MODE_FILE | 0600); + ext4_inode_set_links_cnt(inode, 1); + + blocks_count = ext4_inode_get_blocks_count(&fs->sb, inode); + + while (blocks_count++ < info->journal_blocks) + { + ext4_fsblk_t fblock; + ext4_lblk_t iblock; + struct ext4_block blk; + + ret = ext4_fs_append_inode_dblk(&inode_ref, &fblock, &iblock); + if (ret != EOK) + goto Finish; + + if (iblock != 0) + continue; + + ret = ext4_block_get(fs->bdev, &blk, fblock); + if (ret != EOK) + goto Finish; + + + struct jbd_sb * jbd_sb = (struct jbd_sb * )blk.data; + memset(jbd_sb, 0, sizeof(struct jbd_sb)); + + jbd_sb->header.magic = to_be32(JBD_MAGIC_NUMBER); + jbd_sb->header.blocktype = to_be32(JBD_SUPERBLOCK_V2); + jbd_sb->blocksize = to_be32(info->block_size); + jbd_sb->maxlen = to_be32(info->journal_blocks); + jbd_sb->nr_users = to_be32(1); + jbd_sb->first = to_be32(1); + jbd_sb->sequence = to_be32(1); + + ext4_bcache_set_dirty(blk.buf); + ret = ext4_block_set(fs->bdev, &blk); + if (ret != EOK) + goto Finish; + } + + memcpy(fs->sb.journal_blocks, inode->blocks, sizeof(inode->blocks)); + + Finish: + ext4_fs_put_inode_ref(&inode_ref); + + return ret; +} + +int ext4_mkfs(struct ext4_fs *fs, struct ext4_blockdev *bd, + struct ext4_mkfs_info *info, int fs_type) +{ + int r; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + bd->fs = fs; + + if (info->len == 0) + info->len = bd->part_size; + + if (info->block_size == 0) + info->block_size = 4096; /*Set block size to default value*/ + + /* Round down the filesystem length to be a multiple of the block size */ + info->len &= ~((uint64_t)info->block_size - 1); + + if (info->journal_blocks == 0) + info->journal_blocks = compute_journal_blocks(info); + + if (info->blocks_per_group == 0) + info->blocks_per_group = compute_blocks_per_group(info); + + if (info->inodes == 0) + info->inodes = compute_inodes(info); + + if (info->inode_size == 0) + info->inode_size = 256; + + if (info->label == NULL) + info->label = ""; + + info->inodes_per_group = compute_inodes_per_group(info); + + switch (fs_type) { + case F_SET_EXT2: + info->feat_compat = EXT2_SUPPORTED_FCOM; + info->feat_ro_compat = EXT2_SUPPORTED_FRO_COM; + info->feat_incompat = EXT2_SUPPORTED_FINCOM; + break; + case F_SET_EXT3: + info->feat_compat = EXT3_SUPPORTED_FCOM; + info->feat_ro_compat = EXT3_SUPPORTED_FRO_COM; + info->feat_incompat = EXT3_SUPPORTED_FINCOM; + break; + case F_SET_EXT4: + info->feat_compat = EXT4_SUPPORTED_FCOM; + info->feat_ro_compat = EXT4_SUPPORTED_FRO_COM; + info->feat_incompat = EXT4_SUPPORTED_FINCOM; + break; + } + + /*TODO: handle this features some day...*/ + info->feat_incompat &= ~EXT4_FINCOM_META_BG; + info->feat_incompat &= ~EXT4_FINCOM_FLEX_BG; + info->feat_incompat &= ~EXT4_FINCOM_64BIT; + + info->feat_ro_compat &= ~EXT4_FRO_COM_METADATA_CSUM; + info->feat_ro_compat &= ~EXT4_FRO_COM_GDT_CSUM; + info->feat_ro_compat &= ~EXT4_FRO_COM_DIR_NLINK; + info->feat_ro_compat &= ~EXT4_FRO_COM_EXTRA_ISIZE; + info->feat_ro_compat &= ~EXT4_FRO_COM_HUGE_FILE; + + if (info->journal) + info->feat_compat |= EXT4_FCOM_HAS_JOURNAL; + + if (info->dsc_size == 0) { + + if (info->feat_incompat & EXT4_FINCOM_64BIT) + info->dsc_size = EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE; + else + info->dsc_size = EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE; + } + + info->bg_desc_reserve_blocks = 0; + + ext4_dbg(DEBUG_MKFS, DBG_INFO "Creating filesystem with parameters:\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Size: %"PRIu64"\n", info->len); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Block size: %"PRIu32"\n", + info->block_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Blocks per group: %"PRIu32"\n", + info->blocks_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes per group: %"PRIu32"\n", + info->inodes_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inode size: %"PRIu32"\n", + info->inode_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes: %"PRIu32"\n", info->inodes); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Journal blocks: %"PRIu32"\n", + info->journal_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features ro_compat: 0x%x\n", + info->feat_ro_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features compat: 0x%x\n", + info->feat_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features incompat: 0x%x\n", + info->feat_incompat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "BG desc reserve: %"PRIu32"\n", + info->bg_desc_reserve_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Descriptor size: %"PRIu16"\n", + info->dsc_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "journal: %s\n", + info->journal ? "yes" : "no"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Label: %s\n", info->label); + + struct ext4_bcache bc; + + memset(&bc, 0, sizeof(struct ext4_bcache)); + ext4_block_set_lb_size(bd, info->block_size); + + r = ext4_bcache_init_dynamic(&bc, CONFIG_BLOCK_DEV_CACHE_SIZE, + info->block_size); + if (r != EOK) + goto block_fini; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, &bc); + if (r != EOK) + goto cache_fini; + + r = ext4_block_cache_write_back(bd, 1); + if (r != EOK) + goto cache_fini; + + r = mkfs_init(bd, info); + if (r != EOK) + goto cache_fini; + + r = ext4_fs_init(fs, bd, false); + if (r != EOK) + goto cache_fini; + + r = init_bgs(fs); + if (r != EOK) + goto fs_fini; + + r = alloc_inodes(fs); + if (r != EOK) + goto fs_fini; + + r = create_dirs(fs); + if (r != EOK) + goto fs_fini; + + r = create_journal_inode(fs, info); + if (r != EOK) + goto fs_fini; + + fs_fini: + ext4_fs_fini(fs); + + cache_fini: + ext4_block_cache_write_back(bd, 0); + ext4_bcache_fini_dynamic(&bc); + + block_fini: + ext4_block_fini(bd); + + return r; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_super.c b/clib/lib/lwext4/src/ext4_super.c new file mode 100644 index 0000000..092c38b --- /dev/null +++ b/clib/lib/lwext4/src/ext4_super.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_super.h + * @brief Superblock operations. + */ + +#include +#include +#include +#include +#include + +#include +#include + +uint32_t ext4_block_group_cnt(struct ext4_sblock *s) +{ + uint64_t blocks_count = ext4_sb_get_blocks_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + + uint32_t block_groups_count = (uint32_t)(blocks_count / blocks_per_group); + + if (blocks_count % blocks_per_group) + block_groups_count++; + + return block_groups_count; +} + +uint32_t ext4_blocks_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + uint64_t total_blocks = ext4_sb_get_blocks_cnt(s); + + if (bgid < block_group_count - 1) + return blocks_per_group; + + return (uint32_t)(total_blocks - ((block_group_count - 1) * blocks_per_group)); +} + +uint32_t ext4_inodes_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t inodes_per_group = ext4_get32(s, inodes_per_group); + uint32_t total_inodes = ext4_get32(s, inodes_count); + + if (bgid < block_group_count - 1) + return inodes_per_group; + + return (total_inodes - ((block_group_count - 1) * inodes_per_group)); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_sb_csum(struct ext4_sblock *s) +{ + + return ext4_crc32c(EXT4_CRC32_INIT, s, + offsetof(struct ext4_sblock, checksum)); +} +#else +#define ext4_sb_csum(...) 0 +#endif + +static bool ext4_sb_verify_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (s->checksum_type != to_le32(EXT4_CHECKSUM_CRC32C)) + return false; + + return s->checksum == to_le32(ext4_sb_csum(s)); +} + +static void ext4_sb_set_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return; + + s->checksum = to_le32(ext4_sb_csum(s)); +} + +int ext4_sb_write(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + ext4_sb_set_csum(s); + return ext4_block_writebytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +int ext4_sb_read(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + return ext4_block_readbytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +bool ext4_sb_check(struct ext4_sblock *s) +{ + if (ext4_get16(s, magic) != EXT4_SUPERBLOCK_MAGIC) + return false; + + if (ext4_get32(s, inodes_count) == 0) + return false; + + if (ext4_sb_get_blocks_cnt(s) == 0) + return false; + + if (ext4_get32(s, blocks_per_group) == 0) + return false; + + if (ext4_get32(s, inodes_per_group) == 0) + return false; + + if (ext4_get16(s, inode_size) < 128) + return false; + + if (ext4_get32(s, first_inode) < 11) + return false; + + if (ext4_sb_get_desc_size(s) < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (ext4_sb_get_desc_size(s) > EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (!ext4_sb_verify_csum(s)) + return false; + + return true; +} + +static inline int is_power_of(uint32_t a, uint32_t b) +{ + while (1) { + if (a < b) + return 0; + if (a == b) + return 1; + if ((a % b) != 0) + return 0; + a = a / b; + } +} + +bool ext4_sb_sparse(uint32_t group) +{ + if (group <= 1) + return 1; + + if (!(group & 1)) + return 0; + + return (is_power_of(group, 7) || is_power_of(group, 5) || + is_power_of(group, 3)); +} + +bool ext4_sb_is_super_in_bg(struct ext4_sblock *s, uint32_t group) +{ + if (ext4_sb_feature_ro_com(s, EXT4_FRO_COM_SPARSE_SUPER) && + !ext4_sb_sparse(group)) + return false; + return true; +} + +static uint32_t ext4_bg_num_gdb_meta(struct ext4_sblock *s, uint32_t group) +{ + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + uint32_t metagroup = group / dsc_per_block; + uint32_t first = metagroup * dsc_per_block; + uint32_t last = first + dsc_per_block - 1; + + if (group == first || group == first + 1 || group == last) + return 1; + return 0; +} + +static uint32_t ext4_bg_num_gdb_nometa(struct ext4_sblock *s, uint32_t group) +{ + if (!ext4_sb_is_super_in_bg(s, group)) + return 0; + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + uint32_t db_count = + (ext4_block_group_cnt(s) + dsc_per_block - 1) / dsc_per_block; + + if (ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG)) + return ext4_sb_first_meta_bg(s); + + return db_count; +} + +uint32_t ext4_bg_num_gdb(struct ext4_sblock *s, uint32_t group) +{ + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + uint32_t first_meta_bg = ext4_sb_first_meta_bg(s); + uint32_t metagroup = group / dsc_per_block; + + if (!ext4_sb_feature_incom(s,EXT4_FINCOM_META_BG) || + metagroup < first_meta_bg) + return ext4_bg_num_gdb_nometa(s, group); + + return ext4_bg_num_gdb_meta(s, group); +} + +uint32_t ext4_num_base_meta_clusters(struct ext4_sblock *s, + uint32_t block_group) +{ + uint32_t num; + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + num = ext4_sb_is_super_in_bg(s, block_group); + + if (!ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG) || + block_group < ext4_sb_first_meta_bg(s) * dsc_per_block) { + if (num) { + num += ext4_bg_num_gdb(s, block_group); + num += ext4_get16(s, s_reserved_gdt_blocks); + } + } else { + num += ext4_bg_num_gdb(s, block_group); + } + + uint32_t clustersize = 1024 << ext4_get32(s, log_cluster_size); + uint32_t cluster_ratio = clustersize / ext4_sb_get_block_size(s); + uint32_t v = + (num + cluster_ratio - 1) >> ext4_get32(s, log_cluster_size); + + return v; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_trans.c b/clib/lib/lwext4/src/ext4_trans.c new file mode 100644 index 0000000..f228751 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_trans.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_trans.c + * @brief Ext4 transaction buffer operations. + */ + +#include +#include +#include +#include +#include + +#include +#include + +int ext4_trans_set_block_dirty(struct ext4_buf *buf) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = buf->bc->bdev->fs; + struct ext4_block block = { + .lb_id = buf->lba, + .data = buf->data, + .buf = buf + }; + + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + return jbd_trans_set_block_dirty(trans, &block); + } +#endif + ext4_bcache_set_dirty(buf); + return r; +} + +int ext4_trans_block_get_noread(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + return r; +} + +int ext4_trans_block_get(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get(bdev, b, lba); + if (r != EOK) + return r; + + return r; +} + +int ext4_trans_try_revoke_block(struct ext4_blockdev *bdev __unused, + uint64_t lba __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = bdev->fs; + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + r = jbd_trans_try_revoke_block(trans, lba); + } else if (fs->jbd_journal) { + r = ext4_block_flush_lba(fs->bdev, lba); + } +#endif + return r; +} + +/** + * @} + */ diff --git a/clib/lib/lwext4/src/ext4_xattr.c b/clib/lib/lwext4/src/ext4_xattr.c new file mode 100644 index 0000000..f8a5778 --- /dev/null +++ b/clib/lib/lwext4/src/ext4_xattr.c @@ -0,0 +1,1564 @@ +/* + * Copyright (c) 2017 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2017 Kaho Ng (ngkaho1234@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.c + * @brief Extended Attribute manipulation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if CONFIG_XATTR_ENABLE + +/** + * @file ext4_xattr.c + * @brief Extended Attribute Manipulation + */ + +/* Extended Attribute(EA) */ + +/* Magic value in attribute blocks */ +#define EXT4_XATTR_MAGIC 0xEA020000 + +/* Maximum number of references to one attribute block */ +#define EXT4_XATTR_REFCOUNT_MAX 1024 + +/* Name indexes */ +#define EXT4_XATTR_INDEX_USER 1 +#define EXT4_XATTR_INDEX_POSIX_ACL_ACCESS 2 +#define EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT 3 +#define EXT4_XATTR_INDEX_TRUSTED 4 +#define EXT4_XATTR_INDEX_LUSTRE 5 +#define EXT4_XATTR_INDEX_SECURITY 6 +#define EXT4_XATTR_INDEX_SYSTEM 7 +#define EXT4_XATTR_INDEX_RICHACL 8 +#define EXT4_XATTR_INDEX_ENCRYPTION 9 + +#define EXT4_XATTR_PAD_BITS 2 +#define EXT4_XATTR_PAD (1 << EXT4_XATTR_PAD_BITS) +#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD - 1) +#define EXT4_XATTR_LEN(name_len) \ + (((name_len) + EXT4_XATTR_ROUND + sizeof(struct ext4_xattr_entry)) & \ + ~EXT4_XATTR_ROUND) +#define EXT4_XATTR_NEXT(entry) \ + ((struct ext4_xattr_entry *)((char *)(entry) + \ + EXT4_XATTR_LEN((entry)->e_name_len))) +#define EXT4_XATTR_SIZE(size) (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND) +#define EXT4_XATTR_NAME(entry) ((char *)((entry) + 1)) + +#define EXT4_XATTR_IHDR(sb, raw_inode) \ + ((struct ext4_xattr_ibody_header *)((char *)raw_inode + \ + EXT4_GOOD_OLD_INODE_SIZE + \ + ext4_inode_get_extra_isize( \ + sb, raw_inode))) +#define EXT4_XATTR_IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr) + 1)) + +#define EXT4_XATTR_BHDR(block) ((struct ext4_xattr_header *)((block)->data)) +#define EXT4_XATTR_ENTRY(ptr) ((struct ext4_xattr_entry *)(ptr)) +#define EXT4_XATTR_BFIRST(block) EXT4_XATTR_ENTRY(EXT4_XATTR_BHDR(block) + 1) +#define EXT4_XATTR_IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0) + +#define EXT4_ZERO_XATTR_VALUE ((void *)-1) + +#pragma pack(push, 1) + +struct ext4_xattr_header { + uint32_t h_magic; /* magic number for identification */ + uint32_t h_refcount; /* reference count */ + uint32_t h_blocks; /* number of disk blocks used */ + uint32_t h_hash; /* hash value of all attributes */ + uint32_t h_checksum; /* crc32c(uuid+id+xattrblock) */ + /* id = inum if refcount=1, blknum otherwise */ + uint32_t h_reserved[3]; /* zero right now */ +}; + +struct ext4_xattr_ibody_header { + uint32_t h_magic; /* magic number for identification */ +}; + +struct ext4_xattr_entry { + uint8_t e_name_len; /* length of name */ + uint8_t e_name_index; /* attribute name index */ + uint16_t e_value_offs; /* offset in disk block of value */ + uint32_t e_value_block; /* disk block attribute is stored on (n/i) */ + uint32_t e_value_size; /* size of attribute value */ + uint32_t e_hash; /* hash value of name and value */ +}; + +#pragma pack(pop) + + +#define NAME_HASH_SHIFT 5 +#define VALUE_HASH_SHIFT 16 + +static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + uint32_t hash = 0; + char *name = EXT4_XATTR_NAME(entry); + int n; + + for (n = 0; n < entry->e_name_len; n++) { + hash = (hash << NAME_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - NAME_HASH_SHIFT)) ^ *name++; + } + + if (entry->e_value_block == 0 && entry->e_value_size != 0) { + uint32_t *value = + (uint32_t *)((char *)header + to_le16(entry->e_value_offs)); + for (n = (to_le32(entry->e_value_size) + EXT4_XATTR_ROUND) >> + EXT4_XATTR_PAD_BITS; + n; n--) { + hash = (hash << VALUE_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - VALUE_HASH_SHIFT)) ^ + to_le32(*value++); + } + } + entry->e_hash = to_le32(hash); +} + +#define BLOCK_HASH_SHIFT 16 + +/* + * ext4_xattr_rehash() + * + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext4_xattr_rehash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + struct ext4_xattr_entry *here; + uint32_t hash = 0; + + ext4_xattr_compute_hash(header, entry); + here = EXT4_XATTR_ENTRY(header + 1); + while (!EXT4_XATTR_IS_LAST_ENTRY(here)) { + if (!here->e_hash) { + /* Block is not shared if an entry's hash value == 0 */ + hash = 0; + break; + } + hash = (hash << BLOCK_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - BLOCK_HASH_SHIFT)) ^ + to_le32(here->e_hash); + here = EXT4_XATTR_NEXT(here); + } + header->h_hash = to_le32(hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr, + struct ext4_xattr_header *header) +{ + uint32_t checksum = 0; + uint64_t le64_blocknr = blocknr; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = header->h_checksum; + header->h_checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + checksum = + ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum block number */ + checksum = + ext4_crc32c(checksum, &le64_blocknr, sizeof(le64_blocknr)); + /* Finally calculate crc32 checksum against + * the entire xattr block */ + checksum = + ext4_crc32c(checksum, header, ext4_sb_get_block_size(sb)); + header->h_checksum = orig_checksum; + } + return checksum; +} +#else +#define ext4_xattr_block_checksum(...) 0 +#endif + +static void ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr __unused, + struct ext4_xattr_header *header) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + header->h_checksum = + ext4_xattr_block_checksum(inode_ref, blocknr, header); +} + +struct xattr_prefix { + const char *prefix; + uint8_t name_index; +}; + +static const struct xattr_prefix prefix_tbl[] = { + {"user.", EXT4_XATTR_INDEX_USER}, + {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS}, + {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT}, + {"trusted.", EXT4_XATTR_INDEX_TRUSTED}, + {"security.", EXT4_XATTR_INDEX_SECURITY}, + {"system.", EXT4_XATTR_INDEX_SYSTEM}, + {"system.richacl", EXT4_XATTR_INDEX_RICHACL}, + {NULL, 0}, +}; + +const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len, + bool *found) +{ + int i; + ext4_assert(name_index); + ext4_assert(found); + + *found = false; + + if (!full_name_len) { + if (name_len) + *name_len = 0; + + return NULL; + } + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (full_name_len >= prefix_len && + !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) { + bool require_name = + prefix_tbl[i].prefix[prefix_len - 1] == '.'; + *name_index = prefix_tbl[i].name_index; + if (name_len) + *name_len = full_name_len - prefix_len; + + if (!(full_name_len - prefix_len) && require_name) + return NULL; + + *found = true; + if (require_name) + return full_name + prefix_len; + + return NULL; + } + } + if (name_len) + *name_len = 0; + + return NULL; +} + +const char *ext4_get_xattr_name_prefix(uint8_t name_index, + size_t *ret_prefix_len) +{ + int i; + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (prefix_tbl[i].name_index == name_index) { + if (ret_prefix_len) + *ret_prefix_len = prefix_len; + + return prefix_tbl[i].prefix; + } + } + if (ret_prefix_len) + *ret_prefix_len = 0; + + return NULL; +} + +static const char ext4_xattr_empty_value; + +/** + * @brief Insert/Remove/Modify the given entry + * + * @param i The information of the given EA entry + * @param s Search context block + * @param dry_run Do not modify the content of the buffer + * + * @return Return EOK when finished, ENOSPC when there is no enough space + */ +static int ext4_xattr_set_entry(struct ext4_xattr_info *i, + struct ext4_xattr_search *s, bool dry_run) +{ + struct ext4_xattr_entry *last; + size_t free, min_offs = (char *)s->end - (char *)s->base, + name_len = i->name_len; + + /* + * If the entry is going to be removed but not found, return 0 to + * indicate success. + */ + if (!i->value && s->not_found) + return EOK; + + /* Compute min_offs and last. */ + last = s->first; + for (; !EXT4_XATTR_IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) { + if (last->e_value_size) { + size_t offs = to_le16(last->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + + /* Calculate free space in the block. */ + free = min_offs - ((char *)last - (char *)s->base) - sizeof(uint32_t); + if (!s->not_found) + free += EXT4_XATTR_SIZE(s->here->e_value_size) + + EXT4_XATTR_LEN(s->here->e_name_len); + + if (i->value) { + /* See whether there is enough space to hold new entry */ + if (free < + EXT4_XATTR_SIZE(i->value_len) + EXT4_XATTR_LEN(name_len)) + return ENOSPC; + } + + /* Return EOK now if we do not intend to modify the content. */ + if (dry_run) + return EOK; + + /* First remove the old entry's data part */ + if (!s->not_found) { + size_t value_offs = to_le16(s->here->e_value_offs); + void *value = (char *)s->base + value_offs; + void *first_value = (char *)s->base + min_offs; + size_t value_size = + EXT4_XATTR_SIZE(to_le32(s->here->e_value_size)); + + if (value_offs) { + /* Remove the data part. */ + memmove((char *)first_value + value_size, first_value, + (char *)value - (char *)first_value); + + /* Zero the gap created */ + memset(first_value, 0, value_size); + + /* + * Calculate the new min_offs after removal of the old + * entry's data part + */ + min_offs += value_size; + } + + /* + * Adjust the value offset of entries which has value offset + * prior to the s->here. The offset of these entries won't be + * shifted if the size of the entry we removed is zero. + */ + for (last = s->first; !EXT4_XATTR_IS_LAST_ENTRY(last); + last = EXT4_XATTR_NEXT(last)) { + size_t offs = to_le16(last->e_value_offs); + + /* For zero-value-length entry, offs will be zero. */ + if (offs < value_offs) + last->e_value_offs = to_le16(offs + value_size); + } + } + + /* If caller wants us to insert... */ + if (i->value) { + size_t value_offs; + if (i->value_len) + value_offs = min_offs - EXT4_XATTR_SIZE(i->value_len); + else + value_offs = 0; + + if (!s->not_found) { + struct ext4_xattr_entry *here = s->here; + + /* Reuse the current entry we have got */ + here->e_value_offs = to_le16(value_offs); + here->e_value_size = to_le32(i->value_len); + } else { + /* Insert a new entry */ + last->e_name_len = (uint8_t)name_len; + last->e_name_index = i->name_index; + last->e_value_offs = to_le16(value_offs); + last->e_value_block = 0; + last->e_value_size = to_le32(i->value_len); + memcpy(EXT4_XATTR_NAME(last), i->name, name_len); + + /* Set valid last entry indicator */ + *(uint32_t *)EXT4_XATTR_NEXT(last) = 0; + + s->here = last; + } + + /* Insert the value's part */ + if (value_offs) { + memcpy((char *)s->base + value_offs, i->value, + i->value_len); + + /* Clear the padding bytes if there is */ + if (EXT4_XATTR_SIZE(i->value_len) != i->value_len) + memset((char *)s->base + value_offs + + i->value_len, + 0, EXT4_XATTR_SIZE(i->value_len) - + i->value_len); + } + } else { + size_t shift_offs; + + /* Remove the whole entry */ + shift_offs = (char *)EXT4_XATTR_NEXT(s->here) - (char *)s->here; + memmove(s->here, EXT4_XATTR_NEXT(s->here), + (char *)last + sizeof(uint32_t) - + (char *)EXT4_XATTR_NEXT(s->here)); + + /* Zero the gap created */ + memset((char *)last - shift_offs + sizeof(uint32_t), 0, + shift_offs); + + s->here = NULL; + } + + return EOK; +} + +static inline bool ext4_xattr_is_empty(struct ext4_xattr_search *s) +{ + if (!EXT4_XATTR_IS_LAST_ENTRY(s->first)) + return false; + + return true; +} + +/** + * @brief Find the entry according to given information + * + * @param i The information of the EA entry to be found, + * including name_index, name and the length of name + * @param s Search context block + */ +static void ext4_xattr_find_entry(struct ext4_xattr_info *i, + struct ext4_xattr_search *s) +{ + struct ext4_xattr_entry *entry = NULL; + + s->not_found = true; + s->here = NULL; + + /* + * Find the wanted EA entry by simply comparing the namespace, + * name and the length of name. + */ + for (entry = s->first; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + const char *name = EXT4_XATTR_NAME(entry); + if (name_len == i->name_len && + entry->e_name_index == i->name_index && + !memcmp(name, i->name, name_len)) { + s->here = entry; + s->not_found = false; + i->value_len = to_le32(entry->e_value_size); + if (i->value_len) + i->value = (char *)s->base + + to_le16(entry->e_value_offs); + else + i->value = NULL; + + return; + } + } +} + +/** + * @brief Check whether the xattr block's content is valid + * + * @param inode_ref Inode reference + * @param block The block buffer to be validated + * + * @return true if @block is valid, false otherwise. + */ +static bool ext4_xattr_is_block_valid(struct ext4_inode_ref *inode_ref, + struct ext4_block *block) +{ + + void *base = block->data, + *end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb); + size_t min_offs = (char *)end - (char *)base; + struct ext4_xattr_header *header = EXT4_XATTR_BHDR(block); + struct ext4_xattr_entry *entry = EXT4_XATTR_BFIRST(block); + + /* + * Check whether the magic number in the header is correct. + */ + if (header->h_magic != to_le32(EXT4_XATTR_MAGIC)) + return false; + + /* + * The in-kernel filesystem driver only supports 1 block currently. + */ + if (header->h_blocks != to_le32(1)) + return false; + + /* + * Check if those entries are maliciously corrupted to inflict harm + * upon us. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + if (!to_le32(entry->e_value_size) && + to_le16(entry->e_value_offs)) + return false; + + if ((char *)base + to_le16(entry->e_value_offs) + + to_le32(entry->e_value_size) > + (char *)end) + return false; + + /* + * The name length field should also be correct, + * also there should be an 4-byte zero entry at the + * end. + */ + if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > + (char *)end) + return false; + + if (to_le32(entry->e_value_size)) { + size_t offs = to_le16(entry->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + /* + * Entry field and data field do not override each other. + */ + if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) + return false; + + return true; +} + +/** + * @brief Check whether the inode buffer's content is valid + * + * @param inode_ref Inode reference + * + * @return true if the inode buffer is valid, false otherwise. + */ +static bool ext4_xattr_is_ibody_valid(struct ext4_inode_ref *inode_ref) +{ + size_t min_offs; + void *base, *end; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + struct ext4_xattr_entry *entry; + size_t inode_size = ext4_get16(&fs->sb, inode_size); + + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + entry = EXT4_XATTR_IFIRST(iheader); + base = iheader; + end = (char *)inode_ref->inode + inode_size; + min_offs = (char *)end - (char *)base; + + /* + * Check whether the magic number in the header is correct. + */ + if (iheader->h_magic != to_le32(EXT4_XATTR_MAGIC)) + return false; + + /* + * Check if those entries are maliciously corrupted to inflict harm + * upon us. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + if (!to_le32(entry->e_value_size) && + to_le16(entry->e_value_offs)) + return false; + + if ((char *)base + to_le16(entry->e_value_offs) + + to_le32(entry->e_value_size) > + (char *)end) + return false; + + /* + * The name length field should also be correct, + * also there should be an 4-byte zero entry at the + * end. + */ + if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > + (char *)end) + return false; + + if (to_le32(entry->e_value_size)) { + size_t offs = to_le16(entry->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + /* + * Entry field and data field do not override each other. + */ + if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) + return false; + + return true; +} + +/** + * @brief An EA entry finder for inode buffer + */ +struct ext4_xattr_finder { + /** + * @brief The information of the EA entry to be find + */ + struct ext4_xattr_info i; + + /** + * @brief Search context block of the current search + */ + struct ext4_xattr_search s; + + /** + * @brief Inode reference to the corresponding inode + */ + struct ext4_inode_ref *inode_ref; +}; + +static void ext4_xattr_ibody_initialize(struct ext4_inode_ref *inode_ref) +{ + struct ext4_xattr_ibody_header *header; + struct ext4_fs *fs = inode_ref->fs; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + size_t inode_size = ext4_get16(&fs->sb, inode_size); + if (!extra_isize) + return; + + header = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + memset(header, 0, inode_size - EXT4_GOOD_OLD_INODE_SIZE - extra_isize); + header->h_magic = to_le32(EXT4_XATTR_MAGIC); + inode_ref->dirty = true; +} + +/** + * @brief Initialize a given xattr block + * + * @param inode_ref Inode reference + * @param block xattr block buffer + */ +static void ext4_xattr_block_initialize(struct ext4_inode_ref *inode_ref, + struct ext4_block *block) +{ + struct ext4_xattr_header *header; + struct ext4_fs *fs = inode_ref->fs; + + memset(block->data, 0, ext4_sb_get_block_size(&fs->sb)); + + header = EXT4_XATTR_BHDR(block); + header->h_magic = to_le32(EXT4_XATTR_MAGIC); + header->h_refcount = to_le32(1); + header->h_blocks = to_le32(1); + + ext4_trans_set_block_dirty(block->buf); +} + +static void ext4_xattr_block_init_search(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_search *s, + struct ext4_block *block) +{ + s->base = block->data; + s->end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb); + s->first = EXT4_XATTR_BFIRST(block); + s->here = NULL; + s->not_found = true; +} + +/** + * @brief Find an EA entry inside a xattr block + * + * @param inode_ref Inode reference + * @param finder The caller-provided finder block with + * information filled + * @param block The block buffer to be looked into + * + * @return Return EOK no matter the entry is found or not. + * If the IO operation or the buffer validation failed, + * return other value. + */ +static int ext4_xattr_block_find_entry(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_finder *finder, + struct ext4_block *block) +{ + int ret = EOK; + + /* Initialize the caller-given finder */ + finder->inode_ref = inode_ref; + memset(&finder->s, 0, sizeof(finder->s)); + + if (ret != EOK) + return ret; + + /* Check the validity of the buffer */ + if (!ext4_xattr_is_block_valid(inode_ref, block)) + return EIO; + + ext4_xattr_block_init_search(inode_ref, &finder->s, block); + ext4_xattr_find_entry(&finder->i, &finder->s); + return EOK; +} + +/** + * @brief Find an EA entry inside an inode's extra space + * + * @param inode_ref Inode reference + * @param finder The caller-provided finder block with + * information filled + * + * @return Return EOK no matter the entry is found or not. + * If the IO operation or the buffer validation failed, + * return other value. + */ +static int ext4_xattr_ibody_find_entry(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_finder *finder) +{ + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + size_t inode_size = ext4_get16(&fs->sb, inode_size); + + /* Initialize the caller-given finder */ + finder->inode_ref = inode_ref; + memset(&finder->s, 0, sizeof(finder->s)); + + /* + * If there is no extra inode space + * set ext4_xattr_ibody_finder::s::not_found to true and return EOK + */ + if (!extra_isize) { + finder->s.not_found = true; + return EOK; + } + + /* Check the validity of the buffer */ + if (!ext4_xattr_is_ibody_valid(inode_ref)) + return EIO; + + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + finder->s.base = EXT4_XATTR_IFIRST(iheader); + finder->s.end = (char *)inode_ref->inode + inode_size; + finder->s.first = EXT4_XATTR_IFIRST(iheader); + ext4_xattr_find_entry(&finder->i, &finder->s); + return EOK; +} + +/** + * @brief Try to allocate a block holding EA entries. + * + * @param inode_ref Inode reference + * + * @return Error code + */ +static int ext4_xattr_try_alloc_block(struct ext4_inode_ref *inode_ref) +{ + int ret = EOK; + + ext4_fsblk_t xattr_block = 0; + xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb); + + /* + * Only allocate a xattr block when there is no xattr block + * used by the inode. + */ + if (!xattr_block) { + ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); + + ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block); + if (ret != EOK) + goto Finish; + + ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb, + xattr_block); + } + +Finish: + return ret; +} + +/** + * @brief Try to free a block holding EA entries. + * + * @param inode_ref Inode reference + * + * @return Error code + */ +static void ext4_xattr_try_free_block(struct ext4_inode_ref *inode_ref) +{ + ext4_fsblk_t xattr_block; + xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb); + /* + * Free the xattr block used by the inode when there is one. + */ + if (xattr_block) { + ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb, + 0); + ext4_balloc_free_block(inode_ref, xattr_block); + inode_ref->dirty = true; + } +} + +/** + * @brief Put a list of EA entries into a caller-provided buffer + * In order to make sure that @list buffer can fit in the data, + * the routine should be called twice. + * + * @param inode_ref Inode reference + * @param list A caller-provided buffer to hold a list of EA entries. + * If list == NULL, list_len will contain the size of + * the buffer required to hold these entries + * @param list_len The length of the data written to @list + * @return Error code + */ +int ext4_xattr_list(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_list_entry *list, size_t *list_len) +{ + int ret = EOK; + size_t buf_len = 0; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + struct ext4_block block; + bool block_loaded = false; + ext4_fsblk_t xattr_block = 0; + struct ext4_xattr_entry *entry; + struct ext4_xattr_list_entry *list_prev = NULL; + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + /* + * If there is extra inode space and the xattr buffer in the + * inode is valid. + */ + if (extra_isize && ext4_xattr_is_ibody_valid(inode_ref)) { + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + entry = EXT4_XATTR_IFIRST(iheader); + + /* + * The format of the list should be like this: + * + * name_len indicates the length in bytes of the name + * of the EA entry. The string is null-terminated. + * + * list->name => (char *)(list + 1); + * list->next => (void *)((char *)(list + 1) + name_len + 1); + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + if (list) { + list->name_index = entry->e_name_index; + list->name_len = name_len; + list->name = (char *)(list + 1); + memcpy(list->name, EXT4_XATTR_NAME(entry), + list->name_len); + + if (list_prev) + list_prev->next = list; + + list_prev = list; + list = (struct ext4_xattr_list_entry + *)(list->name + name_len + 1); + } + + /* + * Size calculation by pointer arithmetics. + */ + buf_len += + (char *)((struct ext4_xattr_list_entry *)0 + 1) + + name_len + 1 - + (char *)(struct ext4_xattr_list_entry *)0; + } + } + + /* + * If there is a xattr block used by the inode + */ + if (xattr_block) { + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + block_loaded = true; + + /* + * As we don't allow the content in the block being invalid, + * bail out. + */ + if (!ext4_xattr_is_block_valid(inode_ref, &block)) { + ret = EIO; + goto out; + } + + entry = EXT4_XATTR_BFIRST(&block); + + /* + * The format of the list should be like this: + * + * name_len indicates the length in bytes of the name + * of the EA entry. The string is null-terminated. + * + * list->name => (char *)(list + 1); + * list->next => (void *)((char *)(list + 1) + name_len + 1); + * + * Same as above actually. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + if (list) { + list->name_index = entry->e_name_index; + list->name_len = name_len; + list->name = (char *)(list + 1); + memcpy(list->name, EXT4_XATTR_NAME(entry), + list->name_len); + + if (list_prev) + list_prev->next = list; + + list_prev = list; + list = (struct ext4_xattr_list_entry + *)(list->name + name_len + 1); + } + + /* + * Size calculation by pointer arithmetics. + */ + buf_len += + (char *)((struct ext4_xattr_list_entry *)0 + 1) + + name_len + 1 - + (char *)(struct ext4_xattr_list_entry *)0; + } + } + if (list_prev) + list_prev->next = NULL; +out: + if (ret == EOK && list_len) + *list_len = buf_len; + + if (block_loaded) + ext4_block_set(fs->bdev, &block); + + return ret; +} + +/** + * @brief Query EA entry's value with given name-index and name + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be queried + * @param name_len Length of name in bytes + * @param buf Output buffer to hold content + * @param buf_len Output buffer's length + * @param data_len The length of data of the EA entry found + * + * @return Error code + */ +int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, void *buf, size_t buf_len, + size_t *data_len) +{ + int ret = EOK; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_finder block_finder; + struct ext4_xattr_info i; + size_t value_len = 0; + size_t value_offs = 0; + struct ext4_fs *fs = inode_ref->fs; + ext4_fsblk_t xattr_block; + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = 0; + i.value_len = 0; + if (data_len) + *data_len = 0; + + ibody_finder.i = i; + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) + goto out; + + if (!ibody_finder.s.not_found) { + value_len = to_le32(ibody_finder.s.here->e_value_size); + value_offs = to_le32(ibody_finder.s.here->e_value_offs); + if (buf_len && buf) { + void *data_loc = + (char *)ibody_finder.s.base + value_offs; + memcpy(buf, data_loc, + (buf_len < value_len) ? buf_len : value_len); + } + } else { + struct ext4_block block; + + /* Return ENODATA if there is no EA block */ + if (!xattr_block) { + ret = ENODATA; + goto out; + } + + block_finder.i = i; + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + /* Return ENODATA if entry is not found */ + if (block_finder.s.not_found) { + ext4_block_set(fs->bdev, &block); + ret = ENODATA; + goto out; + } + + value_len = to_le32(block_finder.s.here->e_value_size); + value_offs = to_le32(block_finder.s.here->e_value_offs); + if (buf_len && buf) { + void *data_loc = + (char *)block_finder.s.base + value_offs; + memcpy(buf, data_loc, + (buf_len < value_len) ? buf_len : value_len); + } + + /* + * Free the xattr block buffer returned by + * ext4_xattr_block_find_entry. + */ + ext4_block_set(fs->bdev, &block); + } + +out: + if (ret == EOK && data_len) + *data_len = value_len; + + return ret; +} + +/** + * @brief Try to copy the content of an xattr block to a newly-allocated + * block. If the operation fails, the block buffer provided by + * caller will be freed + * + * @param inode_ref Inode reference + * @param block The block buffer reference + * @param new_block The newly-allocated block buffer reference + * @param orig_block The block number of @block + * @param allocated a new block is allocated + * + * @return Error code + */ +static int ext4_xattr_copy_new_block(struct ext4_inode_ref *inode_ref, + struct ext4_block *block, + struct ext4_block *new_block, + ext4_fsblk_t *orig_block, bool *allocated) +{ + int ret = EOK; + ext4_fsblk_t xattr_block = 0; + struct ext4_xattr_header *header; + struct ext4_fs *fs = inode_ref->fs; + header = EXT4_XATTR_BHDR(block); + + if (orig_block) + *orig_block = block->lb_id; + + if (allocated) + *allocated = false; + + /* Only do copy when a block is referenced by more than one inode. */ + if (to_le32(header->h_refcount) > 1) { + ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); + + /* Allocate a new block to be used by this inode */ + ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block); + if (ret != EOK) + goto out; + + ret = ext4_trans_block_get(fs->bdev, new_block, xattr_block); + if (ret != EOK) + goto out; + + /* Copy the content of the whole block */ + memcpy(new_block->data, block->data, + ext4_sb_get_block_size(&inode_ref->fs->sb)); + + /* + * Decrement the reference count of the original xattr block + * by one + */ + header->h_refcount = to_le32(to_le32(header->h_refcount) - 1); + ext4_trans_set_block_dirty(block->buf); + ext4_trans_set_block_dirty(new_block->buf); + + header = EXT4_XATTR_BHDR(new_block); + header->h_refcount = to_le32(1); + + if (allocated) + *allocated = true; + } +out: + if (xattr_block) { + if (ret != EOK) + ext4_balloc_free_block(inode_ref, xattr_block); + else { + /* + * Modify the in-inode pointer to point to the new xattr block + */ + ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, xattr_block); + inode_ref->dirty = true; + } + } + + return ret; +} + +/** + * @brief Given an EA entry's name, remove the EA entry + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be removed + * @param name_len Length of name in bytes + * + * @return Error code + */ +int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len) +{ + int ret = EOK; + struct ext4_block block; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_finder block_finder; + bool use_block = false; + bool block_loaded = false; + struct ext4_xattr_info i; + struct ext4_fs *fs = inode_ref->fs; + ext4_fsblk_t xattr_block; + + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = NULL; + i.value_len = 0; + + ibody_finder.i = i; + block_finder.i = i; + + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) + goto out; + + if (ibody_finder.s.not_found && xattr_block) { + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + block_loaded = true; + block_finder.i = i; + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &block); + if (ret != EOK) + goto out; + + /* Return ENODATA if entry is not found */ + if (block_finder.s.not_found) { + ret = ENODATA; + goto out; + } + use_block = true; + } + + if (use_block) { + bool allocated = false; + struct ext4_block new_block; + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &xattr_block, &allocated); + if (ret != EOK) + goto out; + + if (!allocated) { + /* Prevent double-freeing */ + block_loaded = false; + new_block = block; + } + + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &new_block); + if (ret != EOK) + goto out; + + /* Now remove the entry */ + ext4_xattr_set_entry(&i, &block_finder.s, false); + + if (ext4_xattr_is_empty(&block_finder.s)) { + ext4_block_set(fs->bdev, &new_block); + ext4_xattr_try_free_block(inode_ref); + } else { + struct ext4_xattr_header *header = + EXT4_XATTR_BHDR(&new_block); + header = EXT4_XATTR_BHDR(&new_block); + ext4_assert(block_finder.s.first); + ext4_xattr_rehash(header, block_finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + + ext4_trans_set_block_dirty(new_block.buf); + ext4_block_set(fs->bdev, &new_block); + } + + } else { + /* Now remove the entry */ + ext4_xattr_set_entry(&i, &block_finder.s, false); + inode_ref->dirty = true; + } +out: + if (block_loaded) + ext4_block_set(fs->bdev, &block); + + return ret; +} + +/** + * @brief Insert/overwrite an EA entry into/in a xattr block + * + * @param inode_ref Inode reference + * @param i The information of the given EA entry + * + * @return Error code + */ +static int ext4_xattr_block_set(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_info *i, + bool no_insert) +{ + int ret = EOK; + bool allocated = false; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_block block, new_block; + ext4_fsblk_t orig_xattr_block; + + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + ext4_assert(i->value); + if (!orig_xattr_block) { + struct ext4_xattr_search s; + struct ext4_xattr_header *header; + + /* If insertion of new entry is not allowed... */ + if (no_insert) { + ret = ENODATA; + goto out; + } + + ret = ext4_xattr_try_alloc_block(inode_ref); + if (ret != EOK) + goto out; + + orig_xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) { + ext4_xattr_try_free_block(inode_ref); + goto out; + } + + ext4_xattr_block_initialize(inode_ref, &block); + ext4_xattr_block_init_search(inode_ref, &s, &block); + + ret = ext4_xattr_set_entry(i, &s, false); + if (ret == EOK) { + header = EXT4_XATTR_BHDR(&block); + + ext4_assert(s.here); + ext4_assert(s.first); + ext4_xattr_compute_hash(header, s.here); + ext4_xattr_rehash(header, s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + ext4_block_set(fs->bdev, &block); + if (ret != EOK) + ext4_xattr_try_free_block(inode_ref); + + } else { + struct ext4_xattr_finder finder; + struct ext4_xattr_header *header; + finder.i = *i; + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) + goto out; + + header = EXT4_XATTR_BHDR(&block); + + /* + * Consider the following case when insertion of new + * entry is not allowed + */ + if (to_le32(header->h_refcount) > 1 && no_insert) { + /* + * There are other people referencing the + * same xattr block + */ + ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + if (finder.s.not_found) { + ext4_block_set(fs->bdev, &block); + ret = ENODATA; + goto out; + } + } + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &orig_xattr_block, &allocated); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + if (allocated) { + ext4_block_set(fs->bdev, &block); + new_block = block; + } + + ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + ret = ext4_xattr_set_entry(i, &finder.s, false); + if (ret == EOK) { + header = EXT4_XATTR_BHDR(&block); + + ext4_assert(finder.s.here); + ext4_assert(finder.s.first); + ext4_xattr_compute_hash(header, finder.s.here); + ext4_xattr_rehash(header, finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + ext4_block_set(fs->bdev, &block); + } +out: + return ret; +} + +/** + * @brief Remove an EA entry from a xattr block + * + * @param inode_ref Inode reference + * @param i The information of the given EA entry + * + * @return Error code + */ +static int ext4_xattr_block_remove(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_info *i) +{ + int ret = EOK; + bool allocated = false; + const void *value = i->value; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_finder finder; + struct ext4_block block, new_block; + struct ext4_xattr_header *header; + ext4_fsblk_t orig_xattr_block; + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + ext4_assert(orig_xattr_block); + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) + goto out; + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &orig_xattr_block, &allocated); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + if (allocated) { + ext4_block_set(fs->bdev, &block); + block = new_block; + } + + ext4_xattr_block_find_entry(inode_ref, &finder, &block); + + if (!finder.s.not_found) { + i->value = NULL; + ret = ext4_xattr_set_entry(i, &finder.s, false); + i->value = value; + + header = EXT4_XATTR_BHDR(&block); + ext4_assert(finder.s.first); + ext4_xattr_rehash(header, finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + + ext4_block_set(fs->bdev, &block); +out: + return ret; +} + +/** + * @brief Insert an EA entry into a given inode reference + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be inserted + * @param name_len Length of name in bytes + * @param value Input buffer to hold content + * @param value_len Length of input content + * + * @return Error code + */ +int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, const void *value, + size_t value_len) +{ + int ret = EOK; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_info i; + bool block_found = false; + ext4_fsblk_t orig_xattr_block; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = (value_len) ? value : &ext4_xattr_empty_value; + i.value_len = value_len; + + ibody_finder.i = i; + + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + /* + * Even if entry is not found, search context block inside the + * finder is still valid and can be used to insert entry. + */ + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) { + ext4_xattr_ibody_initialize(inode_ref); + ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + } + + if (ibody_finder.s.not_found) { + if (orig_xattr_block) { + block_found = true; + ret = ext4_xattr_block_set(inode_ref, &i, true); + if (ret == ENOSPC) + goto try_insert; + else if (ret == ENODATA) + goto try_insert; + else if (ret != EOK) + goto out; + + } else + goto try_insert; + + } else { + try_insert: + /* Only try to set entry in ibody if inode is sufficiently large */ + if (extra_isize) + ret = ext4_xattr_set_entry(&i, &ibody_finder.s, false); + else + ret = ENOSPC; + + if (ret == ENOSPC) { + if (!block_found) { + ret = ext4_xattr_block_set(inode_ref, &i, false); + ibody_finder.i.value = NULL; + ext4_xattr_set_entry(&ibody_finder.i, + &ibody_finder.s, false); + inode_ref->dirty = true; + } + + } else if (ret == EOK) { + if (block_found) + ret = ext4_xattr_block_remove(inode_ref, &i); + + inode_ref->dirty = true; + } + } + +out: + return ret; +} + +#endif + +/** + * @} + */ diff --git a/clib/lib/tlsf/CMakeLists.txt b/clib/lib/tlsf/CMakeLists.txt new file mode 100644 index 0000000..3a09655 --- /dev/null +++ b/clib/lib/tlsf/CMakeLists.txt @@ -0,0 +1,16 @@ +set(TLSF_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/tlsf.c +) + +add_library(tlsf OBJECT ${TLSF_SOURCES}) +add_library(tlsf::tlsf ALIAS tlsf) + +target_include_directories(tlsf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(tlsf PRIVATE + KERNEL_BUILD=1 +) + +set_target_properties(tlsf PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib +) diff --git a/clib/lib/tlsf/README.md b/clib/lib/tlsf/README.md new file mode 100644 index 0000000..982919f --- /dev/null +++ b/clib/lib/tlsf/README.md @@ -0,0 +1,92 @@ +# tlsf +Two-Level Segregated Fit memory allocator implementation. +Written by Matthew Conte (matt@baisoku.org). +Released under the BSD license. + +Features +-------- + * O(1) cost for malloc, free, realloc, memalign + * Extremely low overhead per allocation (4 bytes) + * Low overhead per TLSF management of pools (~3kB) + * Low fragmentation + * Compiles to only a few kB of code and data + * Support for adding and removing memory pool regions on the fly + +Caveats +------- + * Currently, assumes architecture can make 4-byte aligned accesses + * Not designed to be thread safe; the user must provide this + +Notes +----- +This code was based on the TLSF 1.4 spec and documentation found at: + + http://www.gii.upv.es/tlsf/main/docs + +It also leverages the TLSF 2.0 improvement to shrink the per-block overhead from 8 to 4 bytes. + +History +------- +2016/04/10 - v3.1 + * Code moved to github + * tlsfbits.h rolled into tlsf.c + * License changed to BSD + +2014/02/08 - v3.0 + * This version is based on improvements from 3DInteractive GmbH + * Interface changed to allow more than one memory pool + * Separated pool handling from control structure (adding, removing, debugging) + * Control structure and pools can still be constructed in the same memory block + * Memory blocks for control structure and pools are checked for alignment + * Added functions to retrieve control structure size, alignment size, min and max block size, overhead of pool structure, and overhead of a single allocation + * Minimal Pool size is tlsf_block_size_min() + tlsf_pool_overhead() + * Pool must be empty when it is removed, in order to allow O(1) removal + +2011/10/20 - v2.0 + * 64-bit support + * More compiler intrinsics for ffs/fls + * ffs/fls verification during TLSF creation in debug builds + +2008/04/04 - v1.9 + * Add tlsf_heap_check, a heap integrity check + * Support a predefined tlsf_assert macro + * Fix realloc case where block should shrink; if adjacent block is in use, execution would go down the slow path + +2007/02/08 - v1.8 + * Fix for unnecessary reallocation in tlsf_realloc + +2007/02/03 - v1.7 + * tlsf_heap_walk takes a callback + * tlsf_realloc now returns NULL on failure + * tlsf_memalign optimization for 4-byte alignment + * Usage of size_t where appropriate + +2006/11/21 - v1.6 + * ffs/fls broken out into tlsfbits.h + * tlsf_overhead queries per-pool overhead + +2006/11/07 - v1.5 + * Smart realloc implementation + * Smart memalign implementation + +2006/10/11 - v1.4 + * Add some ffs/fls implementations + * Minor code footprint reduction + +2006/09/14 - v1.3 + * Profiling indicates heavy use of blocks of size 1-128, so implement small block handling + * Reduce pool overhead by about 1kb + * Reduce minimum block size from 32 to 12 bytes + * Realloc bug fix + +2006/09/09 - v1.2 + * Add tlsf_block_size + * Static assertion mechanism for invariants + * Minor bugfixes + +2006/09/01 - v1.1 + * Add tlsf_realloc + * Add tlsf_walk_heap + +2006/08/25 - v1.0 + * First release diff --git a/clib/lib/tlsf/tlsf.c b/clib/lib/tlsf/tlsf.c new file mode 100644 index 0000000..ecf8f9f --- /dev/null +++ b/clib/lib/tlsf/tlsf.c @@ -0,0 +1,1292 @@ +#pragma GCC diagnostic ignored "-Wunused-value" + +#include +#include + +#include "tlsf.h" + +#if defined(__cplusplus) +#define tlsf_decl inline +#else +#define tlsf_decl static +#endif + +/* +** Architecture-specific bit manipulation routines. +** +** TLSF achieves O(1) cost for malloc and free operations by limiting +** the search for a free block to a free list of guaranteed size +** adequate to fulfill the request, combined with efficient free list +** queries using bitmasks and architecture-specific bit-manipulation +** routines. +** +** Most modern processors provide instructions to count leading zeroes +** in a word, find the lowest and highest set bit, etc. These +** specific implementations will be used when available, falling back +** to a reasonably efficient generic implementation. +** +** NOTE: TLSF spec relies on ffs/fls returning value 0..31. +** ffs/fls return 1-32 by default, returning 0 for error. +*/ + +/* +** Detect whether or not we are building for a 32- or 64-bit (LP/LLP) +** architecture. There is no reliable portable method at compile-time. +*/ +#if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) \ + || defined (_WIN64) || defined (__LP64__) || defined (__LLP64__) +#define TLSF_64BIT +#endif + +/* +** gcc 3.4 and above have builtin support, specialized for architecture. +** Some compilers masquerade as gcc; patchlevel test filters them out. +*/ +#if defined (__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \ + && defined (__GNUC_PATCHLEVEL__) + +#if defined (__SNC__) +/* SNC for Playstation 3. */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __builtin_clz(reverse); + return bit - 1; +} + +#else + +static int __ffs(unsigned int x) { + if (x == 0) return 0; + x = x & -x; + int n = 0; + if (x > 0xFFFF) { n = n + 16; x = x >> 16; } + if (x > 0xFF) { n = n + 8; x = x >> 8; } + if (x > 0xF) { n = n + 4; x = x >> 4; } + if (x > 0x3) { n = n + 2; x = x >> 2; } + if (x > 0x1) { n = n + 1; } + return n + 1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + // return __builtin_ffs(word) - 1; + return __ffs(word) - 1; +} + +#endif + +static unsigned int __clz(unsigned int x) { + if (x == 0) return 32; + int n = 0; + if (x <= 0x0000FFFF) { n = n + 16; x = x << 16; } + if (x <= 0x00FFFFFF) { n = n + 8; x = x << 8; } + if (x <= 0x0FFFFFFF) { n = n + 4; x = x << 4; } + if (x <= 0x3FFFFFFF) { n = n + 2; x = x << 2; } + if (x <= 0x7FFFFFFF) { n = n + 1; } + return n; + // return __builtin_clz(word); +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __clz(word) : 0; + return bit - 1; +} + +#elif defined (_MSC_VER) && (_MSC_VER >= 1400) && (defined (_M_IX86) || defined (_M_X64)) +/* Microsoft Visual C++ support on x86/X64 architectures. */ + +#include + +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +tlsf_decl int tlsf_fls(unsigned int word) +{ + unsigned long index; + return _BitScanReverse(&index, word) ? index : -1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + unsigned long index; + return _BitScanForward(&index, word) ? index : -1; +} + +#elif defined (_MSC_VER) && defined (_M_PPC) +/* Microsoft Visual C++ support on PowerPC architectures. */ + +#include + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = 32 - _CountLeadingZeros(word); + return bit - 1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - _CountLeadingZeros(reverse); + return bit - 1; +} + +#elif defined (__ARMCC_VERSION) +/* RealView Compilation Tools for ARM */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __clz(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __clz(word) : 0; + return bit - 1; +} + +#elif defined (__ghs__) +/* Green Hills support for PowerPC */ + +#include + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __CLZ32(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __CLZ32(word) : 0; + return bit - 1; +} + +#else +/* Fall back to generic implementation. */ + +tlsf_decl int tlsf_fls_generic(unsigned int word) +{ + int bit = 32; + + if (!word) bit -= 1; + if (!(word & 0xffff0000)) { word <<= 16; bit -= 16; } + if (!(word & 0xff000000)) { word <<= 8; bit -= 8; } + if (!(word & 0xf0000000)) { word <<= 4; bit -= 4; } + if (!(word & 0xc0000000)) { word <<= 2; bit -= 2; } + if (!(word & 0x80000000)) { word <<= 1; bit -= 1; } + + return bit; +} + +/* Implement ffs in terms of fls. */ +tlsf_decl int tlsf_ffs(unsigned int word) +{ + return tlsf_fls_generic(word & (~word + 1)) - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + return tlsf_fls_generic(word) - 1; +} + +#endif + +/* Possibly 64-bit version of tlsf_fls. */ +#if defined (TLSF_64BIT) +tlsf_decl int tlsf_fls_sizet(size_t size) +{ + int high = (int)(size >> 32); + int bits = 0; + if (high) + { + bits = 32 + tlsf_fls(high); + } + else + { + bits = tlsf_fls((int)size & 0xffffffff); + + } + return bits; +} +#else +#define tlsf_fls_sizet tlsf_fls +#endif + +#undef tlsf_decl + +/* +** Constants. +*/ + +/* Public constants: may be modified. */ +enum tlsf_public +{ + /* log2 of number of linear subdivisions of block sizes. Larger + ** values require more memory in the control structure. Values of + ** 4 or 5 are typical. + */ + SL_INDEX_COUNT_LOG2 = 5, +}; + +/* Private constants: do not modify. */ +enum tlsf_private +{ +#if defined (TLSF_64BIT) + /* All allocation sizes and addresses are aligned to 8 bytes. */ + ALIGN_SIZE_LOG2 = 3, +#else + /* All allocation sizes and addresses are aligned to 4 bytes. */ + ALIGN_SIZE_LOG2 = 2, +#endif + ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2), + + /* + ** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits. + ** However, because we linearly subdivide the second-level lists, and + ** our minimum size granularity is 4 bytes, it doesn't make sense to + ** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4, + ** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be + ** trying to split size ranges into more slots than we have available. + ** Instead, we calculate the minimum threshold size, and place all + ** blocks below that size into the 0th first-level list. + */ + +#if defined (TLSF_64BIT) + /* + ** TODO: We can increase this to support larger sizes, at the expense + ** of more overhead in the TLSF structure. + */ + FL_INDEX_MAX = 32, +#else + FL_INDEX_MAX = 30, +#endif + SL_INDEX_COUNT = (1 << SL_INDEX_COUNT_LOG2), + FL_INDEX_SHIFT = (SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2), + FL_INDEX_COUNT = (FL_INDEX_MAX - FL_INDEX_SHIFT + 1), + + SMALL_BLOCK_SIZE = (1 << FL_INDEX_SHIFT), +}; + +/* +** Cast and min/max macros. +*/ + +#define tlsf_cast(t, exp) ((t) (exp)) +#define tlsf_min(a, b) ((a) < (b) ? (a) : (b)) +#define tlsf_max(a, b) ((a) > (b) ? (a) : (b)) + +/* +** Set assert macro, if it has not been provided by the user. +*/ +// #if !defined (tlsf_assert) +// #define tlsf_assert assert +// #endif + +#define tlsf_assert + +/* +** Static assertion mechanism. +*/ + +#define _tlsf_glue2(x, y) x ## y +#define _tlsf_glue(x, y) _tlsf_glue2(x, y) +#define tlsf_static_assert(exp) \ + typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1] + +/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */ +tlsf_static_assert(sizeof(int) * CHAR_BIT == 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64); + +/* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */ +tlsf_static_assert(sizeof(unsigned int) * CHAR_BIT >= SL_INDEX_COUNT); + +/* Ensure we've properly tuned our sizes. */ +tlsf_static_assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT); + +/* +** Data structures and associated constants. +*/ + +/* +** Block header structure. +** +** There are several implementation subtleties involved: +** - The prev_phys_block field is only valid if the previous block is free. +** - The prev_phys_block field is actually stored at the end of the +** previous block. It appears at the beginning of this structure only to +** simplify the implementation. +** - The next_free / prev_free fields are only valid if the block is free. +*/ +typedef struct block_header_t +{ + /* Points to the previous physical block. */ + struct block_header_t* prev_phys_block; + + /* The size of this block, excluding the block header. */ + size_t size; + + /* Next and previous free blocks. */ + struct block_header_t* next_free; + struct block_header_t* prev_free; +} block_header_t; + +/* +** Since block sizes are always at least a multiple of 4, the two least +** significant bits of the size field are used to store the block status: +** - bit 0: whether block is busy or free +** - bit 1: whether previous block is busy or free +*/ +static const size_t block_header_free_bit = 1 << 0; +static const size_t block_header_prev_free_bit = 1 << 1; + +/* +** The size of the block header exposed to used blocks is the size field. +** The prev_phys_block field is stored *inside* the previous free block. +*/ +static const size_t block_header_overhead = sizeof(size_t); + +/* User data starts directly after the size field in a used block. */ +static const size_t block_start_offset = + offsetof(block_header_t, size) + sizeof(size_t); + +/* +** A free block must be large enough to store its header minus the size of +** the prev_phys_block field, and no larger than the number of addressable +** bits for FL_INDEX. +*/ +static const size_t block_size_min = + sizeof(block_header_t) - sizeof(block_header_t*); +static const size_t block_size_max = tlsf_cast(size_t, 1) << FL_INDEX_MAX; + + +/* The TLSF control structure. */ +typedef struct control_t +{ + /* Empty lists point at this block to indicate they are free. */ + block_header_t block_null; + + /* Bitmaps for free lists. */ + unsigned int fl_bitmap; + unsigned int sl_bitmap[FL_INDEX_COUNT]; + + /* Head of free lists. */ + block_header_t* blocks[FL_INDEX_COUNT][SL_INDEX_COUNT]; +} control_t; + +/* A type used for casting when doing pointer arithmetic. */ +typedef ptrdiff_t tlsfptr_t; + +/* +** block_header_t member functions. +*/ + +static size_t block_size(const block_header_t* block) +{ + return block->size & ~(block_header_free_bit | block_header_prev_free_bit); +} + +static void block_set_size(block_header_t* block, size_t size) +{ + const size_t oldsize = block->size; + block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit)); +} + +static int block_is_last(const block_header_t* block) +{ + return block_size(block) == 0; +} + +static int block_is_free(const block_header_t* block) +{ + return tlsf_cast(int, block->size & block_header_free_bit); +} + +static void block_set_free(block_header_t* block) +{ + block->size |= block_header_free_bit; +} + +static void block_set_used(block_header_t* block) +{ + block->size &= ~block_header_free_bit; +} + +static int block_is_prev_free(const block_header_t* block) +{ + return tlsf_cast(int, block->size & block_header_prev_free_bit); +} + +static void block_set_prev_free(block_header_t* block) +{ + block->size |= block_header_prev_free_bit; +} + +static void block_set_prev_used(block_header_t* block) +{ + block->size &= ~block_header_prev_free_bit; +} + +static block_header_t* block_from_ptr(const void* ptr) +{ + return tlsf_cast(block_header_t*, + tlsf_cast(unsigned char*, ptr) - block_start_offset); +} + +static void* block_to_ptr(const block_header_t* block) +{ + return tlsf_cast(void*, + tlsf_cast(unsigned char*, block) + block_start_offset); +} + +/* Return location of next block after block of given size. */ +static block_header_t* offset_to_block(const void* ptr, size_t size) +{ + return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size); +} + +/* Return location of previous block. */ +static block_header_t* block_prev(const block_header_t* block) +{ + tlsf_assert(block_is_prev_free(block) && "previous block must be free"); + return block->prev_phys_block; +} + +/* Return location of next existing block. */ +static block_header_t* block_next(const block_header_t* block) +{ + block_header_t* next = offset_to_block(block_to_ptr(block), + block_size(block) - block_header_overhead); + tlsf_assert(!block_is_last(block)); + return next; +} + +/* Link a new block with its physical neighbor, return the neighbor. */ +static block_header_t* block_link_next(block_header_t* block) +{ + block_header_t* next = block_next(block); + next->prev_phys_block = block; + return next; +} + +static void block_mark_as_free(block_header_t* block) +{ + /* Link the block to the next block, first. */ + block_header_t* next = block_link_next(block); + block_set_prev_free(next); + block_set_free(block); +} + +static void block_mark_as_used(block_header_t* block) +{ + block_header_t* next = block_next(block); + block_set_prev_used(next); + block_set_used(block); +} + +static size_t align_up(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return (x + (align - 1)) & ~(align - 1); +} + +static size_t align_down(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return x - (x & (align - 1)); +} + +static void* align_ptr(const void* ptr, size_t align) +{ + const tlsfptr_t aligned = + (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1); + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return tlsf_cast(void*, aligned); +} + +/* +** Adjust an allocation size to be aligned to word size, and no smaller +** than internal minimum. +*/ +static size_t adjust_request_size(size_t size, size_t align) +{ + size_t adjust = 0; + if (size) + { + const size_t aligned = align_up(size, align); + + /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */ + if (aligned < block_size_max) + { + adjust = tlsf_max(aligned, block_size_min); + } + } + return adjust; +} + +/* +** TLSF utility functions. In most cases, these are direct translations of +** the documentation found in the white paper. +*/ + +static void mapping_insert(size_t size, int* fli, int* sli) +{ + int fl, sl; + if (size < SMALL_BLOCK_SIZE) + { + /* Store small blocks in first list. */ + fl = 0; + sl = tlsf_cast(int, size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT); + } + else + { + fl = tlsf_fls_sizet(size); + sl = tlsf_cast(int, size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2); + fl -= (FL_INDEX_SHIFT - 1); + } + *fli = fl; + *sli = sl; +} + +/* This version rounds up to the next block size (for allocations) */ +static void mapping_search(size_t size, int* fli, int* sli) +{ + if (size >= SMALL_BLOCK_SIZE) + { + const size_t round = (1 << (tlsf_fls_sizet(size) - SL_INDEX_COUNT_LOG2)) - 1; + size += round; + } + mapping_insert(size, fli, sli); +} + +static block_header_t* search_suitable_block(control_t* control, int* fli, int* sli) +{ + int fl = *fli; + int sl = *sli; + + /* + ** First, search for a block in the list associated with the given + ** fl/sl index. + */ + unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl); + if (!sl_map) + { + /* No block exists. Search in the next largest first-level list. */ + const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1)); + if (!fl_map) + { + /* No free blocks available, memory has been exhausted. */ + return 0; + } + + fl = tlsf_ffs(fl_map); + *fli = fl; + sl_map = control->sl_bitmap[fl]; + } + tlsf_assert(sl_map && "internal error - second level bitmap is null"); + sl = tlsf_ffs(sl_map); + *sli = sl; + + /* Return the first block in the free list. */ + return control->blocks[fl][sl]; +} + +/* Remove a free block from the free list.*/ +static void remove_free_block(control_t* control, block_header_t* block, int fl, int sl) +{ + block_header_t* prev = block->prev_free; + block_header_t* next = block->next_free; + tlsf_assert(prev && "prev_free field can not be null"); + tlsf_assert(next && "next_free field can not be null"); + next->prev_free = prev; + prev->next_free = next; + + /* If this block is the head of the free list, set new head. */ + if (control->blocks[fl][sl] == block) + { + control->blocks[fl][sl] = next; + + /* If the new head is null, clear the bitmap. */ + if (next == &control->block_null) + { + control->sl_bitmap[fl] &= ~(1U << sl); + + /* If the second bitmap is now empty, clear the fl bitmap. */ + if (!control->sl_bitmap[fl]) + { + control->fl_bitmap &= ~(1U << fl); + } + } + } +} + +/* Insert a free block into the free block list. */ +static void insert_free_block(control_t* control, block_header_t* block, int fl, int sl) +{ + block_header_t* current = control->blocks[fl][sl]; + tlsf_assert(current && "free list cannot have a null entry"); + tlsf_assert(block && "cannot insert a null entry into the free list"); + block->next_free = current; + block->prev_free = &control->block_null; + current->prev_free = block; + + tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE) + && "block not aligned properly"); + /* + ** Insert the new block at the head of the list, and mark the first- + ** and second-level bitmaps appropriately. + */ + control->blocks[fl][sl] = block; + control->fl_bitmap |= (1U << fl); + control->sl_bitmap[fl] |= (1U << sl); +} + +/* Remove a given block from the free list. */ +static void block_remove(control_t* control, block_header_t* block) +{ + int fl, sl; + mapping_insert(block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* Insert a given block into the free list. */ +static void block_insert(control_t* control, block_header_t* block) +{ + int fl, sl; + mapping_insert(block_size(block), &fl, &sl); + insert_free_block(control, block, fl, sl); +} + +static int block_can_split(block_header_t* block, size_t size) +{ + return block_size(block) >= sizeof(block_header_t) + size; +} + +/* Split a block into two, the second of which is free. */ +static block_header_t* block_split(block_header_t* block, size_t size) +{ + /* Calculate the amount of space left in the remaining block. */ + block_header_t* remaining = + offset_to_block(block_to_ptr(block), size - block_header_overhead); + + const size_t remain_size = block_size(block) - (size + block_header_overhead); + + tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE) + && "remaining block not aligned properly"); + + tlsf_assert(block_size(block) == remain_size + size + block_header_overhead); + block_set_size(remaining, remain_size); + tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size"); + + block_set_size(block, size); + block_mark_as_free(remaining); + + return remaining; +} + +/* Absorb a free block's storage into an adjacent previous free block. */ +static block_header_t* block_absorb(block_header_t* prev, block_header_t* block) +{ + tlsf_assert(!block_is_last(prev) && "previous block can't be last"); + /* Note: Leaves flags untouched. */ + prev->size += block_size(block) + block_header_overhead; + block_link_next(prev); + return prev; +} + +/* Merge a just-freed block with an adjacent previous free block. */ +static block_header_t* block_merge_prev(control_t* control, block_header_t* block) +{ + if (block_is_prev_free(block)) + { + block_header_t* prev = block_prev(block); + tlsf_assert(prev && "prev physical block can't be null"); + tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such"); + block_remove(control, prev); + block = block_absorb(prev, block); + } + + return block; +} + +/* Merge a just-freed block with an adjacent free block. */ +static block_header_t* block_merge_next(control_t* control, block_header_t* block) +{ + block_header_t* next = block_next(block); + tlsf_assert(next && "next physical block can't be null"); + + if (block_is_free(next)) + { + tlsf_assert(!block_is_last(block) && "previous block can't be last"); + block_remove(control, next); + block = block_absorb(block, next); + } + + return block; +} + +/* Trim any trailing block space off the end of a block, return to pool. */ +static void block_trim_free(control_t* control, block_header_t* block, size_t size) +{ + tlsf_assert(block_is_free(block) && "block must be free"); + if (block_can_split(block, size)) + { + block_header_t* remaining_block = block_split(block, size); + block_link_next(block); + block_set_prev_free(remaining_block); + block_insert(control, remaining_block); + } +} + +/* Trim any trailing block space off the end of a used block, return to pool. */ +static void block_trim_used(control_t* control, block_header_t* block, size_t size) +{ + tlsf_assert(!block_is_free(block) && "block must be used"); + if (block_can_split(block, size)) + { + /* If the next block is free, we must coalesce. */ + block_header_t* remaining_block = block_split(block, size); + block_set_prev_used(remaining_block); + + remaining_block = block_merge_next(control, remaining_block); + block_insert(control, remaining_block); + } +} + +static block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size) +{ + block_header_t* remaining_block = block; + if (block_can_split(block, size)) + { + /* We want the 2nd block. */ + remaining_block = block_split(block, size - block_header_overhead); + block_set_prev_free(remaining_block); + + block_link_next(block); + block_insert(control, block); + } + + return remaining_block; +} + +static block_header_t* block_locate_free(control_t* control, size_t size) +{ + int fl = 0, sl = 0; + block_header_t* block = 0; + + if (size) + { + mapping_search(size, &fl, &sl); + + /* + ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up + ** with indices that are off the end of the block array. + ** So, we protect against that here, since this is the only callsite of mapping_search. + ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range. + */ + if (fl < FL_INDEX_COUNT) + { + block = search_suitable_block(control, &fl, &sl); + } + } + + if (block) + { + tlsf_assert(block_size(block) >= size); + remove_free_block(control, block, fl, sl); + } + + return block; +} + +static void* block_prepare_used(control_t* control, block_header_t* block, size_t size) +{ + void* p = 0; + if (block) + { + tlsf_assert(size && "size must be non-zero"); + block_trim_free(control, block, size); + block_mark_as_used(block); + p = block_to_ptr(block); + } + return p; +} + +/* Clear structure and point all empty lists at the null block. */ +static void control_construct(control_t* control) +{ + int i, j; + + control->block_null.next_free = &control->block_null; + control->block_null.prev_free = &control->block_null; + + control->fl_bitmap = 0; + for (i = 0; i < FL_INDEX_COUNT; ++i) + { + control->sl_bitmap[i] = 0; + for (j = 0; j < SL_INDEX_COUNT; ++j) + { + control->blocks[i][j] = &control->block_null; + } + } +} + +/* +** Debugging utilities. +*/ + +typedef struct integrity_t +{ + int prev_status; + int status; +} integrity_t; + +#define tlsf_insist(x) { tlsf_assert(x); if (!(x)) { status--; } } + +static void integrity_walker(void* ptr, size_t size, int used, void* user) +{ + block_header_t* block = block_from_ptr(ptr); + integrity_t* integ = tlsf_cast(integrity_t*, user); + const int this_prev_status = block_is_prev_free(block) ? 1 : 0; + const int this_status = block_is_free(block) ? 1 : 0; + const size_t this_block_size = block_size(block); + + int status = 0; + (void)used; + tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect"); + tlsf_insist(size == this_block_size && "block size incorrect"); + + integ->prev_status = this_status; + integ->status += status; +} + +int tlsf_check(tlsf_t tlsf) +{ + int i, j; + + control_t* control = tlsf_cast(control_t*, tlsf); + int status = 0; + + /* Check that the free lists and bitmaps are accurate. */ + for (i = 0; i < FL_INDEX_COUNT; ++i) + { + for (j = 0; j < SL_INDEX_COUNT; ++j) + { + const int fl_map = control->fl_bitmap & (1U << i); + const int sl_list = control->sl_bitmap[i]; + const int sl_map = sl_list & (1U << j); + const block_header_t* block = control->blocks[i][j]; + + /* Check that first- and second-level lists agree. */ + if (!fl_map) + { + tlsf_insist(!sl_map && "second-level map must be null"); + } + + if (!sl_map) + { + tlsf_insist(block == &control->block_null && "block list must be null"); + continue; + } + + /* Check that there is at least one free block. */ + tlsf_insist(sl_list && "no free blocks in second-level map"); + tlsf_insist(block != &control->block_null && "block should not be null"); + + while (block != &control->block_null) + { + int fli, sli; + tlsf_insist(block_is_free(block) && "block should be free"); + tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced"); + tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced"); + tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free"); + tlsf_insist(block_size(block) >= block_size_min && "block not minimum size"); + + mapping_insert(block_size(block), &fli, &sli); + tlsf_insist(fli == i && sli == j && "block size indexed in wrong list"); + block = block->next_free; + } + } + } + + return status; +} + +#undef tlsf_insist + +static void default_walker(void* ptr, size_t size, int used, void* user) +{ + (void)ptr; + (void)size; + (void)used; + (void)user; + // printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr)); +} + +void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user) +{ + tlsf_walker pool_walker = walker ? walker : default_walker; + block_header_t* block = + offset_to_block(pool, -(int)block_header_overhead); + + while (block && !block_is_last(block)) + { + pool_walker( + block_to_ptr(block), + block_size(block), + !block_is_free(block), + user); + block = block_next(block); + } +} + +size_t tlsf_block_size(void* ptr) +{ + size_t size = 0; + if (ptr) + { + const block_header_t* block = block_from_ptr(ptr); + size = block_size(block); + } + return size; +} + +int tlsf_check_pool(pool_t pool) +{ + /* Check that the blocks are physically correct. */ + integrity_t integ = { 0, 0 }; + tlsf_walk_pool(pool, integrity_walker, &integ); + + return integ.status; +} + +/* +** Size of the TLSF structures in a given memory block passed to +** tlsf_create, equal to the size of a control_t +*/ +size_t tlsf_size(void) +{ + return sizeof(control_t); +} + +size_t tlsf_align_size(void) +{ + return ALIGN_SIZE; +} + +size_t tlsf_block_size_min(void) +{ + return block_size_min; +} + +size_t tlsf_block_size_max(void) +{ + return block_size_max; +} + +/* +** Overhead of the TLSF structures in a given memory block passed to +** tlsf_add_pool, equal to the overhead of a free block and the +** sentinel block. +*/ +size_t tlsf_pool_overhead(void) +{ + return 2 * block_header_overhead; +} + +size_t tlsf_alloc_overhead(void) +{ + return block_header_overhead; +} + +pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes) +{ + block_header_t* block; + block_header_t* next; + + const size_t pool_overhead = tlsf_pool_overhead(); + const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE); + + if (((ptrdiff_t)mem % ALIGN_SIZE) != 0) + { + // printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n", + // (unsigned int)ALIGN_SIZE); + return 0; + } + + if (pool_bytes < block_size_min || pool_bytes > block_size_max) + { +#if defined (TLSF_64BIT) + // printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n", + // (unsigned int)(pool_overhead + block_size_min), + // (unsigned int)((pool_overhead + block_size_max) / 256)); +#else + printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n", + (unsigned int)(pool_overhead + block_size_min), + (unsigned int)(pool_overhead + block_size_max)); +#endif + return 0; + } + + /* + ** Create the main free block. Offset the start of the block slightly + ** so that the prev_phys_block field falls outside of the pool - + ** it will never be used. + */ + block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead); + block_set_size(block, pool_bytes); + block_set_free(block); + block_set_prev_used(block); + block_insert(tlsf_cast(control_t*, tlsf), block); + + /* Split the block to create a zero-size sentinel block. */ + next = block_link_next(block); + block_set_size(next, 0); + block_set_used(next); + block_set_prev_free(next); + + return mem; +} + +void tlsf_remove_pool(tlsf_t tlsf, pool_t pool) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + block_header_t* block = offset_to_block(pool, -(int)block_header_overhead); + + int fl = 0, sl = 0; + + tlsf_assert(block_is_free(block) && "block should be free"); + tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free"); + tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero"); + + mapping_insert(block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* +** TLSF main interface. +*/ + +#if _DEBUG +int test_ffs_fls() +{ + /* Verify ffs/fls work properly. */ + int rv = 0; + rv += (tlsf_ffs(0) == -1) ? 0 : 0x1; + rv += (tlsf_fls(0) == -1) ? 0 : 0x2; + rv += (tlsf_ffs(1) == 0) ? 0 : 0x4; + rv += (tlsf_fls(1) == 0) ? 0 : 0x8; + rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10; + rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20; + rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40; + rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80; + +#if defined (TLSF_64BIT) + rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100; + rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200; + rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400; +#endif + + if (rv) + { + printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv); + } + return rv; +} +#endif + +tlsf_t tlsf_create(void* mem) +{ +#if _DEBUG + if (test_ffs_fls()) + { + return 0; + } +#endif + + if (((tlsfptr_t)mem % ALIGN_SIZE) != 0) + { + // printf("tlsf_create: Memory must be aligned to %u bytes.\n", + // (unsigned int)ALIGN_SIZE); + return 0; + } + + control_construct(tlsf_cast(control_t*, mem)); + + return tlsf_cast(tlsf_t, mem); +} + +tlsf_t tlsf_create_with_pool(void* mem, size_t bytes) +{ + tlsf_t tlsf = tlsf_create(mem); + tlsf_add_pool(tlsf, (char*)mem + tlsf_size(), bytes - tlsf_size()); + return tlsf; +} + +void tlsf_destroy(tlsf_t tlsf) +{ + /* Nothing to do. */ + (void)tlsf; +} + +pool_t tlsf_get_pool(tlsf_t tlsf) +{ + return tlsf_cast(pool_t, (char*)tlsf + tlsf_size()); +} + +void* tlsf_malloc(tlsf_t tlsf, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + block_header_t* block = block_locate_free(control, adjust); + return block_prepare_used(control, block, adjust); +} + +void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + + /* + ** We must allocate an additional minimum block size bytes so that if + ** our free block will leave an alignment gap which is smaller, we can + ** trim a leading free block and release it back to the pool. We must + ** do this because the previous physical block is in use, therefore + ** the prev_phys_block field is not valid, and we can't simply adjust + ** the size of that block. + */ + const size_t gap_minimum = sizeof(block_header_t); + const size_t size_with_gap = adjust_request_size(adjust + align + gap_minimum, align); + + /* + ** If alignment is less than or equals base alignment, we're done. + ** If we requested 0 bytes, return null, as tlsf_malloc(0) does. + */ + const size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust; + + block_header_t* block = block_locate_free(control, aligned_size); + + /* This can't be a static assert. */ + tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead); + + if (block) + { + void* ptr = block_to_ptr(block); + void* aligned = align_ptr(ptr, align); + size_t gap = tlsf_cast(size_t, + tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + + /* If gap size is too small, offset to next aligned boundary. */ + if (gap && gap < gap_minimum) + { + const size_t gap_remain = gap_minimum - gap; + const size_t offset = tlsf_max(gap_remain, align); + const void* next_aligned = tlsf_cast(void*, + tlsf_cast(tlsfptr_t, aligned) + offset); + + aligned = align_ptr(next_aligned, align); + gap = tlsf_cast(size_t, + tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + } + + if (gap) + { + tlsf_assert(gap >= gap_minimum && "gap size too small"); + block = block_trim_free_leading(control, block, gap); + } + } + + return block_prepare_used(control, block, adjust); +} + +void tlsf_free(tlsf_t tlsf, void* ptr) +{ + /* Don't attempt to free a NULL pointer. */ + if (ptr) + { + control_t* control = tlsf_cast(control_t*, tlsf); + block_header_t* block = block_from_ptr(ptr); + tlsf_assert(!block_is_free(block) && "block already marked as free"); + block_mark_as_free(block); + block = block_merge_prev(control, block); + block = block_merge_next(control, block); + block_insert(control, block); + } +} + +/* +** The TLSF block information provides us with enough information to +** provide a reasonably intelligent implementation of realloc, growing or +** shrinking the currently allocated block as required. +** +** This routine handles the somewhat esoteric edge cases of realloc: +** - a non-zero size with a null pointer will behave like malloc +** - a zero size with a non-null pointer will behave like free +** - a request that cannot be satisfied will leave the original buffer +** untouched +** - an extended buffer size will leave the newly-allocated area with +** contents undefined +*/ +void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size) +{ + control_t* control = tlsf_cast(control_t*, tlsf); + void* p = 0; + + /* Zero-size requests are treated as free. */ + if (ptr && size == 0) + { + tlsf_free(tlsf, ptr); + } + /* Requests with NULL pointers are treated as malloc. */ + else if (!ptr) + { + p = tlsf_malloc(tlsf, size); + } + else + { + block_header_t* block = block_from_ptr(ptr); + block_header_t* next = block_next(block); + + const size_t cursize = block_size(block); + const size_t combined = cursize + block_size(next) + block_header_overhead; + const size_t adjust = adjust_request_size(size, ALIGN_SIZE); + + tlsf_assert(!block_is_free(block) && "block already marked as free"); + + /* + ** If the next block is used, or when combined with the current + ** block, does not offer enough space, we must reallocate and copy. + */ + if (adjust > cursize && (!block_is_free(next) || adjust > combined)) + { + p = tlsf_malloc(tlsf, size); + if (p) + { + const size_t minsize = tlsf_min(cursize, size); + __builtin_memcpy(p, ptr, minsize); + tlsf_free(tlsf, ptr); + } + } + else + { + /* Do we need to expand to the next block? */ + if (adjust > cursize) + { + block_merge_next(control, block); + block_mark_as_used(block); + } + + /* Trim the resulting block and return the original pointer. */ + block_trim_used(control, block, adjust); + p = ptr; + } + } + + return p; +} diff --git a/clib/lib/tlsf/tlsf.h b/clib/lib/tlsf/tlsf.h new file mode 100644 index 0000000..e9b5a91 --- /dev/null +++ b/clib/lib/tlsf/tlsf.h @@ -0,0 +1,90 @@ +#ifndef INCLUDED_tlsf +#define INCLUDED_tlsf + +/* +** Two Level Segregated Fit memory allocator, version 3.1. +** Written by Matthew Conte +** http://tlsf.baisoku.org +** +** Based on the original documentation by Miguel Masmano: +** http://www.gii.upv.es/tlsf/main/docs +** +** This implementation was written to the specification +** of the document, therefore no GPL restrictions apply. +** +** Copyright (c) 2006-2016, Matthew Conte +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the copyright holder nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */ +/* pool_t: a block of memory that TLSF can manage. */ +typedef void* tlsf_t; +typedef void* pool_t; + +/* Create/destroy a memory pool. */ +tlsf_t tlsf_create(void* mem); +tlsf_t tlsf_create_with_pool(void* mem, size_t bytes); +void tlsf_destroy(tlsf_t tlsf); +pool_t tlsf_get_pool(tlsf_t tlsf); + +/* Add/remove memory pools. */ +pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes); +void tlsf_remove_pool(tlsf_t tlsf, pool_t pool); + +/* malloc/memalign/realloc/free replacements. */ +void* tlsf_malloc(tlsf_t tlsf, size_t bytes); +void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes); +void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size); +void tlsf_free(tlsf_t tlsf, void* ptr); + +/* Returns internal block size, not original request size */ +size_t tlsf_block_size(void* ptr); + +/* Overheads/limits of internal structures. */ +size_t tlsf_size(void); +size_t tlsf_align_size(void); +size_t tlsf_block_size_min(void); +size_t tlsf_block_size_max(void); +size_t tlsf_pool_overhead(void); +size_t tlsf_alloc_overhead(void); + +/* Debugging. */ +typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user); +void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user); +/* Returns nonzero if any internal consistency check fails. */ +int tlsf_check(tlsf_t tlsf); +int tlsf_check_pool(pool_t pool); + +#if defined(__cplusplus) +}; +#endif + +#endif diff --git a/clib/src/arch/riscv/entry/addr.S b/clib/src/arch/riscv/entry/addr.S new file mode 100644 index 0000000..aca2cb4 --- /dev/null +++ b/clib/src/arch/riscv/entry/addr.S @@ -0,0 +1,57 @@ + .extern __riscv_kernel_end + .extern __riscv_kpgtable_root + + .section .text.init + + .global __riscv_init_load_kernel_end + .type __riscv_init_load_kernel_end, @function + +__riscv_init_load_kernel_end: + .option push + .option norelax +1: auipc a0, %got_pcrel_hi(__riscv_kernel_end) + ld a0, %pcrel_lo(1b)(a0) + .option pop + + sub a0, a0, tp + + ret + + .global __riscv_init_load_kpgtable_root + .type __riscv_init_load_kpgtable_root, @function +__riscv_init_load_kpgtable_root: + .option push + .option norelax +2: auipc a0, %got_pcrel_hi(__riscv_kpgtable_root) + ld a0, %pcrel_lo(2b)(a0) + .option pop + + sub a0, a0, tp + + ret + + .global __riscv_init_load_kaddr_offset + .type __riscv_init_load_kaddr_offset, @function +__riscv_init_load_kaddr_offset: + .option push + .option norelax +3: auipc a0, %got_pcrel_hi(__riscv_kaddr_offset) + ld a0, %pcrel_lo(3b)(a0) + .option pop + + sub a0, a0, tp + + ret + + .global __riscv_init_load_copied_fdt + .type __riscv_init_load_copied_fdt, @function +__riscv_init_load_copied_fdt: + .option push + .option norelax +4: auipc a0, %got_pcrel_hi(__riscv_copied_fdt) + ld a0, %pcrel_lo(4b)(a0) + .option pop + + sub a0, a0, tp + + ret diff --git a/clib/src/arch/riscv/entry/entry.S b/clib/src/arch/riscv/entry/entry.S new file mode 100644 index 0000000..7be8b42 --- /dev/null +++ b/clib/src/arch/riscv/entry/entry.S @@ -0,0 +1,35 @@ + .global _entry + .section .text.entry + +_entry: + auipc t0, 0 +1: auipc t1, %got_pcrel_hi(_entry) + ld t1, %pcrel_lo(1b)(t1) + sub t1, t1, t0 // t1 = KADDR_OFFSET + mv tp, t1 + + la sp, __stack_end + andi sp, sp, -16 + sub sp, sp, t1 + + mv a2, t1 // a2 = KADDR_OFFSET + call __riscv_init + + li tp, 0 + + la sp, __stack_end + la t0, main + + sfence.vma + csrw satp, a3 + sfence.vma + + /* + We can't use la after setting satp + */ + + jalr t0 + +.spin: + wfi + j .spin \ No newline at end of file diff --git a/clib/src/arch/riscv/entry/fdt.c b/clib/src/arch/riscv/entry/fdt.c new file mode 100644 index 0000000..a0c199f --- /dev/null +++ b/clib/src/arch/riscv/entry/fdt.c @@ -0,0 +1,62 @@ +#include "arch/riscv/entry.h" +#include "libfdt.h" + +#include + +void *__riscv_copied_fdt; + +__init_text +static inline uint64_t get_memory_top_from_fdt(const void *fdt) { + int node_offset; + + fdt_for_each_subnode(node_offset, fdt, 0) { + const char *node_name = fdt_get_name(fdt, node_offset, NULL); + if (!node_name) continue; + + if (strncmp(node_name, "memory", 6) == 0) { + int prop_len; + const uint32_t *prop_val = fdt_getprop(fdt, node_offset, "reg", &prop_len); + if (!prop_val) continue; + + uint64_t base, size; + if (prop_len >= 16) { + base = ((uint64_t)fdt32_to_cpu(prop_val[0]) << 32) | fdt32_to_cpu(prop_val[1]); + size = ((uint64_t)fdt32_to_cpu(prop_val[2]) << 32) | fdt32_to_cpu(prop_val[3]); + } else if (prop_len >= 8) { + base = fdt32_to_cpu(prop_val[0]); + size = fdt32_to_cpu(prop_val[1]); + } else { + __riscv_init_die("prop_len is invalid."); + return 0; + } + return base + size; + } + } + + __riscv_init_die("Kernel panic: no memory node found in FDT\n"); + + return 0; +} + +__init_text +uintptr_t __riscv_load_fdt(const void *fdt) { + uintptr_t *kernel_end = __riscv_init_load_kernel_end(); + + if (fdt_check_header(fdt) != 0) { + __riscv_init_die("FDT header is invalid.\n"); + return 0; + } + + uint32_t fdt_size = fdt_totalsize(fdt); + + const char *src = (const char *)fdt; + char *dst = (char *)*kernel_end; + for (uint32_t i = 0; i < fdt_size; i++) { + dst[i] = src[i]; + } + + *__riscv_init_load_copied_fdt() = (void *)(dst + *__riscv_init_load_kaddr_offset()); + *kernel_end += fdt_size; + + return get_memory_top_from_fdt(fdt); +} diff --git a/clib/src/arch/riscv/entry/init.c b/clib/src/arch/riscv/entry/init.c new file mode 100644 index 0000000..a91b9dd --- /dev/null +++ b/clib/src/arch/riscv/entry/init.c @@ -0,0 +1,86 @@ +#include + +#include "arch/riscv/entry.h" + +extern char __kernel_end[]; +extern char __bss_start[]; +extern char __bss_end []; + +__init_data +uintptr_t __riscv_kernel_end; + +uintptr_t __riscv_kaddr_offset; + +__init_text +void __riscv_init(uintptr_t hartid, const void *fdt, uintptr_t kaddr_offset) { + // Clear BSS + // Assume BSS in aligned to 4K + uintptr_t bss_start = (uintptr_t)__bss_start - kaddr_offset; + uintptr_t bss_end = (uintptr_t)__bss_end - kaddr_offset; + for (uintptr_t p = bss_start; p < bss_end; p++) { + *((char *)p) = 0; + } + + *__riscv_init_load_kernel_end() = (uintptr_t)__kernel_end - kaddr_offset; + *__riscv_init_load_kaddr_offset() = kaddr_offset; + + uintptr_t memory_top = __riscv_load_fdt(fdt); + uintptr_t satp = __riscv_map_kaddr(kaddr_offset, memory_top); + + uintptr_t kernel_end = *__riscv_init_load_kernel_end() + kaddr_offset; + + /* + Return: + a0: hartid + a1: heap_start + a2: satp + */ + asm volatile ( + "mv a0, %0\n" + "mv a1, %1\n" + "mv a2, %2\n" + "mv a3, %3\n" + : + : "r"(hartid), "r"(kernel_end), "r"(memory_top + kaddr_offset), "r"(satp) + : "a0", "a1", "a2", "a3" + ); +} + +__init_text +static inline void sbi_putchar(char c) { + asm volatile ( + "li a6, 0\n" + "li a7, 1\n" + "mv a0, %0\n" + "ecall\n" + : + : "r"(c) + : "a0", "a6", "a7" + ); +} + +__init_text +static inline void sbi_shutdown() { + asm volatile ( + "li a6, 0\n" + "li a7, 8\n" + "ecall\n" + : + : + : "a0", "a7" + ); +} + +__init_text +void __riscv_init_die(const char *reason) { + const char *msg; + msg = "Kernel panic: "; + for (const char *p = msg; *p != '\0'; p++) { + sbi_putchar(*p); + } + for (const char *p = reason; *p != '\0'; p++) { + sbi_putchar(*p); + } + sbi_shutdown(); + while(1); +} diff --git a/clib/src/arch/riscv/entry/mapkernel.c b/clib/src/arch/riscv/entry/mapkernel.c new file mode 100644 index 0000000..c481139 --- /dev/null +++ b/clib/src/arch/riscv/entry/mapkernel.c @@ -0,0 +1,86 @@ +#include "arch/riscv/entry.h" +#include + +#define LEVEL 2 +#define PTE_V (1 << 0) +#define PTE_R (1 << 1) +#define PTE_W (1 << 2) +#define PTE_X (1 << 3) +#define PTE_G (1 << 5) +#define PTE_A (1 << 6) +#define PTE_D (1 << 7) + +__init_text +static inline void *alloc_page() { + uintptr_t *kernel_end = __riscv_init_load_kernel_end(); + void *page = (void *)*kernel_end; + *kernel_end += PGSIZE; + return page; +} + +extern char __init_start[]; +extern char __init_end []; +extern char __text_start[]; +extern char __text_end []; + +uintptr_t __riscv_kpgtable_root; + +__init_text +static inline uintptr_t get_ppn(uintptr_t paddr) { + return paddr >> 12; +} + +__init_text +static inline void map(uintptr_t root, uintptr_t kaddr, uint64_t paddr, uint8_t flag) { + uintptr_t ppn = get_ppn(root); + for (int level = 0; level <= LEVEL; level++) { + uint64_t vpn = (kaddr >> (12 + (LEVEL - level) * 9)) & 0x1ff; + uintptr_t *pagetable = (uintptr_t *)(ppn << 12); + uintptr_t *pte = &pagetable[vpn]; + + if (level == LEVEL) { + *pte = (get_ppn(paddr) << 10) | flag; + return; + } + + if (!(*pte & PTE_V)) { + void *new_page = alloc_page(); + for (unsigned int i = 0; i < PGSIZE / sizeof(uintptr_t); i++) { + ((uintptr_t *)new_page)[i] = 0; + } + *pte = (get_ppn((uintptr_t)new_page) << 10) | PTE_V; + } + + ppn = *pte >> 10; + } +} + +__init_text +uintptr_t __riscv_map_kaddr(uintptr_t kaddr_offset, uintptr_t memory_top) { + uintptr_t *kernel_end = (uintptr_t *)__riscv_init_load_kernel_end(); + *kernel_end = (*kernel_end + PGSIZE - 1) & ~(PGSIZE - 1); + + uintptr_t root = (uintptr_t)alloc_page(); + *__riscv_init_load_kpgtable_root() = root + *__riscv_init_load_kaddr_offset(); + + for (unsigned int i = 0; i < PGSIZE / sizeof(uintptr_t); i++) { + ((uintptr_t *)root)[i] = 0; + } + + for (uintptr_t kaddr = (uintptr_t)__init_start; kaddr < (uintptr_t)__init_end; kaddr += PGSIZE) { + map(root, kaddr - kaddr_offset, kaddr - kaddr_offset, PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_A | PTE_D); + map(root, kaddr, kaddr - kaddr_offset, PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_A | PTE_D); + } + + for (uintptr_t kaddr = (uintptr_t)__text_start; kaddr < (uintptr_t)__text_end; kaddr += PGSIZE) { + map(root, kaddr, kaddr - kaddr_offset, PTE_V | PTE_R | PTE_X | PTE_G | PTE_A | PTE_D); + } + + memory_top = (memory_top + PGSIZE - 1) & ~(PGSIZE - 1); + for (uintptr_t paddr = (uintptr_t)__text_end - kaddr_offset; paddr < memory_top; paddr += PGSIZE) { + map(root, paddr + kaddr_offset, paddr, PTE_V | PTE_R | PTE_W | PTE_G | PTE_A | PTE_D); + } + + uintptr_t satp = (8ULL << 60) | get_ppn(root); + return satp; +} \ No newline at end of file diff --git a/clib/src/arch/riscv/swtich.S b/clib/src/arch/riscv/swtich.S new file mode 100644 index 0000000..dd2d6e7 --- /dev/null +++ b/clib/src/arch/riscv/swtich.S @@ -0,0 +1,41 @@ +.section .text +.global asm_kernel_switch + +asm_kernel_switch: + # save context + sd ra, 0(a0) + sd sp, 8(a0) + sd s0, 16(a0) + sd s1, 24(a0) + sd s2, 32(a0) + sd s3, 40(a0) + sd s4, 48(a0) + sd s5, 56(a0) + sd s6, 64(a0) + sd s7, 72(a0) + sd s8, 80(a0) + sd s9, 88(a0) + sd s10, 96(a0) + sd s11, 104(a0) + + # load context + ld ra, 0(a1) + ld sp, 8(a1) + ld s0, 16(a1) + ld s1, 24(a1) + ld s2, 32(a1) + ld s3, 40(a1) + ld s4, 48(a1) + ld s5, 56(a1) + ld s6, 64(a1) + ld s7, 72(a1) + ld s8, 80(a1) + ld s9, 88(a1) + ld s10, 96(a1) + ld s11, 104(a1) + + # load arg + ld a0, 112(a1) + + # jump to ra + jr ra diff --git a/clib/src/arch/riscv/trap/kerneltrap.S b/clib/src/arch/riscv/trap/kerneltrap.S new file mode 100644 index 0000000..b0c5f02 --- /dev/null +++ b/clib/src/arch/riscv/trap/kerneltrap.S @@ -0,0 +1,87 @@ +#if __riscv_xlen == 64 + #define LOAD(dest, n) ld dest, (n*8)(sp) + #define STORE(src, n) sd src , (n*8)(sp) +#else + #define LOAD(dest, n) lw dest, (n*4)(sp) + #define STORE(src, n) sw src , (n*4)(sp) +#endif + + .global asm_kerneltrap_entry + +.align 3 +asm_kerneltrap_entry: + csrw sscratch, sp + + la sp, __ktrap_temp_stack_end + + addi sp, sp, -32*__riscv_xlen/8 + + STORE(ra, 1) + STORE(gp, 3) + STORE(tp, 4) + STORE(t0, 5) + STORE(t1, 6) + STORE(t2, 7) + STORE(s0, 8) + STORE(s1, 9) + STORE(a0, 10) + STORE(a1, 11) + STORE(a2, 12) + STORE(a3, 13) + STORE(a4, 14) + STORE(a5, 15) + STORE(a6, 16) + STORE(a7, 17) + STORE(s2, 18) + STORE(s3, 19) + STORE(s4, 20) + STORE(s5, 21) + STORE(s6, 22) + STORE(s7, 23) + STORE(s8, 24) + STORE(s9, 25) + STORE(s10, 26) + STORE(s11, 27) + STORE(t3, 28) + STORE(t4, 29) + STORE(t5, 30) + STORE(t6, 31) + + call kerneltrap_handler + + LOAD(ra, 1) + LOAD(gp, 3) + LOAD(tp, 4) + LOAD(t0, 5) + LOAD(t1, 6) + LOAD(t2, 7) + LOAD(s0, 8) + LOAD(s1, 9) + LOAD(a0, 10) + LOAD(a1, 11) + LOAD(a2, 12) + LOAD(a3, 13) + LOAD(a4, 14) + LOAD(a5, 15) + LOAD(a6, 16) + LOAD(a7, 17) + LOAD(s2, 18) + LOAD(s3, 19) + LOAD(s4, 20) + LOAD(s5, 21) + LOAD(s6, 22) + LOAD(s7, 23) + LOAD(s8, 24) + LOAD(s9, 25) + LOAD(s10, 26) + LOAD(s11, 27) + LOAD(t3, 28) + LOAD(t4, 29) + LOAD(t5, 30) + LOAD(t6, 31) + + addi sp, sp, 32*__riscv_xlen/8 + + csrr sp, sscratch + + sret diff --git a/clib/src/arch/riscv/trap/usertrap.S b/clib/src/arch/riscv/trap/usertrap.S new file mode 100644 index 0000000..efaff0b --- /dev/null +++ b/clib/src/arch/riscv/trap/usertrap.S @@ -0,0 +1,122 @@ +#if __riscv_xlen == 64 + #define LOAD(dest, n) ld dest, (n*8)(a0) + #define STORE(src, n) sd src , (n*8)(a0) +#else + #define LOAD(dest, n) lw dest, (n*4)(a0) + #define STORE(src, n) sw src , (n*4)(a0) +#endif + + .section .trampoline.text + .global asm_usertrap_entry + .global asm_usertrap_return + +.align 3 +asm_usertrap_entry: + csrrw a0, sscratch, a0 + + STORE(ra, 1) + STORE(sp, 2) + STORE(gp, 3) + STORE(tp, 4) + STORE(t0, 5) + STORE(t1, 6) + STORE(t2, 7) + STORE(s0, 8) + STORE(s1, 9) + # STORE(a0, 10) # a0 is used to save the real a0, so we save it later + STORE(a1, 11) + STORE(a2, 12) + STORE(a3, 13) + STORE(a4, 14) + STORE(a5, 15) + STORE(a6, 16) + STORE(a7, 17) + STORE(s2, 18) + STORE(s3, 19) + STORE(s4, 20) + STORE(s5, 21) + STORE(s6, 22) + STORE(s7, 23) + STORE(s8, 24) + STORE(s9, 25) + STORE(s10, 26) + STORE(s11, 27) + STORE(t3, 28) + STORE(t4, 29) + STORE(t5, 30) + STORE(t6, 31) + + # save the real a0 + csrr t0, sscratch + STORE(t0, 10) + + # load the kernel tp + LOAD(tp, 32) + + # load the kernel stack pointer + # LOAD sp, 256(a0) + LOAD(sp, 33) + + # load the usertrap handler + # LOAD t0, 280(a0) + LOAD(t0, 36) + # load the TCB pointer + # LOAD t1, 288(a0) + + # load the kernel page table + # LOAD t2, 272(a0) + LOAD(t1, 35) + sfence.vma + csrw satp, t1 + sfence.vma + + # call the usertrap handler: + jr t0 + +asm_usertrap_return: + STORE(tp, 32) + + LOAD(ra, 1) + LOAD(sp, 2) + LOAD(gp, 3) + LOAD(tp, 4) + LOAD(t0, 5) + LOAD(t1, 6) + LOAD(t2, 7) + LOAD(s0, 8) + LOAD(s1, 9) + # LOAD(a0, 10) # a0 is used to save the real + # a0, so we restore it later + LOAD(a1, 11) + LOAD(a2, 12) + LOAD(a3, 13) + LOAD(a4, 14) + LOAD(a5, 15) + LOAD(a6, 16) + LOAD(a7, 17) + LOAD(s2, 18) + LOAD(s3, 19) + LOAD(s4, 20) + LOAD(s5, 21) + LOAD(s6, 22) + LOAD(s7, 23) + LOAD(s8, 24) + LOAD(s9, 25) + LOAD(s10, 26) + LOAD(s11, 27) + LOAD(t3, 28) + LOAD(t4, 29) + LOAD(t5, 30) + LOAD(t6, 31) + + # load the user page table + LOAD(a0, 34) + sfence.vma + csrw satp, a0 + sfence.vma + + # restore the a0 + csrr a0, sscratch + LOAD(a0, 10) + + sret diff --git a/clib/src/fs/ext4/create.c b/clib/src/fs/ext4/create.c new file mode 100644 index 0000000..a33fa60 --- /dev/null +++ b/clib/src/fs/ext4/create.c @@ -0,0 +1,39 @@ +// #include "ext4.h" +// #include "ext4_dir.h" +// #include "ext4_errno.h" +// #include "ext4_fs.h" +// #include "ext4_types.h" + +// #include + +// int kernelx_ext4_create_inode( +// struct ext4_inode_ref *parent, +// const char *name, +// uint32_t mode +// ) { +// struct ext4_fs *fs = parent->fs; +// struct ext4_inode_ref child_ref; +// int r; + +// r = ext4_fs_alloc_inode(fs, &child_ref, EXT4_DE_REG_FILE); +// if (r != EOK) { +// return -r; +// } + +// ext4_fs_inode_blocks_init(fs, &child_ref); + +// r = ext4_dir_add_entry(parent, name, strlen(name), &child_ref); +// if (r != EOK) { +// ext4_fs_free_inode(&child_ref); +// child_ref.dirty = false; +// ext4_fs_put_inode_ref(&child_ref); +// return -r; +// } + +// ext4_fs_inode_links_count_inc(&child_ref); +// child_ref.dirty = true; + +// ext4_fs_put_inode_ref(&child_ref); + +// return EOK; +// } diff --git a/clib/src/fs/ext4/dir.c b/clib/src/fs/ext4/dir.c new file mode 100644 index 0000000..38b39f5 --- /dev/null +++ b/clib/src/fs/ext4/dir.c @@ -0,0 +1,7 @@ +// int kernelx_ext4_inode_getdents( +// struct ext4_inode_ref *parent, +// void *dirp, +// size_t count +// ) { +// ext4_dir_ +// } \ No newline at end of file diff --git a/clib/src/fs/ext4/filesystem.c b/clib/src/fs/ext4/filesystem.c new file mode 100644 index 0000000..a4af1ba --- /dev/null +++ b/clib/src/fs/ext4/filesystem.c @@ -0,0 +1,189 @@ +// #include "ext4_bcache.h" +// #include "ext4_blockdev.h" +// #include "ext4_errno.h" +// #include "ext4_fs.h" +// #include "ext4_super.h" + +// #include +// #include +// #include +// #include +// #include + +// typedef struct BlockDevice { +// int (*open)(void *user); +// int (*bread)(void *user, void *buf, uint64_t block_id, uint32_t block_count); +// int (*bwrite)(void *user, const void *buf, uint64_t block_id, uint32_t block_count); +// int (*close)(void *user); +// void *user; +// } BlockDevice; + +// static int open(struct ext4_blockdev *bd) { +// BlockDevice *d = (BlockDevice *)bd->bdif->p_user; +// return d->open(d->user); +// } + +// static int bread(struct ext4_blockdev *bd, void *buf, uint64_t block, uint32_t count) { +// BlockDevice *d = (BlockDevice *)bd->bdif->p_user; +// return d->bread(d->user, buf, block, count); +// } + +// static int bwrite(struct ext4_blockdev *bd, const void *buf, uint64_t block, uint32_t count) { +// BlockDevice *d = (BlockDevice *)bd->bdif->p_user; +// return d->bwrite(d->user, buf, block, count); +// } + +// static int close(struct ext4_blockdev *bd) { +// BlockDevice *d = (BlockDevice *)bd->bdif->p_user; +// return d->close(d->user); +// } + +// static int lock(struct ext4_blockdev *bd) { +// // Implement locking logic if needed +// (void)bd; +// return EOK; +// } + +// static int unlock(struct ext4_blockdev *bd) { +// // Implement unlocking logic if needed +// (void)bd; +// return EOK; +// } + +// const size_t BLOCK_SIZE = 512; + +// int kernelx_ext4_create_filesystem( +// uint32_t block_size, +// uint64_t block_count, +// uintptr_t f_open, +// uintptr_t f_bread, +// uintptr_t f_bwrite, +// uintptr_t f_close, +// void *user, +// struct ext4_fs **return_fs +// ) { +// struct ext4_blockdev *bd = (struct ext4_blockdev *)malloc(sizeof(struct ext4_blockdev)); +// memset(bd, 0, sizeof(struct ext4_blockdev)); + +// BlockDevice *block_user = (BlockDevice *)malloc(sizeof(BlockDevice)); +// *block_user = (BlockDevice){ +// .open = (int (*)(void *))f_open, +// .bread = (int (*)(void *, void *, uint64_t, uint32_t))f_bread, +// .bwrite = (int (*)(void *, const void *, uint64_t, uint32_t))f_bwrite, +// .close = (int (*)(void *))f_close, +// .user = user +// }; + +// bd->bdif = (struct ext4_blockdev_iface *)malloc(sizeof(struct ext4_blockdev_iface)); +// *bd->bdif = (struct ext4_blockdev_iface){ +// .open = open, +// .bread = bread, +// .bwrite = bwrite, +// .close = close, +// .lock = lock, +// .unlock = unlock, +// .ph_bsize = block_size, +// .ph_bcnt = block_count, +// .ph_bbuf = malloc(block_size), +// .ph_refctr = 0, +// .bread_ctr = 0, +// .bwrite_ctr = 0, +// .p_user = block_user +// }; + +// bd->part_offset = 0; +// bd->part_size = block_count * block_size; + +// int r; +// r = ext4_block_init(bd); +// if (r != EOK) +// return -r; + +// struct ext4_fs *fs = (struct ext4_fs *)malloc(sizeof(struct ext4_fs)); +// memset(fs, 0, sizeof(struct ext4_fs)); + +// r = ext4_fs_init(fs, bd, false); +// if (r != EOK) { +// ext4_block_fini(bd); + +// free(bd->bdif->ph_bbuf); +// free(bd->bdif->p_user); +// free(bd->bdif); +// free(bd); + +// return -r; +// } + +// uint32_t bsize = ext4_sb_get_block_size(&fs->sb); +// ext4_block_set_lb_size(bd, bsize); + +// struct ext4_bcache *bc = (struct ext4_bcache *)malloc(sizeof(struct ext4_bcache)); +// memset(bc, 0, sizeof(struct ext4_bcache)); + +// r = ext4_bcache_init_dynamic(bc, CONFIG_BLOCK_DEV_CACHE_SIZE, bsize); +// if (r != EOK) { +// ext4_block_fini(bd); + +// free(bc); +// free(bd->bdif->ph_bbuf); +// free(bd->bdif->p_user); +// free(bd->bdif); +// free(bd); +// free(fs); + +// return -r; +// } + +// if (bsize != bc->itemsize) +// return -ENOTSUP; + +// /*Bind block cache to block device*/ +// r = ext4_block_bind_bcache(bd, bc); +// if (r != EOK) { +// ext4_bcache_cleanup(bc); +// ext4_block_fini(bd); +// ext4_bcache_fini_dynamic(bc); + +// free(bc); +// free(bd->bdif->ph_bbuf); +// free(bd->bdif->p_user); +// free(bd->bdif); +// free(bd); +// free(fs); + +// return -r; +// } + +// bd->fs = fs; + +// *return_fs = fs; + +// return EOK; +// } + +// int kernelx_ext4_destroy_filesystem( +// struct ext4_fs *fs +// ) { +// if (!fs) +// return EINVAL; + +// int r = ext4_fs_fini(fs); +// if (r != EOK) +// return -r; + +// ext4_bcache_cleanup(fs->bdev->bc); +// ext4_bcache_fini_dynamic(fs->bdev->bc); + +// r = ext4_block_fini(fs->bdev); +// if (r != EOK) +// return -r; + +// free(fs->bdev->bdif->ph_bbuf); +// free(fs->bdev->bdif->p_user); +// free(fs->bdev->bdif); +// free(fs->bdev); + +// free(fs); + +// return EOK; +// } diff --git a/clib/src/fs/ext4/fstat.c b/clib/src/fs/ext4/fstat.c new file mode 100644 index 0000000..2e75516 --- /dev/null +++ b/clib/src/fs/ext4/fstat.c @@ -0,0 +1,15 @@ +// #include "ext4_inode.h" +// #include "ext4_fs.h" + +// #include +// #include +// #include + +// ssize_t kernelx_ext4_get_inode_size(struct ext4_inode_ref *inode_ref) { +// struct ext4_fs *const fs = inode_ref->fs; +// struct ext4_sblock *const sb = &fs->sb; + +// ssize_t size = ext4_inode_get_size(sb, inode_ref->inode); + +// return size; +// } diff --git a/clib/src/fs/ext4/inode.c b/clib/src/fs/ext4/inode.c new file mode 100644 index 0000000..3a29614 --- /dev/null +++ b/clib/src/fs/ext4/inode.c @@ -0,0 +1,68 @@ +// #include "ext4_blockdev.h" +// #include "ext4_errno.h" +// #include "ext4_fs.h" +// #include "ext4_dir.h" + +// #include +// #include +// #include +// #include +// #include + +// int kernelx_ext4_get_inode( +// struct ext4_fs *fs, +// uint32_t ino, +// struct ext4_inode_ref **ret_inode +// ) { +// struct ext4_inode_ref *inode_ref = (struct ext4_inode_ref *)malloc(sizeof(struct ext4_inode_ref)); +// if (inode_ref == NULL) { +// return -ENOMEM; +// } + +// int rc = ext4_fs_get_inode_ref(fs, ino, inode_ref); +// if (rc != EOK) { +// free(inode_ref); +// return -rc; +// } + +// *ret_inode = inode_ref; + +// return rc; +// } + +// int kernelx_ext4_put_inode( +// struct ext4_inode_ref *inode_ref +// ) { +// int rc = ext4_fs_put_inode_ref(inode_ref); +// if (rc != EOK) { +// return -rc; +// } + +// free(inode_ref); +// return EOK; +// } + +// int kernelx_ext4_inode_lookup( +// struct ext4_inode_ref *inode, +// const char *name, +// uint32_t *ret_ino +// ) { +// struct ext4_dir_search_result result; +// int rc; + +// ext4_block_cache_write_back(inode->fs->bdev, 1); + +// rc = ext4_dir_find_entry(&result, inode, name, strlen(name)); +// if (rc != EOK) { +// ext4_dir_destroy_result(inode, &result); +// return -rc; +// } + +// *ret_ino = ext4_dir_en_get_inode(result.dentry); + +// ext4_dir_destroy_result(inode, &result); + +// ext4_block_cache_write_back(inode->fs->bdev, 0); + +// return EOK; +// } \ No newline at end of file diff --git a/clib/src/fs/ext4/mkdir.c b/clib/src/fs/ext4/mkdir.c new file mode 100644 index 0000000..3eefc0e --- /dev/null +++ b/clib/src/fs/ext4/mkdir.c @@ -0,0 +1,75 @@ +// #include "ext4.h" +// #include "ext4_dir.h" +// #include "ext4_dir_idx.h" +// #include "ext4_fs.h" +// #include "ext4_inode.h" + +// #include + +// int kernelx_ext4_inode_mkdir( +// struct ext4_inode_ref *parent, +// const char *name, +// uint32_t mode +// ) { +// struct ext4_fs *fs = parent->fs; +// struct ext4_inode_ref child_ref; +// int r; + +// struct ext4_dir_search_result result; +// r = ext4_dir_find_entry(&result, parent, name, strlen(name)); +// if (r == EOK) { +// ext4_dir_destroy_result(parent, &result); +// return EEXIST; +// } else if (r != ENOENT) { +// return r; +// } + +// r = ext4_fs_alloc_inode(fs, &child_ref, EXT4_DE_DIR); +// if (r != EOK) { +// return r; +// } + +// ext4_fs_inode_blocks_init(fs, &child_ref); + +// r = ext4_dir_add_entry(parent, name, strlen(name), &child_ref); +// if (r != EOK) { +// ext4_fs_free_inode(&child_ref); +// return r; +// } + +// #if CONFIG_DIR_INDEX_ENABLE +// /* Initialize directory index if supported */ +// if (ext4_sb_feature_com(&fs->sb, EXT4_FCOM_DIR_INDEX)) { +// r = ext4_dir_dx_init(&child_ref, parent); +// if (r != EOK) +// return r; + +// ext4_inode_set_flag(child_ref.inode, EXT4_INODE_FLAG_INDEX); +// child_ref.dirty = true; +// } else +// #endif +// { +// r = ext4_dir_add_entry(&child_ref, ".", strlen("."), &child_ref); +// if (r != EOK) { +// ext4_dir_remove_entry(parent, name, strlen(name)); +// return r; +// } + +// r = ext4_dir_add_entry(&child_ref, "..", strlen(".."), parent); +// if (r != EOK) { +// ext4_dir_remove_entry(parent, name, strlen(name)); +// ext4_dir_remove_entry(&child_ref, ".", strlen(".")); +// return r; +// } +// } + +// /*New empty directory. Two links (. and ..) */ +// ext4_inode_set_links_cnt(child_ref.inode, 2); +// ext4_fs_inode_links_count_inc(parent); +// child_ref.dirty = true; +// parent->dirty = true; + +// ext4_fs_put_inode_ref(&child_ref); + +// return r; +// } diff --git a/clib/src/fs/ext4/read.c b/clib/src/fs/ext4/read.c new file mode 100644 index 0000000..289accd --- /dev/null +++ b/clib/src/fs/ext4/read.c @@ -0,0 +1,198 @@ +// #include "ext4.h" +// #include "ext4_super.h" +// #include "ext4_fs.h" +// #include "ext4_inode.h" +// #include "ext4_errno.h" + +// #include +// #include + +// static size_t min(size_t a, size_t b) { +// return (a < b) ? a : b; +// } + +// ssize_t kernelx_ext4_inode_readat( +// struct ext4_inode_ref *inode, +// void *buf, +// size_t size, +// size_t offset +// ) { +// size_t inode_size; +// uint32_t unalg; +// uint32_t iblock_idx; +// uint32_t iblock_last; +// uint32_t block_size; + +// ext4_fsblk_t fblock; +// ext4_fsblk_t fblock_start; +// uint32_t fblock_count; + +// uint8_t *u8_buf = buf; +// int r; +// // struct ext4_inode_ref ref; + +// size_t cnt = 0; + +// if (size == 0) return 0; + +// struct ext4_fs *const fs = inode->fs; +// struct ext4_sblock *const sb = &fs->sb; + +// inode_size = ext4_inode_get_size(sb, inode->inode); + +// block_size = ext4_sb_get_block_size(sb); +// // size = (size > inode_size - offset) +// // ? ((size_t)(inode_size - offset)) : size; +// size = min(size, inode_size - offset); + +// // iblock_idx = (uint32_t)((file->fpos) / block_size); +// // iblock_last = (uint32_t)((file->fpos + size) / block_size); +// // unalg = (file->fpos) % block_size; +// iblock_idx = (uint32_t)(offset / block_size); +// iblock_last = (uint32_t)((offset + size) / block_size); +// unalg = offset % block_size; + +// /*If the size of symlink is smaller than 60 bytes*/ +// bool softlink; +// // softlink = ext4_inode_is_type(sb, ref.inode, EXT4_INODE_MODE_SOFTLINK); +// softlink = ext4_inode_is_type(&fs->sb, inode->inode, EXT4_INODE_MODE_SOFTLINK); +// // if (softlink && file->fsize < sizeof(ref.inode->blocks) +// // && !ext4_inode_get_blocks_count(sb, ref.inode)) { +// if (softlink && inode_size < sizeof(inode->inode->blocks) +// && !ext4_inode_get_blocks_count(sb, inode->inode)) { + +// char *content = (char *)inode->inode->blocks; +// // if (file->fpos < file->fsize) { +// // size_t len = size; +// // if (unalg + size > (uint32_t)file->fsize) +// // len = (uint32_t)file->fsize - unalg; +// // memcpy(buf, content + unalg, len); +// // if (rcnt) +// // *rcnt = len; + +// // } +// if (offset < inode_size) { +// size_t len = size; +// if (unalg + size > inode_size) +// len = inode_size - unalg; +// memcpy(buf, content + unalg, len); +// cnt = len; +// } + +// // r = EOK; +// return cnt; +// // goto Finish; +// } + +// if (unalg) { +// size_t len = size; +// if (size > (block_size - unalg)) +// len = block_size - unalg; + +// // r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); +// r = ext4_fs_get_inode_dblk_idx(inode, iblock_idx, &fblock, true); +// if (r != EOK) { +// return -r; +// // goto Finish; +// } + +// /* Do we get an unwritten range? */ +// if (fblock != 0) { +// uint64_t off = fblock * block_size + unalg; +// // r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, len); +// r = ext4_block_readbytes(fs->bdev, off, u8_buf, len); +// if (r != EOK) { +// // goto Finish; +// return -r; +// } +// } else { +// /* Yes, we do. */ +// memset(u8_buf, 0, len); +// } + +// u8_buf += len; +// size -= len; +// // file->fpos += len; + +// // if (rcnt) +// // *rcnt += len; +// cnt += len; + +// iblock_idx++; +// } + +// fblock_start = 0; +// fblock_count = 0; +// while (size >= block_size) { +// while (iblock_idx < iblock_last) { +// // r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, +// // &fblock, true); +// r = ext4_fs_get_inode_dblk_idx(inode, iblock_idx, &fblock, true); +// if (r != EOK) { +// return -r; +// // goto Finish; +// } + +// iblock_idx++; + +// if (!fblock_start) +// fblock_start = fblock; + +// if ((fblock_start + fblock_count) != fblock) +// break; + +// fblock_count++; +// } + +// // r = ext4_blocks_get_direct(file->mp->fs.bdev, u8_buf, fblock_start, +// // fblock_count); +// r = ext4_blocks_get_direct(fs->bdev, u8_buf, fblock_start, +// fblock_count); +// if (r != EOK) { +// return -r; +// // goto Finish; +// } + +// size -= block_size * fblock_count; +// u8_buf += block_size * fblock_count; +// // file->fpos += block_size * fblock_count; + +// // if (rcnt) +// // *rcnt += block_size * fblock_count; +// cnt += block_size * fblock_count; + +// fblock_start = fblock; +// fblock_count = 1; +// } + +// if (size) { +// uint64_t off; +// // r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); +// r = ext4_fs_get_inode_dblk_idx(inode, iblock_idx, &fblock, true); +// if (r != EOK) { +// return -r; +// // goto Finish; +// } + +// off = fblock * block_size; +// // r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, size); +// r = ext4_block_readbytes(fs->bdev, off, u8_buf, size); +// if (r != EOK) { +// return -r; +// // goto Finish; +// } + +// // file->fpos += size; + +// // if (rcnt) +// // *rcnt += size; +// cnt += size; +// } + +// return cnt; + +// // Finish: +// // ext4_fs_put_inode_ref(&ref); +// // EXT4_MP_UNLOCK(file->mp); +// // return r; +// } \ No newline at end of file diff --git a/clib/src/fs/ext4/write.c b/clib/src/fs/ext4/write.c new file mode 100644 index 0000000..5ac8fe4 --- /dev/null +++ b/clib/src/fs/ext4/write.c @@ -0,0 +1,195 @@ +// #include "ext4.h" +// #include "ext4_blockdev.h" +// #include "ext4_super.h" +// #include "ext4_fs.h" +// #include "ext4_inode.h" +// #include "ext4_errno.h" + +// #include +// #include +// #include + +// // reference: ext4_fwrite +// ssize_t kernelx_ext4_inode_writeat( +// struct ext4_inode_ref *inode_ref, +// const void *buf, +// size_t size, +// size_t fpos +// ) { +// // ext4_fwrite(NULL, NULL, 0, NULL); +// uint32_t unalg; +// uint32_t iblk_idx; +// uint32_t iblock_last; +// uint32_t ifile_blocks; +// uint32_t block_size; +// uint64_t file_fsize; + +// uint32_t fblock_count; +// ext4_fsblk_t fblk; +// ext4_fsblk_t fblock_start; + +// struct ext4_fs *const fs = inode_ref->fs; +// struct ext4_sblock *const sb = &fs->sb; +// const uint8_t *u8_buf = buf; +// int r; +// ssize_t wcnt = 0; + +// file_fsize = ext4_inode_get_size(sb, inode_ref->inode); +// block_size = ext4_sb_get_block_size(sb); + +// // iblock_last = (uint32_t)((file->fpos + size) / block_size); +// // iblk_idx = (uint32_t)(file->fpos / block_size); +// // ifile_blocks = (uint32_t)((file->fsize + block_size - 1) / block_size); +// iblock_last = (uint32_t)((fpos + size) / block_size); +// iblk_idx = (uint32_t)(fpos / block_size); +// ifile_blocks = (uint32_t)((file_fsize + block_size - 1) / block_size); + +// // unalg = (file->fpos) % block_size; +// unalg = fpos % block_size; + +// if (unalg) { +// size_t len = size; +// uint64_t off; + +// if (size > (block_size - unalg)) +// len = block_size - unalg; + +// r = ext4_fs_init_inode_dblk_idx(inode_ref, iblk_idx, &fblk); +// if (r != EOK) +// return -r; + +// off = fblk * block_size + unalg; +// r = ext4_block_writebytes(fs->bdev, off, u8_buf, len); +// if (r != EOK) +// return -r; + +// u8_buf += len; +// size -= len; +// fpos += len; +// wcnt += len; + +// iblk_idx++; +// } + +// /*Start write back cache mode.*/ +// r = ext4_block_cache_write_back(fs->bdev, 1); +// if (r != EOK) +// return -r; + +// fblock_start = 0; +// fblock_count = 0; +// while (size >= block_size) { +// int rr; +// while (iblk_idx < iblock_last) { +// if (iblk_idx < ifile_blocks) { +// r = ext4_fs_init_inode_dblk_idx(inode_ref, iblk_idx, +// &fblk); +// if (r != EOK) { +// return -r; +// } +// } else { +// rr = ext4_fs_append_inode_dblk(inode_ref, &fblk, +// &iblk_idx); +// if (rr != EOK) { +// /* Unable to append more blocks. But +// * some block might be allocated already +// * */ +// break; +// } +// } + +// iblk_idx++; + +// if (!fblock_start) { +// fblock_start = fblk; +// } + +// if ((fblock_start + fblock_count) != fblk) +// break; + +// fblock_count++; +// } + +// // r = ext4_blocks_set_direct(file->mp->fs.bdev, u8_buf, fblock_start, +// // fblock_count); +// r = ext4_blocks_set_direct(fs->bdev, u8_buf, fblock_start, +// fblock_count); +// if (r != EOK) +// break; + +// size -= block_size * fblock_count; +// u8_buf += block_size * fblock_count; +// // file->fpos += block_size * fblock_count; +// fpos += block_size * fblock_count; + +// // if (wcnt) +// // *wcnt += block_size * fblock_count; +// wcnt += block_size * fblock_count; + +// fblock_start = fblk; +// fblock_count = 1; + +// if (rr != EOK) { +// /*ext4_fs_append_inode_block has failed and no +// * more blocks might be written. But node size +// * should be updated.*/ +// r = rr; +// goto out_fsize; +// } +// } + +// /*Stop write back cache mode*/ +// // ext4_block_cache_write_back(file->mp->fs.bdev, 0); +// ext4_block_cache_write_back(fs->bdev, 0); + +// if (r != EOK) { +// return -r; +// } + +// if (size) { +// uint64_t off; +// if (iblk_idx < ifile_blocks) { +// // r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); +// // if (r != EOK) +// // goto Finish; +// r = ext4_fs_init_inode_dblk_idx(inode_ref, iblk_idx, &fblk); +// if (r != EOK) +// return -r; +// } else { +// // r = ext4_fs_append_inode_dblk(&ref, &fblk, &iblk_idx); +// r = ext4_fs_append_inode_dblk(inode_ref, &fblk, &iblk_idx); +// if (r != EOK) +// /*Node size sholud be updated.*/ +// goto out_fsize; +// } + +// off = fblk * block_size; +// // r = ext4_block_writebytes(file->mp->fs.bdev, off, u8_buf, size); +// // if (r != EOK) +// // goto Finish; +// r = ext4_block_writebytes(fs->bdev, off, u8_buf, size); +// if (r != EOK) +// return -r; + +// // file->fpos += size; +// fpos += size; + +// // if (wcnt) +// // *wcnt += size; +// wcnt += size; +// } + +// out_fsize: +// // if (file->fpos > file->fsize) { +// // file->fsize = file->fpos; +// // ext4_inode_set_size(ref.inode, file->fsize); +// // ref.dirty = true; +// // } +// if (fpos > file_fsize) { +// file_fsize = fpos; +// ext4_inode_set_size(inode_ref->inode, file_fsize); +// inode_ref->dirty = true; +// } + +// return wcnt; +// } \ No newline at end of file diff --git a/clib/src/klib/malloc.c b/clib/src/klib/malloc.c new file mode 100644 index 0000000..0d8e55d --- /dev/null +++ b/clib/src/klib/malloc.c @@ -0,0 +1,33 @@ +#include "tlsf.h" +#include +#include + +static tlsf_t tlsf = NULL; + +void init_heap(void *start, size_t size) { + tlsf = tlsf_create_with_pool(start, size); +} + +void *malloc(size_t size) { + return tlsf_malloc(tlsf, size); +} + +void *malloc_aligned(size_t align, size_t size) { + if (align <= 8) [[clang::likely]] { + return tlsf_malloc(tlsf, size); + } else { + return tlsf_memalign(tlsf, align, size); + } +} + +void *calloc(size_t count, size_t size) { + void *ptr = tlsf_malloc(tlsf, count * size); + if (ptr) { + memset(ptr, 0, count * size); + } + return ptr; +} + +void free(void* ptr) { + tlsf_free(tlsf, ptr); +} diff --git a/clib/src/klib/stdlib.c b/clib/src/klib/stdlib.c new file mode 100644 index 0000000..9730a4b --- /dev/null +++ b/clib/src/klib/stdlib.c @@ -0,0 +1,43 @@ +#include +#include + +static void swap(char *a, char *b, size_t size) { + while (size--) { + char tmp = *a; + *a++ = *b; + *b++ = tmp; + } +} + +static void _qsort_inner(char *base, size_t left, size_t right, size_t size, + int (*compar)(const void *, const void *)) { + if (left >= right) + return; + + size_t i = left, j = right; + char *pivot = base + ((left + right) / 2) * size; + + while (i <= j) { + while (compar(base + i * size, pivot) < 0) i++; + while (compar(base + j * size, pivot) > 0) j--; + + if (i <= j) { + swap(base + i * size, base + j * size, size); + i++; + if (j > 0) j--; + } + } + + if (j > left) + _qsort_inner(base, left, j, size, compar); + if (i < right) + _qsort_inner(base, i, right, size, compar); +} + +void qsort(void *base, size_t n, size_t size, + int (*compar)(const void *, const void *)) { + if (n <= 1) + return; + + _qsort_inner((char *)base, 0, n - 1, size, compar); +} \ No newline at end of file diff --git a/clib/src/klib/string.c b/clib/src/klib/string.c new file mode 100644 index 0000000..e5379a1 --- /dev/null +++ b/clib/src/klib/string.c @@ -0,0 +1,53 @@ +#include + +int strcmp(const char *s1, const char *s2) { + while (*s1 && (*s1 == *s2)) { + s1++; + s2++; + } + return *(unsigned char *)s1 - *(unsigned char *)s2; +} + +int strncmp(const char *s1, const char *s2, size_t n) { + while (n && *s1 && (*s1 == *s2)) { + s1++; + s2++; + n--; + } + return n ? *(unsigned char *)s1 - *(unsigned char *)s2 : 0; +} + +char *strcpy(char *dest, const char *src) { + char *d = dest; + while ((*d++ = *src++)); + return dest; +} + +size_t strnlen(const char *s, size_t maxlen) { + size_t l = 0; + while (l < maxlen && s[l]) + ++l; + return l; +} + +char *strrchr(const char *s, int ch) { + char c = ch; + const char *ret = NULL; + do { + if(*s == c) + ret = s; + } while(*s++); + return (char *) ret; +} + +void *memchr(const void *ptr, int ch, size_t n) { + const char *p = ptr; + char c = ch; + while (n--) { + if (*p != c) + ++p; + else + return (void *) p; + } + return NULL; +} diff --git a/clib/src/platform/qemu-virt-riscv64/boot.S b/clib/src/platform/qemu-virt-riscv64/boot.S new file mode 100644 index 0000000..ee281d2 --- /dev/null +++ b/clib/src/platform/qemu-virt-riscv64/boot.S @@ -0,0 +1,30 @@ +# .section .rodata.bootloader +# .align 12 +# pagetable: +# .dword 0, 0 +# .dword (0x80000 << 10) | 0xcf // 2: VA 0x8000_0000 ~ 0xc000_0000 -> PA 0x8000_0000 ~ 0xc000_0000 +# .rept 258 - 2 - 1 +# .dword 0 +# .endr +# .dword (0x80000 << 10) | 0xcf // 258: VA 0xffff_ffc0_8000_0000 ~ 0xffff_ffc0_c000_0000 to 0x8000_0000 ~ 0xc000_0000 +# .rept 512 - 258 - 1 +# .dword 0 +# .endr + +# #define PAGETABLE_ADDR 0x80201000 +# #define ENTRY_ADDR 0xffffffc080205000 + +# .global bootloader +# .section .text.bootloader +# .align 12 +# bootloader: +# li t0, (PAGETABLE_ADDR >> 12) | (8 << 60) + +# sfence.vma +# csrw satp, t0 +# sfence.vma + +# li tp, 0 + +# li t0, ENTRY_ADDR +# jr t0 diff --git a/clib/src/platform/starfive-jh7110-riscv64/boot.S b/clib/src/platform/starfive-jh7110-riscv64/boot.S new file mode 100644 index 0000000..1849e8b --- /dev/null +++ b/clib/src/platform/starfive-jh7110-riscv64/boot.S @@ -0,0 +1,37 @@ +#define PTE_FLAG 0xcf + + .section .rodata.bootloader + .align 12 +pagetable: + .dword 0 + .dword (0x040000 << 10) | PTE_FLAG // 001: VA 0x4000_0000 ~ 0x8000_0000 -> PA 0x4000_0000 ~ 0x8000_4000 + .dword (0x080000 << 10) | PTE_FLAG // 002: VA 0x8000_0000 ~ 0x0c0000000 -> PA 0x8000_0000 ~ 0x0c0004000 + .dword (0x0c0000 << 10) | PTE_FLAG // 003: VA 0xc000_0000 ~ 0x100000000 -> PA 0xc000_0000 ~ 0x1000004000 + .dword (0x100000 << 10) | PTE_FLAG // 004: VA 0x1000_0000 ~ 0x140000000 -> PA 0x1000_0000 ~ 0x1400004000 + .rept 257 - 4 - 1 + .dword 0 + .endr + .dword (0x040000 << 10) | PTE_FLAG // 257: VA 0xffff_ffc0_4000_0000 ~ 0xffff_fff0_8000_0000 to 0x040000000 ~ 0x080000000 + .dword (0x080000 << 10) | PTE_FLAG // 258: VA 0xffff_ffc0_8000_0000 ~ 0xffff_fff0_c000_0000 to 0x080000000 ~ 0x0c0000000 + .dword (0x0c0000 << 10) | PTE_FLAG // 259: VA 0xffff_ffc0_c000_0000 ~ 0xffff_fff1_0000_0000 to 0x0c0000000 ~ 0x100000000 + .dword (0x100000 << 10) | PTE_FLAG // 260: VA 0xffff_ffc1_0000_0000 ~ 0xffff_fff1_4000_0000 to 0x100000000 ~ 0x140000000 + .rept 512 - 260 - 1 + .dword 0 + .endr + +#define PAGETABLE_ADDR (0x40080000 + 0x1000) +#define ENTRY_ADDR (0xffffffc040080000 + 0x5000) + + .global bootloader + + .section .text.bootloader + .align 2 +bootloader: + li t0, (PAGETABLE_ADDR >> 12) | (8 << 60) + + sfence.vma + csrw satp, t0 + sfence.vma + + li t0, ENTRY_ADDR + jr t0 diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..456ad11 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,6 @@ +* +*.* + +!.gitignore +!.config.mk +!Kconfig diff --git a/config/Kconfig b/config/Kconfig new file mode 100644 index 0000000..4801ba9 --- /dev/null +++ b/config/Kconfig @@ -0,0 +1,203 @@ +# +# KernelX Configuration +# + +mainmenu "KernelX Kernel Configuration" + +config KERNELX_VERSION + string + default "5.0" + +menu "Platform Configuration" + +choice + prompt "Target Architecture" + default RISCV64 + help + Select the target architecture for KernelX kernel. + +config RISCV64 + bool "RISC-V 64-bit" + help + Support for RISC-V 64-bit architecture. + +endchoice + +config ARCH + string + default "riscv" if RISCV64 + help + Target architecture for the kernel. + Automatically selected based on the chosen platform. + +config ARCH_BITS + int + default 64 if RISCV64 + help + Architecture bit width (32 or 64). + Automatically selected based on the chosen platform. + +config CROSS_COMPILE + string "Cross-compiler prefix" + default "riscv64-unknown-elf-" + help + Prefix for cross-compilation tools (e.g., riscv64-unknown-elf-). + +config RUST_TARGET + string + default "riscv64gc-unknown-none-elf" + help + Rust target specification for compilation. + +endmenu + +menu "Build Configuration" + +choice + prompt "Compilation Mode" + default COMPILE_MODE_DEBUG + help + Select the compilation mode for the kernel. + +config COMPILE_MODE_DEBUG + bool "Debug" + help + Build kernel with debug information and no optimizations. + Suitable for development and debugging. + +config COMPILE_MODE_RELEASE + bool "Release" + help + Build kernel with optimizations enabled. + Suitable for production use. + +endchoice + +config COMPILE_MODE + string + default "debug" if COMPILE_MODE_DEBUG + default "release" if COMPILE_MODE_RELEASE + +endmenu + +menu "Debug Configuration" + +choice + prompt "Log Level" + default LOG_LEVEL_TRACE + help + Select the maximum log level for kernel messages. + +config LOG_LEVEL_TRACE + bool "Trace" + help + Enable all log messages including trace level. + Most verbose logging option. + +config LOG_LEVEL_DEBUG + bool "Debug" + help + Enable debug, info, warn and error messages. + +config LOG_LEVEL_INFO + bool "Info" + help + Enable info, warn and error messages. + +config LOG_LEVEL_WARN + bool "Warn" + help + Enable warn and error messages only. + +config LOG_LEVEL_SYSCALL + bool "Syscall Trace" + help + Enable trace level logging with syscall tracing. + Very verbose, includes all system call traces. + +endchoice + +config LOG_LEVEL + string + default "trace" if LOG_LEVEL_TRACE + default "debug" if LOG_LEVEL_DEBUG + default "info" if LOG_LEVEL_INFO + default "warn" if LOG_LEVEL_WARN + +config LOG_SYSCALL + bool "Enable Syscall Tracing" + default n + help + Enable detailed tracing of all system calls. + +config WARN_UNIMPLEMENTED_SYSCALL + bool "Warn on Unimplemented Syscalls" + default y + help + Enable warnings for unimplemented syscalls. + +endmenu + +menu "Experimental Features" + +config ENABLE_SWAP_MEMORY + bool "Enable Swap Memory" + default n + help + Enable swap memory support in the kernel. + This feature allows the kernel to swap memory pages to disk when physical memory is low. + +endmenu + +menu "QEMU Configuration" + +config QEMU_MACHINE + string "QEMU machine type" + default "virt" + help + QEMU machine type for virtualization. + +config QEMU_MEMORY + string "QEMU memory size" + default "256M" + help + Amount of memory to allocate for QEMU virtual machine. + +config QEMU_CPUS + int "Number of CPUs" + range 1 8 + default 1 + help + Number of CPU cores for QEMU virtual machine. + +config DISK_IMAGE + string "Disk image path" + default "./sdcard-rv.img" + help + Path to the disk image file used as storage. + +config INITPATH + string "Init process path" + default "/init" + help + Path to the init process executable. + +config INITCWD + string "Init process working directory" + default "/" + help + Working directory for the init process. + +config ROOT_FS_TYPE + string "Root filesystem type" + default "ext4" + help + Filesystem type for the root filesystem. + +config ROOT_DEVICE + string "Root device name" + default "virtio_block0" + help + Device path for the root filesystem. + +endmenu diff --git a/config/config.mk b/config/config.mk new file mode 100644 index 0000000..b9e8d64 --- /dev/null +++ b/config/config.mk @@ -0,0 +1,56 @@ +# Include configuration if it exists +CONFIG_FILE := config/.config +-include $(CONFIG_FILE) + +ARCH = $(CONFIG_ARCH) +ARCH_BITS = $(CONFIG_ARCH_BITS) + +COMPILE_MODE ?= $(CONFIG_COMPILE_MODE) +COMPILE_MODE ?= debug + +KERNELX_RELEASE ?= $(CONFIG_KERNELX_RELEASE) +KERNELX_RELEASE ?= 5.0 + +# Default values with Kconfig support +INITPATH ?= $(CONFIG_INITPATH) +INITPATH ?= /init + +INITCWD ?= $(CONFIG_INITCWD) +INITCWD ?= / + +PLATFORM ?= $(CONFIG_PLATFORM) +PLATFORM ?= qemu-virt-riscv64 + +CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE) +CROSS_COMPILE ?= riscv64-unknown-elf- + +# Log level control: trace, debug, info, warn, none +LOG_LEVEL ?= $(CONFIG_LOG_LEVEL) +LOG_LEVEL ?= trace + +BIOS_FIRMWARE ?= $(CONFIG_BIOS_FIRMWARE) +BIOS_FIRMWARE ?= ./lib/opensbi/build/platform/generic/firmware/fw_jump.bin + +KERNEL_CONFIG = \ + PLATFORM=$(PLATFORM) \ + ARCH=$(ARCH) \ + ARCH_BITS=$(ARCH_BITS) \ + CROSS_COMPILE=$(CROSS_COMPILE) \ + KERNELX_RELEASE=$(KERNELX_RELEASE) \ + CONFIG_LOG_LEVEL=$(LOG_LEVEL) \ + CONFIG_LOG_SYSCALL=$(CONFIG_LOG_SYSCALL) \ + CONFIG_WARN_UNIMPLEMENTED_SYSCALL=$(CONFIG_WARN_UNIMPLEMENTED_SYSCALL) \ + CONFIG_ENABLE_SWAP_MEMORY=$(CONFIG_ENABLE_SWAP_MEMORY) \ + COMPILE_MODE=$(COMPILE_MODE) + +# Configuration targets +menuconfig: + @if command -v kconfig-mconf >/dev/null 2>&1; then \ + KCONFIG_CONFIG=config/.config kconfig-mconf config/Kconfig; \ + elif command -v menuconfig >/dev/null 2>&1; then \ + KCONFIG_CONFIG=config/.config menuconfig config/Kconfig; \ + else \ + echo "Error: menuconfig not found. Please install kconfig-frontends:"; \ + echo " Ubuntu/Debian: sudo apt-get install kconfig-frontends"; \ + exit 1; \ + fi diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/ext4_rs/.gitignore b/lib/ext4_rs/.gitignore new file mode 100644 index 0000000..66508f8 --- /dev/null +++ b/lib/ext4_rs/.gitignore @@ -0,0 +1,4 @@ +/target +*.img +test_files* +tmp \ No newline at end of file diff --git a/lib/ext4_rs/Cargo.lock b/lib/ext4_rs/Cargo.lock new file mode 100644 index 0000000..3c84060 --- /dev/null +++ b/lib/ext4_rs/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "ext4_rs" +version = "1.3.2" +dependencies = [ + "bitflags", + "log", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" diff --git a/lib/ext4_rs/Cargo.toml b/lib/ext4_rs/Cargo.toml new file mode 100644 index 0000000..586ed88 --- /dev/null +++ b/lib/ext4_rs/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ext4_rs" +version = "1.3.2" +edition = "2021" +description = "Cross-platform rust ext4." +authors = ["yuoo655 "] +license = "MIT" +repository = "https://github.com/yuoo655/ext4_rs" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "2.2.1" +log = "0.4" \ No newline at end of file diff --git a/lib/ext4_rs/LICENSE b/lib/ext4_rs/LICENSE new file mode 100644 index 0000000..88c972d --- /dev/null +++ b/lib/ext4_rs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 yuoo655 (lenuulan@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/ext4_rs/README.json b/lib/ext4_rs/README.json new file mode 100644 index 0000000..f1be6fc --- /dev/null +++ b/lib/ext4_rs/README.json @@ -0,0 +1,11 @@ +{ + "name": "ext4_rs", + "description": "This is a cross-platform ext4 file system", + "authors": [{ + "name": "yuoo655", + "email": "lenuulan@gmail.com" + }], + "keywords": ["ext4", "file system","fuse"], + "repo": "yuoo655/ext4_rs", + "doc_url": "https://github.com/yuoo655/ext4_rs/blob/refactor/README.md" + } diff --git a/lib/ext4_rs/README.md b/lib/ext4_rs/README.md new file mode 100644 index 0000000..e1e67cf --- /dev/null +++ b/lib/ext4_rs/README.md @@ -0,0 +1,172 @@ +# An os independent rust ext4 file system + +[![Crates.io Version](https://img.shields.io/crates/v/ext4_rs)](https://crates.io/crates/ext4_rs) +[![Crates.io License](https://img.shields.io/crates/l/ext4_rs)](LICENSE) +[![docs.rs](https://img.shields.io/docsrs/ext4_rs)](https://docs.rs/ext4_rs) + +## env +wsl2 ubuntu22.04 + +rust version nightly-2024-06-01 + +rustc 1.80.0-nightly (ada5e2c7b 2024-05-31) + +mkfs.ext4 1.46.5 (30-Dec-2021) + +For small images, the newer mkfs.ext4 uses a 512-byte block size. Use **mkfs.ext4 -b 4096** to set a 4096-byte block size. + +## run example +```sh +git clone https://github.com/yuoo655/ext4_rs.git +sh run.sh +``` +## fuse example +``` +git clone https://github.com/yuoo655/ext4libtest.git +cd ext4libtest +sh gen_img.sh +# cargo run /path/to/mountpoint +cargo run ./foo/ +``` +# features + +| 操作 |支持情况| +|--------------|------| +| mount | ✅ | +| open | ✅ | +| close | ✅ | +| lsdir | ✅ | +| mkdir | ✅ | +| read_file | ✅ | +| read_link | ✅ | +| create_file | ✅ | +| write_file | ✅ | +| link | ✅ | +| unlink | ✅ | +| file_truncate| ✅ | +| file_remove | ✅ | +| umount | ✅ | +| dir_remove | ✅ | + + + +# how to use + +## impl BlockDevice Trait + +```rust +#[derive(Debug)] +pub struct Disk {} + +impl BlockDevice for Disk { + fn read_offset(&self, offset: usize) -> Vec { + use std::fs::OpenOptions; + use std::io::{Read, Seek}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + let mut buf = vec![0u8; BLOCK_SIZE as usize]; + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.read_exact(&mut buf); + + buf + } + + fn write_offset(&self, offset: usize, data: &[u8]) { + use std::fs::OpenOptions; + use std::io::{Seek, Write}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.write_all(&data); + } +} + +``` + +## open ext4 + +```rust +let disk = Arc::new(Disk {}); +let ext4 = Ext4::open(disk); +``` + +### read regular file +```rust +let path = "test_files/0.txt"; +let mut read_buf = vec![0u8; READ_SIZE as usize]; +let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); +// 1G +let mut data = vec![0u8; 0x100000 * 1024 as usize]; +let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); +log::info!("read data {:?}", &data[..10]); +``` + +### read link +```rust +let path = "test_files/linktest"; +let mut read_buf = vec![0u8; READ_SIZE as usize]; +// 2 is root inode +let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); +let mut data = vec![0u8; 0x100000 * 1024 as usize]; +let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); +log::info!("read data {:?}", &data[..10]); +``` + +### mkdir +```rust +for i in 0..10 { + let path = format!("dirtest{}", i); + let path = path.as_str(); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); +} +let path = "dir1/dir2/dir3/dir4/dir5/dir6"; +let r = ext4.dir_mk(&path); +assert!(r.is_ok(), "dir make error {:?}", r.err()); +``` + +### file write test +```rust +// file create/write +let inode_mode = InodeFileType::S_IFREG.bits(); +let inode_ref = ext4.create(ROOT_INODE, "512M.txt", inode_mode).unwrap(); + +const WRITE_SIZE: usize = (0x100000 * 512); +let write_buf = vec![0x41 as u8; WRITE_SIZE]; +let r = ext4.write_at(inode_ref.inode_num, 0, &write_buf); +``` + + +### ls +```rust +let entries = ext4.dir_get_entries(ROOT_INODE); +log::info!("dir ls root"); +for entry in entries { + log::info!("{:?}", entry.get_name()); +} +``` + +### file remove +```rust +let path = "test_files/file_to_remove"; +let r = ext4.file_remove(&path); +``` + +### dir remove +```rust +let path = "dir_to_remove"; +let r = ext4.dir_remove(ROOT_INODE, &path); +``` + + +# known bugs +1. ~~ext4_valid_extent check fails in linux due to block allocation using system reserved blocks.~~ (Fixed: Block allocator now checks for system reserved blocks before allocation) + +2. ~~extent block checksum not set~~ (Fixed: Added support for computing and setting extent block checksums) diff --git a/lib/ext4_rs/buginfo.md b/lib/ext4_rs/buginfo.md new file mode 100644 index 0000000..8f97717 --- /dev/null +++ b/lib/ext4_rs/buginfo.md @@ -0,0 +1,53 @@ +# ext4_valid_extent +Disabling the ext4_inode_block_valid check in linux/fs/ext4/block_validity.c allows cat 4G.txt to work normally. + +```bash +root@acc:~/ext4_rs# umount tmp +root@acc:~/ext4_rs# mount ./ex4.img ./tmp +root@acc:~/ext4_rs# cd tmp +root@acc:~/ext4_rs/tmp# dmesg -C +root@acc:~/ext4_rs/tmp# cat 4G.txt +cat: 4G.txt: Input/output error +root@acc:~/sync/ext4_rs/tmp# dmesg +[ 314.393336] CPU: 3 PID: 7442 Comm: cat Not tainted 5.15.167.4-microsoft-standard-WSL2+ #8 +[ 314.393343] Call Trace: +[ 314.393347] +[ 314.393350] dump_stack_lvl+0x33/0x46 +[ 314.393356] ext4_inode_block_valid.cold+0x5/0x16 +[ 314.393358] __ext4_ext_check+0x12f/0x3c0 +[ 314.393362] __read_extent_tree_block+0xb2/0x160 +[ 314.393363] ext4_find_extent+0x1a7/0x420 +[ 314.393364] ext4_ext_map_blocks+0x60/0x17b0 +[ 314.393366] ? page_counter_try_charge+0x2f/0xc0 +[ 314.393368] ? obj_cgroup_charge_pages+0xc3/0x170 +[ 314.393369] ext4_map_blocks+0x1bb/0x5c0 +[ 314.393371] ? page_counter_try_charge+0x2f/0xc0 +[ 314.393372] ext4_mpage_readpages+0x500/0x760 +[ 314.393374] ? __mod_memcg_lruvec_state+0x41/0x80 +[ 314.393375] read_pages+0x93/0x270 +[ 314.393377] page_cache_ra_unbounded+0x1d4/0x260 +[ 314.393379] filemap_get_pages+0xee/0x610 +[ 314.393381] filemap_read+0xa7/0x330 +[ 314.393382] ? memory_oom_group_write+0x20/0xa0 +[ 314.393383] ? __mod_lruvec_page_state+0x53/0xa0 +[ 314.393384] ? page_add_new_anon_rmap+0x44/0x110 +[ 314.393386] ? __handle_mm_fault+0xe1a/0x13a0 +[ 314.393387] ? mmap_region+0x29e/0x620 +[ 314.393389] new_sync_read+0x10e/0x1a0 +[ 314.393392] vfs_read+0xfa/0x190 +[ 314.393394] ksys_read+0x63/0xe0 +[ 314.393395] do_syscall_64+0x35/0xb0 +[ 314.393398] entry_SYSCALL_64_after_hwframe+0x6c/0xd6 +[ 314.393401] RIP: 0033:0x7fab4abe8a61 +[ 314.393403] Code: 00 48 8b 15 b9 73 0e 00 f7 d8 64 89 02 b8 ff ff ff ff eb bd e8 40 c4 01 00 f3 0f 1e fa 80 3d e5 f5 0e 00 00 74 13 31 c0 0f 05 <48> 3d 00 f0 ff ff 77 4f c3 66 0f 1f 44 00 00 55 48 89 e5 48 83 ec +[ 314.393405] RSP: 002b:00007ffc5eb01a18 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 +[ 314.393407] RAX: ffffffffffffffda RBX: 0000000000020000 RCX: 00007fab4abe8a61 +[ 314.393408] RDX: 0000000000020000 RSI: 00007fab4aa49000 RDI: 0000000000000003 +[ 314.393408] RBP: 00007ffc5eb01a40 R08: 0000000000000000 R09: 00007fab4ad22440 +[ 314.393409] R10: 0000000000000022 R11: 0000000000000246 R12: 0000000000020000 +[ 314.393409] R13: 00007fab4aa49000 R14: 0000000000000003 R15: 0000000000000000 +[ 314.393410] +[ 314.393410] some part of the block region overlaps with some other filesystem metadata blocks. +[ 314.393931] ext4_valid_extent fail +[ 314.394063] EXT4-fs error (device loop0): ext4_find_extent:943: inode #28: comm cat: pblk 2008180 bad header/extent: invalid extent entries - magic f30a, entries 45, max 340(340), depth 0(0) +``` \ No newline at end of file diff --git a/lib/ext4_rs/doc/bugs.png b/lib/ext4_rs/doc/bugs.png new file mode 100644 index 0000000..d752ed1 Binary files /dev/null and b/lib/ext4_rs/doc/bugs.png differ diff --git a/lib/ext4_rs/doc/checksum.png b/lib/ext4_rs/doc/checksum.png new file mode 100644 index 0000000..f9553e0 Binary files /dev/null and b/lib/ext4_rs/doc/checksum.png differ diff --git a/lib/ext4_rs/doc/doc.md b/lib/ext4_rs/doc/doc.md new file mode 100644 index 0000000..caec7ed --- /dev/null +++ b/lib/ext4_rs/doc/doc.md @@ -0,0 +1,237 @@ +# ext4 jbd2 + +https://github.com/yuoo655/ext4_rs + +https://github.com/yuoo655/jbd2_rs + + +# ext4 与 ext2/ext3 区别 +| 功能 | ext2 | ext3 | ext4 | +|-------------------------|------|------|------| +| 日志记录 | ❌ | ✅ | ✅ | +| 目录索引 | ❌ | ✅ | ✅ | +| 在线扩容 | ❌ | ✅ | ✅ | +| 在线碎片整理 | ❌ | ❌ | ✅ | +| 校验和 | ❌ | ❌ | ✅ | +| 是否采用extent | ❌ | ❌ | ✅ | +| 无限数量的子目录 | ❌ | ❌ | ✅ | +| 预分配 | ❌ | ❌ | ✅ | +... + +# ext4支持状态 +| 操作 |支持情况| +|--------------|------| +| mount | ✅ | +| open | ✅ | +| lsdir | ✅ | +| mkdir | ✅ | +| read_file | ✅ | +| read_link | ✅ | +| create_file | ✅ | +| write_file | ✅ | +| link | ✅ | +| umount | ❌ | +| file_remove | ❌ | +| dir_remove | ❌ | + +# jbd2支持状态 +| 操作 | 支持情况 | +|---------------------|----------| +| load_journal | ✅ | +| journal_start | ✅ | +| transaction_start | ✅ | +| write_transaction | ✅ | +| transaction_stop | ✅ | +| journal_stop | ✅ | +| recover | ✅ | +| revoke block | ❌ | +| checksum | ❌ | +... + +# 独立组件 + +![img](fs.png) + + +```rust +pub trait BlockDevice: Send + Sync + Any + Debug { + fn read_offset(&self, offset: usize) -> Vec; + fn write_offset(&self, offset: usize, data: &[u8]); +} + +pub trait Jbd2: Send + Sync + Any + Debug { + fn load_journal(&mut self); + fn journal_start(&mut self); + fn transaction_start(&mut self); + fn write_transaction(&mut self, block_id: usize, block_data: Vec); + fn transaction_stop(&mut self); + fn journal_stop(&mut self); + fn recover(&mut self); +} +``` +# 打开文件 + +从挂载点开始ext4_dir_find_entry遍历目录来,对比文件名,找到目标文件,提取direntry中的inode号,这一步也就是查找文件路径到文件inode的过程。 + +```rust +fn ext4_generic_open(path){ + loop { + ext4_dir_find_entry(path) + + if is_goal { + file.inode = dir_search_result.dentry.inode; + return Ok(EOK); + } + } +} +``` + +# 读文件 + +由于ext4默认所有文件都使用extent。extent记录了文件逻辑块号对应磁盘存储的物理块号。读取文件的过程就是寻找文件所有extent的过程。找到extent之后便可从extent中获取物理块号读出数据 + +```rust +pub struct Ext4Inode { + ... + pub block: [u32; 15], + ... +} +``` +![img](extent.png) + + +```rust + + pub fn ext4_file_read(&self, ext4_file: &mut Ext4File) -> Vec { + + ... + + ext4_find_all_extent(&inode_ref, &mut extents); + + // 遍历extents向量,对每个extent,计算它的物理块号,然后调用read_block函数来读取数据块 + for extent in extents { + let block_data = inode_ref.fs().block_device.read_offset(block_num as usize * BLOCK_SIZE); + file_data.extend(block_data); + } + + file_data + } +``` + +# 创建文件 + +- alloc inode +- init inode +- link + +分配inode +```rust +r = ext4_fs_alloc_inode(&mut child_inode_ref, ftype); +``` + +寻找inode位图找到第一个可用的位 +```rust +ext4_bmap_bit_find_clr(data, 0, inodes_in_bg, &mut idx_in_bg); +ext4_bmap_bit_set(&mut raw_data, idx_in_bg); +``` + +设置相应的inode计数 +```rust +/* Modify filesystem counters */ +bg.set_free_inodes_count(&super_block, free_inodes); +/* Increment used directories counter */ +if is_dir { + used_dirs += 1; + bg.set_used_dirs_count(&super_block, used_dirs); +} +/* Decrease unused inodes count */ +bg.set_itable_unused(&super_block, unused); +/* Update superblock */ +super_block.decrease_free_inodes_count(); +``` + +init inode设置inode基础信息 +```rust +... +inode.ext4_inode_set_mode(mode); +inode.ext4_inode_set_links_cnt(0); +... +``` + +init inode extent 信息 +```rust +pub fn ext4_extent_tree_init(inode_ref: &mut Ext4Inode) { + /* Initialize extent root header */ + let mut header = unsafe { *ext4_inode_get_extent_header(inode_ref) }; + ext4_extent_header_set_depth(&mut header, 0); + ext4_extent_header_set_entries_count(&mut header, 0); + ext4_extent_header_set_generation(&mut header, 0); + ext4_extent_header_set_magic(&mut header, EXT4_EXTENT_MAGIC); + ext4_extent_header_set_max_entries_count(&mut header, 4 as u16); +} +``` + +再接着link inode号到文件名,目录项,父目录,首先找到父目录的目录项,再把当前文件的目录项添加到父目录项的尾部。 +```rust +ext4_link::(&mp,&root_inode,&mut child_inode_ref,path,name_len,false){ + + ext4_dir_find_entry::(&parent_inode, &path, len as u32, &mut dir_search_result); + + /* Add entry to parent directory */ + ext4_dir_add_entry::(parent_inode, child_inode, path, len); + +} +``` + +# 写文件 + +查找文件逻辑块对应的物理块,如果没有对应物理块则分配一个物理块。 +```rust + pub fn ext4_file_write(&self, ext4_file: &mut Ext4File, data: &[u8], size: usize) { + ... + let mut size = size; + while size >= block_size { + while iblk_idx < iblock_last { + if iblk_idx < ifile_blocks { + ext4_fs_append_inode_dblk(&mut inode_ref, &mut (iblk_idx as u32), &mut fblk); + } + + iblk_idx += 1; + + ... + } + size -= block_size; + } + + for i in 0..fblock_count { + ... + self.block_device + .write_offset(offset, &data[idx..(idx + BLOCK_SIZE as usize)]); + } + ... + inode_ref.write_back_inode(); + } +``` + +分配物理块同样要从block bitmap中查询, 当分配完物理块后,就可以填写extent信息了。再把记录了逻辑块和物理块对应信息的extent插入extent树中。最后在相应的物理块中写入数据。 +```rust +ext4_balloc_alloc_block() +ext4_ext_insert_extent() +``` + +# checksum +创建文件,写入文件都涉及对meta data的修改。所有meta data都有crc检验信息。修改元数据后,需设置校验信息,然后写入磁盘 +![img](checksum.png) + +```rust +例 +pub fn sync_inode_to_disk_with_csum( + &mut self, + block_device: Arc, + super_block: &Ext4Superblock, + inode_id: u32, +) -> Result<()> { + self.set_inode_checksum(super_block, inode_id); + self.sync_inode_to_disk(block_device, super_block, inode_id) +} +``` \ No newline at end of file diff --git a/lib/ext4_rs/doc/ext4-disk-layout.jpg b/lib/ext4_rs/doc/ext4-disk-layout.jpg new file mode 100644 index 0000000..01ce90f Binary files /dev/null and b/lib/ext4_rs/doc/ext4-disk-layout.jpg differ diff --git a/lib/ext4_rs/doc/extent.png b/lib/ext4_rs/doc/extent.png new file mode 100644 index 0000000..5cfc437 Binary files /dev/null and b/lib/ext4_rs/doc/extent.png differ diff --git a/lib/ext4_rs/doc/fs.png b/lib/ext4_rs/doc/fs.png new file mode 100644 index 0000000..439affd Binary files /dev/null and b/lib/ext4_rs/doc/fs.png differ diff --git a/lib/ext4_rs/doc/linux_ext.png b/lib/ext4_rs/doc/linux_ext.png new file mode 100644 index 0000000..274e2c9 Binary files /dev/null and b/lib/ext4_rs/doc/linux_ext.png differ diff --git a/lib/ext4_rs/gen_img.sh b/lib/ext4_rs/gen_img.sh new file mode 100644 index 0000000..e883353 --- /dev/null +++ b/lib/ext4_rs/gen_img.sh @@ -0,0 +1,13 @@ +rm -rf ex4.img +dd if=/dev/zero of=ex4.img bs=1M count=8192 +mkfs.ext4 -b 4096 ./ex4.img +rm -rf tmp +mkdir tmp +mount ./ex4.img ./tmp/ +cd tmp +mkdir -p test_files +cp ../test_files/* ./test_files/ +mkdir -p dirtest1/dirtest2/dirtest3/dirtest4/dirtest5/ +cp ../test_files/* ./dirtest1/dirtest2/dirtest3/dirtest4/dirtest5/ +cd ../ +umount tmp \ No newline at end of file diff --git a/lib/ext4_rs/gen_test_files.py b/lib/ext4_rs/gen_test_files.py new file mode 100644 index 0000000..ba5d7f7 --- /dev/null +++ b/lib/ext4_rs/gen_test_files.py @@ -0,0 +1,19 @@ +import os + +if not os.path.exists("test_files"): + os.mkdir("test_files") + +if not os.path.exists("tmp"): + os.mkdir("tmp") + +for i in range(2): + name = "test_files/"+ str(i) + ".txt" + f = open(name, "w") + # 1024M + f.write(str(i) * 0x100000 * 1024) + +name = "test_files/file_to_remove" +f = open(name, "w") +#1MB * 1024 +f.write("A" * (0x100000 * 1024)) +f.close() \ No newline at end of file diff --git a/lib/ext4_rs/run b/lib/ext4_rs/run new file mode 100755 index 0000000..ca7ad91 --- /dev/null +++ b/lib/ext4_rs/run @@ -0,0 +1,35 @@ +rm -rf test_files +python3 gen_test_files.py +rm -rf ex4.img +dd if=/dev/zero of=ex4.img bs=1M count=8192 +mkfs.ext4 -b 4096 ./ex4.img + +## create link +cd test_files +sudo ln -s ./1.txt ./linktest +cd .. + +## copy files to image +umount tmp +rm -rf tmp +mkdir tmp +sudo mount ./ex4.img ./tmp/ +cd tmp +sudo mkdir -p test_files +mkdir -p dir_to_remove +sudo cp ../test_files/* ./test_files/ + +cd ../ +sudo umount tmp + +## run +cargo run LOG=trace + +## write check +sudo mount ./ex4.img ./tmp/ +cd tmp +ls +cd test_files +ls +cd ../../ +sudo umount ./tmp diff --git a/lib/ext4_rs/run.sh b/lib/ext4_rs/run.sh new file mode 100644 index 0000000..ca7ad91 --- /dev/null +++ b/lib/ext4_rs/run.sh @@ -0,0 +1,35 @@ +rm -rf test_files +python3 gen_test_files.py +rm -rf ex4.img +dd if=/dev/zero of=ex4.img bs=1M count=8192 +mkfs.ext4 -b 4096 ./ex4.img + +## create link +cd test_files +sudo ln -s ./1.txt ./linktest +cd .. + +## copy files to image +umount tmp +rm -rf tmp +mkdir tmp +sudo mount ./ex4.img ./tmp/ +cd tmp +sudo mkdir -p test_files +mkdir -p dir_to_remove +sudo cp ../test_files/* ./test_files/ + +cd ../ +sudo umount tmp + +## run +cargo run LOG=trace + +## write check +sudo mount ./ex4.img ./tmp/ +cd tmp +ls +cd test_files +ls +cd ../../ +sudo umount ./tmp diff --git a/lib/ext4_rs/rust-toolchain.toml b/lib/ext4_rs/rust-toolchain.toml new file mode 100644 index 0000000..adda71d --- /dev/null +++ b/lib/ext4_rs/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-06-01" +components = [ "llvm-tools-preview" ] +profile = "default" \ No newline at end of file diff --git a/lib/ext4_rs/src/ext4_defs/block.rs b/lib/ext4_rs/src/ext4_defs/block.rs new file mode 100644 index 0000000..44aad25 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/block.rs @@ -0,0 +1,83 @@ +use crate::prelude::*; + +pub trait BlockDevice: Send + Sync + Any { + fn read_offset(&self, offset: usize) -> Vec; + fn write_offset(&self, offset: usize, data: &[u8]); +} + +pub struct Block { + pub disk_offset: usize, + pub data: Vec, +} + +impl Block { + /// Load the block from the disk. + pub fn load(block_device: &Arc, offset: usize) -> Self { + let data = block_device.read_offset(offset); + Block { + disk_offset: offset, + data, + } + } + + /// Load the block from inode block + pub fn load_inode_root_block(data: &[u32; 15]) -> Self { + let data_bytes: &[u8; 60] = unsafe { + core::mem::transmute(data) + }; + Block { + disk_offset: 0, + data: data_bytes.to_vec(), + } + } + + /// Read the block as a specific type. + pub fn read_as(&self) -> T { + unsafe { + let ptr = self.data.as_ptr() as *const T; + ptr.read_unaligned() + } + } + + /// Read the block as a specific type at a specific offset. + pub fn read_offset_as(&self, offset: usize) -> T { + unsafe { + let ptr = self.data.as_ptr().add(offset) as *const T; + ptr.read_unaligned() + } + } + + /// Read the block as a specific type mutably. + pub fn read_as_mut(&mut self) -> &mut T { + unsafe { + let ptr = self.data.as_mut_ptr() as *mut T; + &mut *ptr + } + } + + /// Read the block as a specific type mutably at a specific offset. + pub fn read_offset_as_mut(&mut self, offset: usize) -> &mut T { + unsafe { + let ptr = self.data.as_mut_ptr().add(offset) as *mut T; + &mut *ptr + } + } + + /// Write data to the block starting at a specific offset. + pub fn write_offset(&mut self, offset: usize, data: &[u8], len: usize) { + let end = offset + len; + if end <= self.data.len() { + let slice_end = len.min(data.len()); + self.data[offset..end].copy_from_slice(&data[..slice_end]); + } else { + panic!("Write would overflow the block buffer"); + } + } +} + + +impl Block{ + pub fn sync_blk_to_disk(&self, block_device: Arc){ + block_device.write_offset(self.disk_offset, &self.data); + } +} \ No newline at end of file diff --git a/lib/ext4_rs/src/ext4_defs/block_group.rs b/lib/ext4_rs/src/ext4_defs/block_group.rs new file mode 100644 index 0000000..24acf7a --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/block_group.rs @@ -0,0 +1,254 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +/// Represents the structure of an Ext4 block group descriptor. +#[derive(Debug, Default, Clone, Copy)] +#[repr(C, packed)] +pub struct Ext4BlockGroup { + pub block_bitmap_lo: u32, // 块位图块 + pub inode_bitmap_lo: u32, // 节点位图块 + pub inode_table_first_block_lo: u32, // 节点表块 + pub free_blocks_count_lo: u16, // 空闲块数 + pub free_inodes_count_lo: u16, // 空闲节点数 + pub used_dirs_count_lo: u16, // 目录数 + pub flags: u16, // EXT4_BG_flags (INODE_UNINIT, etc) + pub exclude_bitmap_lo: u32, // 快照排除位图 + pub block_bitmap_csum_lo: u16, // crc32c(s_uuid+grp_num+bbitmap) LE + pub inode_bitmap_csum_lo: u16, // crc32c(s_uuid+grp_num+ibitmap) LE + pub itable_unused_lo: u16, // 未使用的节点数 + pub checksum: u16, // crc16(sb_uuid+group+desc) + + pub block_bitmap_hi: u32, // 块位图块 MSB + pub inode_bitmap_hi: u32, // 节点位图块 MSB + pub inode_table_first_block_hi: u32, // 节点表块 MSB + pub free_blocks_count_hi: u16, // 空闲块数 MSB + pub free_inodes_count_hi: u16, // 空闲节点数 MSB + pub used_dirs_count_hi: u16, // 目录数 MSB + pub itable_unused_hi: u16, // 未使用的节点数 MSB + pub exclude_bitmap_hi: u32, // 快照排除位图 MSB + pub block_bitmap_csum_hi: u16, // crc32c(s_uuid+grp_num+bbitmap) BE + pub inode_bitmap_csum_hi: u16, // crc32c(s_uuid+grp_num+ibitmap) BE + pub reserved: u32, // 填充 +} + +impl Ext4BlockGroup { + /// Load the block group descriptor from the disk. + pub fn load_new( + block_device: Arc, + super_block: &Ext4Superblock, + block_group_idx: usize, + ) -> Self { + let dsc_cnt = BLOCK_SIZE / super_block.desc_size as usize; + let dsc_id = block_group_idx / dsc_cnt; + let first_data_block = super_block.first_data_block; + let block_id = first_data_block as usize + dsc_id + 1; + let offset = (block_group_idx % dsc_cnt) * super_block.desc_size as usize; + + let ext4block = Block::load(&block_device, block_id * BLOCK_SIZE); + let bg: Ext4BlockGroup = ext4block.read_offset_as(offset); + + bg + } +} + +impl Ext4BlockGroup { + /// Get the block number of the block bitmap for this block group. + pub fn get_block_bitmap_block(&self, s: &Ext4Superblock) -> u64 { + let mut v = self.block_bitmap_lo as u64; + let desc_size = s.desc_size; + if desc_size > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= (self.block_bitmap_hi as u64) << 32; + } + v + } + + /// Get the block number of the inode bitmap for this block group. + pub fn get_inode_bitmap_block(&self, s: &Ext4Superblock) -> u64 { + let mut v = self.inode_bitmap_lo as u64; + let desc_size = s.desc_size; + if desc_size > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= (self.inode_bitmap_hi as u64) << 32; + } + v + } + + /// Get the count of unused inodes in this block group. + pub fn get_itable_unused(&mut self, s: &Ext4Superblock) -> u32 { + let mut v = self.itable_unused_lo as u32; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= ((self.itable_unused_hi as u64) << 32) as u32; + } + v + } + + /// Get the count of used directories in this block group. + pub fn get_used_dirs_count(&self, s: &Ext4Superblock) -> u32 { + let mut v = self.used_dirs_count_lo as u32; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= ((self.used_dirs_count_hi as u64) << 32) as u32; + } + v + } + + /// Set the count of used directories in this block group. + pub fn set_used_dirs_count(&mut self, s: &Ext4Superblock, cnt: u32) { + self.itable_unused_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.itable_unused_hi = (cnt >> 16) as u16; + } + } + + /// Set the count of unused inodes in this block group. + pub fn set_itable_unused(&mut self, s: &Ext4Superblock, cnt: u32) { + self.itable_unused_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.itable_unused_hi = (cnt >> 16) as u16; + } + } + + /// Set the count of free inodes in this block group. + pub fn set_free_inodes_count(&mut self, s: &Ext4Superblock, cnt: u32) { + self.free_inodes_count_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.free_inodes_count_hi = (cnt >> 16) as u16; + } + } + + /// Get the count of free inodes in this block group. + pub fn get_free_inodes_count(&self) -> u32 { + ((self.free_inodes_count_hi as u64) << 32) as u32 | self.free_inodes_count_lo as u32 + } + + /// Get the block number of the inode table for this block group. + pub fn get_inode_table_blk_num(&self) -> u32 { + ((self.inode_table_first_block_hi as u64) << 32) as u32 | self.inode_table_first_block_lo + } +} + +/// sync block group to disk +impl Ext4BlockGroup { + /// Calculate and return the checksum of the block group descriptor. + #[allow(unused)] + pub fn get_block_group_checksum(&mut self, bgid: u32, super_block: &Ext4Superblock) -> u16 { + let desc_size = super_block.desc_size(); + + let mut orig_checksum = 0; + let mut checksum = 0; + + orig_checksum = self.checksum; + + // 准备:暂时将bg校验和设为0 + self.checksum = 0; + + // uuid checksum + checksum = ext4_crc32c( + EXT4_CRC32_INIT, + &super_block.uuid, + super_block.uuid.len() as u32, + ); + + // bgid checksum + checksum = ext4_crc32c(checksum, &bgid.to_le_bytes(), 4); + + // cast self to &[u8] + let self_bytes = + unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, 0x40) }; + + // bg checksum + checksum = ext4_crc32c(checksum, self_bytes, desc_size as u32); + + self.checksum = orig_checksum; + + (checksum & 0xFFFF) as u16 + } + + /// Synchronize the block group data to disk. + pub fn sync_block_group_to_disk( + &self, + block_device: Arc, + bgid: usize, + super_block: &Ext4Superblock, + ) { + let dsc_cnt = BLOCK_SIZE / super_block.desc_size as usize; + // let dsc_per_block = dsc_cnt; + let dsc_id = bgid / dsc_cnt; + // let first_meta_bg = super_block.first_meta_bg; + let first_data_block = super_block.first_data_block; + let block_id = first_data_block as usize + dsc_id + 1; + let offset = (bgid % dsc_cnt) * super_block.desc_size as usize; + + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(block_id * BLOCK_SIZE + offset, data); + } + + /// Set the checksum of the block group descriptor. + pub fn set_block_group_checksum(&mut self, bgid: u32, super_block: &Ext4Superblock) { + let csum = self.get_block_group_checksum(bgid, super_block); + self.checksum = csum; + } + + /// Synchronize the block group data to disk with checksum. + pub fn sync_to_disk_with_csum( + &mut self, + block_device: Arc, + bgid: usize, + super_block: &Ext4Superblock, + ) { + self.set_block_group_checksum(bgid as u32, super_block); + self.sync_block_group_to_disk(block_device, bgid, super_block) + } + + /// Set the block allocation bitmap checksum for this block group. + pub fn set_block_group_balloc_bitmap_csum(&mut self, s: &Ext4Superblock, bitmap: &[u8]) { + let desc_size = s.desc_size(); + + let csum = s.ext4_balloc_bitmap_csum(bitmap); + let lo_csum = (csum & 0xFFFF).to_le(); + let hi_csum = (csum >> 16).to_le(); + + if (s.features_read_only & 0x400) >> 10 == 0 { + return; + } + self.block_bitmap_csum_lo = lo_csum as u16; + if desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.block_bitmap_csum_hi = hi_csum as u16; + } + } + + /// Get the count of free blocks in this block group. + pub fn get_free_blocks_count(&self) -> u64 { + let mut v = self.free_blocks_count_lo as u64; + if self.free_blocks_count_hi != 0 { + v |= (self.free_blocks_count_hi as u64) << 32; + } + v + } + + /// Set the count of free blocks in this block group. + pub fn set_free_blocks_count(&mut self, cnt: u32) { + self.free_blocks_count_lo = (cnt & 0xffff) as u16; + self.free_blocks_count_hi = (cnt >> 16) as u16; + } + + + /// Set the inode allocation bitmap checksum for this block group. + pub fn set_block_group_ialloc_bitmap_csum(&mut self, s: &Ext4Superblock, bitmap: &[u8]) { + let desc_size = s.desc_size(); + + let csum = s.ext4_ialloc_bitmap_csum(bitmap); + let lo_csum = (csum & 0xFFFF).to_le(); + let hi_csum = (csum >> 16).to_le(); + + if (s.features_read_only & 0x400) >> 10 == 0 { + return; + } + self.inode_bitmap_csum_lo = lo_csum as u16; + if desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.inode_bitmap_csum_hi = hi_csum as u16; + } + } +} diff --git a/lib/ext4_rs/src/ext4_defs/consts.rs b/lib/ext4_rs/src/ext4_defs/consts.rs new file mode 100644 index 0000000..bea43b5 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/consts.rs @@ -0,0 +1,62 @@ +use bitflags::bitflags; + +pub const BLOCK_SIZE: usize = 4096; + +pub type Ext4Lblk = u32; +pub type Ext4Fsblk = u64; + +pub const EOK: usize = 0; + +/// Inode +pub const ROOT_INODE: u32 = 2; // 根目录 inode +pub const JOURNAL_INODE: u32 = 8; // 日志文件 inode +pub const UNDEL_DIR_INODE: u32 = 6; // 未删除目录 inode +pub const LOST_AND_FOUND_INODE: u32 = 11; // lost+found 目录 inode +pub const EXT4_INODE_MODE_FILE: usize = 0x8000; +pub const EXT4_INODE_MODE_TYPE_MASK: u16 = 0xF000; +pub const EXT4_INODE_MODE_PERM_MASK: u16 = 0x0FFF; +pub const EXT4_INODE_BLOCK_SIZE: usize = 512; +pub const EXT4_GOOD_OLD_INODE_SIZE: u16 = 128; +pub const EXT4_INODE_FLAG_EXTENTS: usize = 0x00080000; /* Inode uses extents */ + +/// Extent +pub const EXT_INIT_MAX_LEN: u16 = 32768; +pub const EXT_UNWRITTEN_MAX_LEN: u16 = 65535; +pub const EXT_MAX_BLOCKS: Ext4Lblk = u32::MAX; +pub const EXT4_EXTENT_MAGIC: u16 = 0xF30A; + +/// BLock group descriptor flags. +pub const EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE: u16 = 32; +pub const EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE: u16 = 64; + +/// SuperBlock +pub const SUPERBLOCK_OFFSET: usize = 1024; +pub const EXT4_SUPERBLOCK_OS_HURD: u32 = 1; + +/// File +/// libc file open flags +pub const O_ACCMODE: i32 = 0o0003; +pub const O_RDONLY: i32 = 0o00; +pub const O_WRONLY: i32 = 0o01; +pub const O_RDWR: i32 = 0o02; +pub const O_CREAT: i32 = 0o0100; +pub const O_EXCL: i32 = 0o0200; +pub const O_NOCTTY: i32 = 0o0400; +pub const O_TRUNC: i32 = 0o01000; +pub const O_APPEND: i32 = 0o02000; +pub const O_NONBLOCK: i32 = 0o04000; +pub const O_SYNC: i32 = 0o4010000; +pub const O_ASYNC: i32 = 0o020000; +pub const O_LARGEFILE: i32 = 0o0100000; +pub const O_DIRECTORY: i32 = 0o0200000; +pub const O_NOFOLLOW: i32 = 0o0400000; +pub const O_CLOEXEC: i32 = 0o2000000; +pub const O_DIRECT: i32 = 0o040000; +pub const O_NOATIME: i32 = 0o1000000; +pub const O_PATH: i32 = 0o10000000; +pub const O_DSYNC: i32 = 0o010000; +/// linux access syscall flags +pub const F_OK: i32 = 0; +pub const R_OK: i32 = 4; +pub const W_OK: i32 = 2; +pub const X_OK: i32 = 1; \ No newline at end of file diff --git a/lib/ext4_rs/src/ext4_defs/direntry.rs b/lib/ext4_rs/src/ext4_defs/direntry.rs new file mode 100644 index 0000000..92c2d89 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/direntry.rs @@ -0,0 +1,267 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +bitflags! { + #[derive(PartialEq, Eq)] + pub struct DirEntryType: u8 { + const EXT4_DE_UNKNOWN = 0; + const EXT4_DE_REG_FILE = 1; + const EXT4_DE_DIR = 2; + const EXT4_DE_CHRDEV = 3; + const EXT4_DE_BLKDEV = 4; + const EXT4_DE_FIFO = 5; + const EXT4_DE_SOCK = 6; + const EXT4_DE_SYMLINK = 7; + } +} + +/// Directory entry structure. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Ext4DirEntry { + pub inode: u32, // 该目录项指向的inode的编号 + pub entry_len: u16, // 到下一个目录项的距离 + pub name_len: u8, // 低8位的文件名长度 + pub inner: Ext4DirEnInternal, // 联合体成员 + pub name: [u8; 255], // 文件名 +} + + +/// Internal directory entry structure. +#[repr(C)] +#[derive(Clone, Copy)] +pub union Ext4DirEnInternal { + pub name_length_high: u8, // 高8位的文件名长度 + pub inode_type: u8, // 引用的inode的类型(在rev >= 0.5中) +} + + +/// Fake directory entry structure. Used for directory entry iteration. +#[repr(C)] +pub struct Ext4FakeDirEntry { + inode: u32, + entry_length: u16, + name_length: u8, + inode_type: u8, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Ext4DirEntryTail { + pub reserved_zero1: u32, + pub rec_len: u16, + pub reserved_zero2: u8, + pub reserved_ft: u8, + pub checksum: u32, // crc32c(uuid+inum+dirblock) +} + +pub struct Ext4DirSearchResult{ + pub dentry: Ext4DirEntry, + pub pblock_id: usize, // disk block id + pub offset: usize, // offset in block + pub prev_offset: usize, //prev direntry offset +} + + +impl Ext4DirSearchResult { + pub fn new(dentry: Ext4DirEntry) -> Self { + Self { + dentry, + pblock_id: 0, + offset: 0, + prev_offset: 0, + } + } +} + + +impl Debug for Ext4DirEnInternal { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + unsafe { + write!( + f, + "Ext4DirEnInternal {{ name_length_high: {:?} }}", + self.name_length_high + ) + } + } +} + +impl Default for Ext4DirEnInternal { + fn default() -> Self { + Self { + name_length_high: 0, + } + } +} + +impl Default for Ext4DirEntry { + fn default() -> Self { + Self { + inode: 0, + entry_len: 0, + name_len: 0, + inner: Ext4DirEnInternal::default(), + name: [0; 255], + } + } +} + +impl TryFrom<&[T]> for Ext4DirEntry { + type Error = u64; + fn try_from(data: &[T]) -> core::result::Result { + // let data = data; + Ok(unsafe { core::ptr::read(data.as_ptr() as *const _) }) + } +} + +/// Directory entry implementation. +impl Ext4DirEntry { + + /// Check if the directory entry is unused. + pub fn unused(&self) -> bool { + self.inode == 0 + } + + /// Set the directory entry as unused. + pub fn set_unused(&mut self) { + self.inode = 0 + } + + /// Check name + pub fn compare_name(&self, name: &str) -> bool { + if self.name_len as usize == name.len(){ + return &self.name[..name.len()] == name.as_bytes() + } + false + } + + /// Entry length + pub fn entry_len(&self) -> u16 { + self.entry_len + } + + /// Dir type + pub fn get_de_type(&self) -> u8 { + unsafe { self.inner.inode_type } + } + + /// Get name to string + pub fn get_name(&self) -> String { + let name_len = self.name_len as usize; + let name = &self.name[..name_len]; + let name = core::str::from_utf8(name).unwrap(); + name.to_string() + } + + /// Get name len + pub fn get_name_len(&self) -> usize { + self.name_len as usize + } + + /// 计算目录项的实际使用长度(不包括填充字节) + pub fn actual_len(&self) -> usize { + size_of::() + self.name_len as usize + } + + + /// 计算对齐后的目录项长度(包括填充字节) + pub fn used_len_aligned(&self) -> usize { + let mut len = self.actual_len(); + if len % 4 != 0 { + len += 4 - (len % 4); + } + len + } + + + pub fn write_entry(&mut self, entry_len: u16, inode: u32, name: &str, de_type:DirEntryType) { + self.inode = inode; + self.entry_len = entry_len; + self.name_len = name.len() as u8; + self.inner.inode_type = de_type.bits(); + self.name[..name.len()].copy_from_slice(name.as_bytes()); + } + +} + +impl Ext4DirEntry { + + /// Get the checksum of the directory entry. + #[allow(unused)] + pub fn ext4_dir_get_csum(&self, s: &Ext4Superblock, blk_data: &[u8], ino_gen: u32) -> u32 { + let ino_index = self.inode; + + let mut csum = 0; + + let uuid = s.uuid; + + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, &ino_index.to_le_bytes(), 4); + csum = ext4_crc32c(csum, &ino_gen.to_le_bytes(), 4); + let mut data = [0u8; 0xff4]; + unsafe { + core::ptr::copy_nonoverlapping(blk_data.as_ptr(), data.as_mut_ptr(), blk_data.len()); + } + + csum = ext4_crc32c(csum, &data[..], 0xff4); + csum + } + + /// Write de to block + pub fn write_de_to_blk(&self, dst_blk: &mut Block, offset: usize) { + let count = core::mem::size_of::() / core::mem::size_of::(); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + dst_blk.data.splice( + offset..offset + core::mem::size_of::(), + data.iter().cloned(), + ); + // assert_eq!(dst_blk.block_data[offset..offset + core::mem::size_of::()], data[..]); + } + + /// Copy the directory entry to a slice. + pub fn copy_to_slice(&self, array: &mut [u8], offset: usize) { + let de_ptr = self as *const Ext4DirEntry as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::() / core::mem::size_of::(); + unsafe { + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } + } +} + + +impl Ext4DirEntryTail{ + pub fn new() -> Self { + Self { + reserved_zero1: 0, + rec_len: size_of::() as u16, + reserved_zero2: 0, + reserved_ft: 0xDE, + checksum: 0, + } + } + + pub fn tail_set_csum( + &mut self, + s: &Ext4Superblock, + diren: &Ext4DirEntry, + blk_data: &[u8], + ino_gen: u32, + ) { + let csum = diren.ext4_dir_get_csum(s, blk_data, ino_gen); + self.checksum = csum; + } + + pub fn copy_to_slice(&self, array: &mut [u8]) { + unsafe { + let offset = BLOCK_SIZE - core::mem::size_of::(); + let de_ptr = self as *const Ext4DirEntryTail as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::(); + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } + } +} \ No newline at end of file diff --git a/lib/ext4_rs/src/ext4_defs/ext4.rs b/lib/ext4_rs/src/ext4_defs/ext4.rs new file mode 100644 index 0000000..3e24998 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/ext4.rs @@ -0,0 +1,8 @@ +use crate::prelude::*; + +use super::*; + +pub struct Ext4 { + pub block_device: Arc, + pub super_block: Ext4Superblock, +} diff --git a/lib/ext4_rs/src/ext4_defs/extents.rs b/lib/ext4_rs/src/ext4_defs/extents.rs new file mode 100644 index 0000000..35f70de --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/extents.rs @@ -0,0 +1,753 @@ +use crate::prelude::*; +use crate::return_errno_with_message; + +use super::*; + +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct Ext4ExtentHeader { + /// Magic number, 0xF30A. + pub magic: u16, + + /// Number of valid entries following the header. + pub entries_count: u16, + + /// Maximum number of entries that could follow the header. + pub max_entries_count: u16, + + /// Depth of this extent node in the extent tree. Depth 0 indicates that this node points to data blocks. + pub depth: u16, + + /// Generation of the tree (used by Lustre, but not standard in ext4). + pub generation: u32, +} + +/// Structure representing an index node within an extent tree. +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct Ext4ExtentIndex { + /// Block number from which this index node starts. + pub first_block: u32, + + /// Lower 32-bits of the block number to which this index points. + pub leaf_lo: u32, + + /// Upper 16-bits of the block number to which this index points. + pub leaf_hi: u16, + + /// Padding for alignment. + pub padding: u16, +} + +/// Structure representing an Ext4 extent. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct Ext4Extent { + /// First file block number that this extent covers. + pub first_block: u32, + + /// Number of blocks covered by this extent. + pub block_count: u16, + + /// Upper 16-bits of the block number to which this extent points. + pub start_hi: u16, + + /// Lower 32-bits of the block number to which this extent points. + pub start_lo: u32, +} + +/// Extent tree node. Includes the header, the data. +#[derive(Clone, Debug)] +pub struct ExtentNode { + pub header: Ext4ExtentHeader, + pub data: NodeData, + pub is_root: bool, +} + +/// Data of extent tree. +#[derive(Clone, Debug)] +pub enum NodeData { + Root([u32; 15]), + Internal(Vec), // size = BLOCK_SIZE +} + +/// Search path in the extent tree. +#[derive(Clone, Debug)] +pub struct SearchPath { + pub depth: u16, // current depth + pub maxdepth: u16, // max depth + pub path: Vec, // search result of each level +} + +/// Extent tree node search result +#[derive(Clone, Debug)] +pub struct ExtentPathNode { + pub header: Ext4ExtentHeader, // save header for convenience + pub index: Option, // for convenience(you can get index through pos of extent node) + pub extent: Option, // same reason as above + pub position: usize, // position of search result in the node + pub pblock: u64, // physical block of search result + pub pblock_of_node: usize, // physical block of this node +} + +/// load methods for Ext4ExtentHeader +impl Ext4ExtentHeader { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> &mut Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { &mut *ptr } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> &mut Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { &mut *ptr } + } + + /// Is the node a leaf node? + pub fn is_leaf(&self) -> bool { + self.depth == 0 + } +} + +/// load methods for Ext4ExtentIndex +impl Ext4ExtentIndex { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> Self { + unsafe { core::ptr::read(data.as_mut_ptr() as *mut _) } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> Self { + unsafe { core::ptr::read(data.as_mut_ptr() as *mut _) } + } +} + +/// load methods for Ext4Extent +impl Ext4Extent { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { *ptr } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { *ptr } + } +} + +impl ExtentNode { + /// Load the extent node from the data. + pub fn load_from_data(data: &[u8], is_root: bool) -> Result { + if is_root { + if data.len() != 15 * 4 { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + + let mut root_data = [0u32; 15]; + for (i, chunk) in data.chunks(4).enumerate() { + root_data[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + let header = Ext4ExtentHeader::load_from_u32(&root_data); + + Ok(ExtentNode { + header, + data: NodeData::Root(root_data), + is_root, + }) + } else { + if data.len() != BLOCK_SIZE { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + let header = Ext4ExtentHeader::load_from_u8(&data[..size_of::()]); + Ok(ExtentNode { + header, + data: NodeData::Internal(data.to_vec()), + is_root, + }) + } + } + + /// Load the extent node from the data mutably. + pub fn load_from_data_mut(data: &mut [u8], is_root: bool) -> Result { + if is_root { + if data.len() != 15 * 4 { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + + let mut root_data = [0u32; 15]; + for (i, chunk) in data.chunks(4).enumerate() { + root_data[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + let header = *Ext4ExtentHeader::load_from_u32_mut(&mut root_data); + + Ok(ExtentNode { + header, + data: NodeData::Root(root_data), + is_root, + }) + } else { + if data.len() != BLOCK_SIZE { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + let mut header = *Ext4ExtentHeader::load_from_u8_mut(&mut data[..size_of::()]); + Ok(ExtentNode { + header, + data: NodeData::Internal(data.to_vec()), + is_root, + }) + } + } +} + +impl ExtentNode { + /// Binary search for the extent that contains the given block. + pub fn binsearch_extent(&mut self, lblock: Ext4Lblk) -> Option<(Ext4Extent, usize)> { + // empty node + if self.header.entries_count == 0 { + match &self.data { + NodeData::Root(root_data) => { + let extent = Ext4Extent::load_from_u32(&root_data[3..]); + return Some((extent, 0)); + } + NodeData::Internal(internal_data) => { + let extent = Ext4Extent::load_from_u8(&internal_data[12..]); + return Some((extent, 0)); + } + } + } + + match &mut self.data { + NodeData::Root(root_data) => { + let header = self.header; + let mut l = 1; + let mut r = header.entries_count as usize - 1; + while l <= r { + let m = l + (r - l) / 2; + let idx = 3 + m * 3; + let ext = Ext4Extent::load_from_u32(&root_data[idx..]); + if lblock < ext.first_block { + r = m - 1; + } else { + l = m + 1; + } + } + let idx = 3 + (l - 1) * 3; + let ext = Ext4Extent::load_from_u32(&root_data[idx..]); + + Some((ext, l - 1)) + } + NodeData::Internal(internal_data) => { + let mut l = 1; + let mut r = (self.header.entries_count - 1) as usize; + + while l <= r { + let m = l + (r - l) / 2; + let offset = size_of::() + m * size_of::(); + let mut ext = Ext4Extent::load_from_u8_mut(&mut internal_data[offset..]); + + if lblock < ext.first_block { + r = m - 1; + } else { + l = m + 1; // Otherwise, move to the right half + } + } + let offset = size_of::() + (l - 1) * size_of::(); + let mut ext = Ext4Extent::load_from_u8_mut(&mut internal_data[offset..]); + + Some((ext, l - 1)) + } + } + } + + /// Binary search for the closest index of the given block. + pub fn binsearch_idx(&self, lblock: Ext4Lblk) -> Option { + if self.header.entries_count == 0 { + return None; + } + + match &self.data { + NodeData::Root(root_data) => { + // Root node handling + let start = size_of::() / 4; + let indexes = &root_data[start..]; + + let mut l = 1; // Skip the first index + let mut r = self.header.entries_count as usize - 1; + + while l <= r { + let m = l + (r - l) / 2; + let offset = m * size_of::() / 4; // Convert to u32 offset + let extent_index = Ext4ExtentIndex::load_from_u32(&indexes[offset..]); + + if lblock < extent_index.first_block { + if m == 0 { + break; // Prevent underflow + } + r = m - 1; + } else { + l = m + 1; + } + } + + if l == 0 { + return None; + } + + Some(l - 1) + } + NodeData::Internal(internal_data) => { + // Internal node handling + let start = size_of::(); + let indexes = &internal_data[start..]; + + let mut l = 0; + let mut r = (self.header.entries_count - 1) as usize; + + while l <= r { + let m = l + (r - l) / 2; + let offset = m * size_of::(); + let extent_index = Ext4ExtentIndex::load_from_u8(&indexes[offset..]); + + if lblock < extent_index.first_block { + if m == 0 { + break; // Prevent underflow + } + r = m - 1; + } else { + l = m + 1; + } + } + + if l == 0 { + return None; + } + + Some(l - 1) + } + } + } + + /// Get the index node at the given position. + pub fn get_index(&self, pos: usize) -> Result { + match &self.data { + NodeData::Root(root_data) => { + let start = size_of::() / 4; + let indexes = &root_data[start..]; + let offset = pos * size_of::() / 4; + Ok(Ext4ExtentIndex::load_from_u32(&indexes[offset..])) + } + NodeData::Internal(internal_data) => { + let start = size_of::(); + let indexes = &internal_data[start..]; + let offset = pos * size_of::(); + Ok(Ext4ExtentIndex::load_from_u8(&indexes[offset..])) + } + } + } + + /// Get the extent node at the given position. + pub fn get_extent(&self, pos: usize) -> Option { + match &self.data { + NodeData::Root(root_data) => { + let start = size_of::() / 4; + let extents = &root_data[start..]; + let offset = pos * size_of::() / 4; + Some(Ext4Extent::load_from_u32(&extents[offset..])) + } + NodeData::Internal(internal_data) => { + let start = size_of::(); + let extents = &internal_data[start..]; + let offset = pos * size_of::(); + Some(Ext4Extent::load_from_u8(&extents[offset..])) + } + } + } +} + +impl Ext4ExtentIndex { + /// Get the physical block number to which this index points. + pub fn get_pblock(&self) -> u64 { + ((self.leaf_hi as u64) << 32) | (self.leaf_lo as u64) + } + + /// Stores the physical block number to which this extent points. + pub fn store_pblock(&mut self, pblock: u64) { + self.leaf_lo = (pblock & 0xffffffff) as u32; + self.leaf_hi = (pblock >> 32) as u16; + } +} + +impl Ext4Extent { + /// Get the first block number(logical) of the extent. + pub fn get_first_block(&self) -> u32 { + self.first_block + } + + /// Set the first block number(logical) of the extent. + pub fn set_first_block(&mut self, first_block: u32) { + self.first_block = first_block; + } + + /// Get the starting physical block number of the extent. + pub fn get_pblock(&self) -> u64 { + let lo = u64::from(self.start_lo); + let hi = u64::from(self.start_hi) << 32; + lo | hi + } + + /// Stores the physical block number to which this extent points. + pub fn store_pblock(&mut self, pblock: u64) { + self.start_lo = (pblock & 0xffffffff) as u32; + self.start_hi = (pblock >> 32) as u16; + } + + /// Returns true if the extent is unwritten. + pub fn is_unwritten(&self) -> bool { + self.block_count > EXT_INIT_MAX_LEN + } + + /// Returns the actual length of the extent. + pub fn get_actual_len(&self) -> u16 { + if self.is_unwritten() { + self.block_count - EXT_INIT_MAX_LEN + } else { + self.block_count + } + } + + /// Set the actual length of the extent. + pub fn set_actual_len(&mut self, len: u16){ + self.block_count = len; + } + + /// Marks the extent as unwritten. + pub fn mark_unwritten(&mut self) { + self.block_count |= EXT_INIT_MAX_LEN; + } + + /// Get the last file block number that this extent covers. + pub fn get_last_block(&self) -> u32 { + self.first_block + self.block_count as u32 - 1 + } + + /// Set the last file block number for this extent. + pub fn set_last_block(&mut self, last_block: u32) { + self.block_count = (last_block - self.first_block + 1) as u16; + } +} + +impl Ext4ExtentHeader { + pub fn new(magic: u16, entries: u16, max_entries: u16, depth: u16, generation: u32) -> Self { + Self { + magic, + entries_count: entries, + max_entries_count: max_entries, + depth, + generation, + } + } + + pub fn set_depth(&mut self, depth: u16) { + self.depth = depth; + } + + pub fn add_depth(&mut self) { + self.depth += 1; + } + + pub fn set_entries_count(&mut self, entries_count: u16) { + self.entries_count = entries_count; + } + + pub fn set_generation(&mut self, generation: u32) { + self.generation = generation; + } + + pub fn set_magic(&mut self) { + self.magic = EXT4_EXTENT_MAGIC; + } + + pub fn set_max_entries_count(&mut self, max_entries_count: u16) { + self.max_entries_count = max_entries_count; + } +} + +impl SearchPath { + pub fn new() -> Self { + SearchPath { + depth: 0, + maxdepth: 4, + path: vec![], + } + } +} + +impl Default for SearchPath { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::mem::size_of; + + #[test] + fn test_load_from_data() { + // Create a valid root node data + let mut data: [u8; 15 * 4] = [0; 15 * 4]; + data[0..2].copy_from_slice(&EXT4_EXTENT_MAGIC.to_le_bytes()); // set magic number + let node = ExtentNode::load_from_data(&data, true).expect("Failed to load root node"); + assert_eq!(node.header.magic, EXT4_EXTENT_MAGIC); + + // Create a valid internal node data + let mut data: Vec = vec![0; BLOCK_SIZE]; + data[0..2].copy_from_slice(&EXT4_EXTENT_MAGIC.to_le_bytes()); // set magic number + let node = ExtentNode::load_from_data(&data, false).expect("Failed to load internal node"); + assert_eq!(node.header.magic, EXT4_EXTENT_MAGIC); + + // Test invalid data length for root node + let invalid_data: [u8; 10] = [0; 10]; + let result = ExtentNode::load_from_data(&invalid_data, true); + assert!(result.is_err(), "Expected error for invalid root node data length"); + + // Test invalid data length for internal node + let invalid_data: [u8; BLOCK_SIZE - 1] = [0; BLOCK_SIZE - 1]; + let result = ExtentNode::load_from_data(&invalid_data, false); + assert!(result.is_err(), "Expected error for invalid internal node data length"); + } + + #[test] + fn test_binsearch_extent() { + // Create a mock extent node + let extents = [ + Ext4Extent { + first_block: 0, + block_count: 10, + ..Default::default() + }, + Ext4Extent { + first_block: 10, + block_count: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let extent_ptr = header_ptr.add(1) as *mut Ext4Extent; + core::ptr::copy_nonoverlapping(extents.as_ptr(), extent_ptr, 2); + data + }; + + let mut node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Search for a block within the extents + let result = node.binsearch_extent(5); + assert!(result.is_some()); + let (extent, pos) = result.unwrap(); + assert_eq!(extent.first_block, 0); + assert_eq!(pos, 0); + + // Search for a block within the second extent + let result = node.binsearch_extent(15); + assert!(result.is_some()); + let (extent, pos) = result.unwrap(); + assert_eq!(extent.first_block, 10); + assert_eq!(pos, 1); + + // Search for a block outside the extents + let result = node.binsearch_extent(20); + assert!(result.is_none()); + } + + #[test] + fn test_binsearch_idx() { + // Create a mock index node + let indexes = [ + Ext4ExtentIndex { + first_block: 0, + ..Default::default() + }, + Ext4ExtentIndex { + first_block: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let index_ptr = header_ptr.add(1) as *mut Ext4ExtentIndex; + core::ptr::copy_nonoverlapping(indexes.as_ptr(), index_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Search for the closest index of the given block + let result = node.binsearch_idx(5); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 0); + + // Search for the closest index of the given block + let result = node.binsearch_idx(15); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 1); + + // Search for a block outside the indexes + let result = node.binsearch_idx(20); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 1); + } + + #[test] + fn test_get_index() { + // Create a mock index node + let indexes = [ + Ext4ExtentIndex { + first_block: 0, + leaf_lo: 1, + leaf_hi: 2, + ..Default::default() + }, + Ext4ExtentIndex { + first_block: 10, + leaf_lo: 11, + leaf_hi: 12, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let index_ptr = header_ptr.add(1) as *mut Ext4ExtentIndex; + core::ptr::copy_nonoverlapping(indexes.as_ptr(), index_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Get the index at position 0 + let index = node.get_index(0).expect("Failed to get index at position 0"); + assert_eq!(index.first_block, 0); + assert_eq!(index.leaf_lo, 1); + assert_eq!(index.leaf_hi, 2); + + // Get the index at position 1 + let index = node.get_index(1).expect("Failed to get index at position 1"); + assert_eq!(index.first_block, 10); + assert_eq!(index.leaf_lo, 11); + assert_eq!(index.leaf_hi, 12); + } + + #[test] + fn test_get_extent() { + // Create a mock extent node + let extents = [ + Ext4Extent { + first_block: 0, + block_count: 10, + ..Default::default() + }, + Ext4Extent { + first_block: 10, + block_count: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let extent_ptr = header_ptr.add(1) as *mut Ext4Extent; + core::ptr::copy_nonoverlapping(extents.as_ptr(), extent_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Get the extent at position 0 + let extent = node.get_extent(0).expect("Failed to get extent at position 0"); + assert_eq!(extent.first_block, 0); + assert_eq!(extent.block_count, 10); + + // Get the extent at position 1 + let extent = node.get_extent(1).expect("Failed to get extent at position 1"); + assert_eq!(extent.first_block, 10); + assert_eq!(extent.block_count, 10); + } +} diff --git a/lib/ext4_rs/src/ext4_defs/file.rs b/lib/ext4_rs/src/ext4_defs/file.rs new file mode 100644 index 0000000..8d95269 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/file.rs @@ -0,0 +1,163 @@ +use core::default; + +use super::*; + +pub struct FileAttr { + /// Inode number + pub ino: u64, + /// Size in bytes + pub size: u64, + /// Size in blocks + pub blocks: u64, + /// Time of last access + pub atime: u32, + /// Time of last modification + pub mtime: u32, + /// Time of last change + pub ctime: u32, + /// Time of creation (macOS only) + pub crtime: u32, + /// Time of last status change + pub chgtime: u32, + /// Backup time (macOS only) + pub bkuptime: u32, + /// Kind of file (directory, file, pipe, etc) + pub kind: InodeFileType, + /// Permissions + pub perm: InodePerm, + /// Number of hard links + pub nlink: u32, + /// User id + pub uid: u32, + /// Group id + pub gid: u32, + /// Rdev + pub rdev: u32, + /// Block size + pub blksize: u32, + /// Flags (macOS only, see chflags(2)) + pub flags: u32, +} + +impl Default for FileAttr { + fn default() -> Self { + FileAttr { + ino: 0, + size: 0, + blocks: 0, + atime: 0, + mtime: 0, + ctime: 0, + crtime: 0, + chgtime: 0, + bkuptime: 0, + kind: InodeFileType::S_IFREG, + perm: InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + flags: 0, + } + } +} + +impl FileAttr { + pub fn from_inode_ref(inode_ref: &Ext4InodeRef) -> FileAttr { + let inode_num = inode_ref.inode_num; + let inode = inode_ref.inode; + FileAttr { + ino: inode_num as u64, + size: inode.size(), + blocks: inode.blocks_count(), + atime: inode.atime(), + mtime: inode.mtime(), + ctime: inode.ctime(), + crtime: inode.i_crtime(), + // todo: chgtime, bkuptime + chgtime: 0, + bkuptime: 0, + kind: inode.file_type(), + perm: inode.file_perm(), // Extract permission bits + nlink: inode.links_count() as u32, + uid: inode.uid() as u32, + gid: inode.gid() as u32, + rdev: inode.faddr(), + blksize: BLOCK_SIZE as u32, + flags: inode.flags(), + } + } +} + +// #ifdef __i386__ +// struct stat { +// unsigned long st_dev; +// unsigned long st_ino; +// unsigned short st_mode; +// unsigned short st_nlink; +// unsigned short st_uid; +// unsigned short st_gid; +// unsigned long st_rdev; +// unsigned long st_size; +// unsigned long st_blksize; +// unsigned long st_blocks; +// unsigned long st_atime; +// unsigned long st_atime_nsec; +// unsigned long st_mtime; +// unsigned long st_mtime_nsec; +// unsigned long st_ctime; +// unsigned long st_ctime_nsec; +// unsigned long __unused4; +// unsigned long __unused5; +// }; + +#[repr(C)] +pub struct LinuxStat { + st_dev: u32, // ID of device containing file + st_ino: u32, // Inode number + st_mode: u16, // File type and mode + st_nlink: u16, // Number of hard links + st_uid: u16, // User ID of owner + st_gid: u16, // Group ID of owner + st_rdev: u32, // Device ID (if special file) + st_size: u32, // Total size, in bytes + st_blksize: u32, // Block size for filesystem I/O + st_blocks: u32, // Number of 512B blocks allocated + st_atime: u32, // Time of last access + st_atime_nsec: u32, // Nanoseconds part of last access time + st_mtime: u32, // Time of last modification + st_mtime_nsec: u32, // Nanoseconds part of last modification time + st_ctime: u32, // Time of last status change + st_ctime_nsec: u32, // Nanoseconds part of last status change time + __unused4: u32, // Unused field + __unused5: u32, // Unused field +} + +impl LinuxStat { + pub fn from_inode_ref(inode_ref: &Ext4InodeRef) -> LinuxStat { + let inode_num = inode_ref.inode_num; + let inode = &inode_ref.inode; + + LinuxStat { + st_dev: 0, + st_ino: inode_num, + st_mode: inode.mode, + st_nlink: inode.links_count(), + st_uid: inode.uid(), + st_gid: inode.gid(), + st_rdev: 0, + st_size: inode.size() as u32, + st_blksize: 4096, // 假设块大小为4096字节 + st_blocks: inode.blocks_count() as u32, + st_atime: inode.atime(), + st_atime_nsec: 0, + st_mtime: inode.mtime(), + st_mtime_nsec: 0, + st_ctime: inode.ctime(), + st_ctime_nsec: 0, + __unused4: 0, + __unused5: 0, + } + } +} diff --git a/lib/ext4_rs/src/ext4_defs/inode.rs b/lib/ext4_rs/src/ext4_defs/inode.rs new file mode 100644 index 0000000..78fca59 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/inode.rs @@ -0,0 +1,636 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Ext4Inode { + pub mode: u16, // 文件类型和权限 + pub uid: u16, // 所有者用户 ID + pub size: u32, // 低 32 位文件大小 + pub atime: u32, // 最近访问时间 + pub ctime: u32, // 创建时间 + pub mtime: u32, // 最近修改时间 + pub dtime: u32, // 删除时间 + pub gid: u16, // 所有者组 ID + pub links_count: u16, // 链接计数 + pub blocks: u32, // 已分配的块数 + pub flags: u32, // 文件标志 + pub osd1: u32, // 操作系统相关的字段1 + pub block: [u32; 15], // 数据块指针 + pub generation: u32, // 文件版本(NFS) + pub file_acl: u32, // 文件 ACL + pub size_hi: u32, // 高 32 位文件大小 + pub faddr: u32, // 已废弃的碎片地址 + pub osd2: Linux2, // 操作系统相关的字段2 + + pub i_extra_isize: u16, // 额外的 inode 大小 + pub i_checksum_hi: u16, // 高位校验和(crc32c(uuid+inum+inode) BE) + pub i_ctime_extra: u32, // 额外的创建时间(纳秒 << 2 | 纪元) + pub i_mtime_extra: u32, // 额外的修改时间(纳秒 << 2 | 纪元) + pub i_atime_extra: u32, // 额外的访问时间(纳秒 << 2 | 纪元) + pub i_crtime: u32, // 创建时间 + pub i_crtime_extra: u32, // 额外的创建时间(纳秒 << 2 | 纪元) + pub i_version_hi: u32, // 高 32 位版本 +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct Linux2 { + pub l_i_blocks_high: u16, // 高 16 位已分配块数 + pub l_i_file_acl_high: u16, // 高 16 位文件 ACL + pub l_i_uid_high: u16, // 高 16 位用户 ID + pub l_i_gid_high: u16, // 高 16 位组 ID + pub l_i_checksum_lo: u16, // 低位校验和 + pub l_i_reserved: u16, // 保留字段 +} + +bitflags! { + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub struct InodeFileType: u16 { + const S_IFIFO = 0x1000; + const S_IFCHR = 0x2000; + const S_IFDIR = 0x4000; + const S_IFBLK = 0x6000; + const S_IFREG = 0x8000; + const S_IFSOCK = 0xC000; + const S_IFLNK = 0xA000; + } +} + +bitflags! { + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub struct InodePerm: u16 { + const S_IREAD = 0x0100; + const S_IWRITE = 0x0080; + const S_IEXEC = 0x0040; + const S_ISUID = 0x0800; + const S_ISGID = 0x0400; + } +} + +impl Ext4Inode { + pub fn mode(&self) -> u16 { + self.mode + } + + pub fn set_mode(&mut self, mode: u16) { + self.mode = mode; + } + + pub fn uid(&self) -> u16 { + self.uid + } + + pub fn set_uid(&mut self, uid: u16) { + self.uid = uid; + } + + pub fn size(&self) -> u64 { + self.size as u64 | ((self.size_hi as u64) << 32) + } + + pub fn set_size(&mut self, size: u64) { + self.size = (size & 0xffffffff) as u32; + self.size_hi = (size >> 32) as u32; + } + + pub fn atime(&self) -> u32 { + self.atime + } + + pub fn set_atime(&mut self, atime: u32) { + self.atime = atime; + } + + pub fn ctime(&self) -> u32 { + self.ctime + } + + pub fn set_ctime(&mut self, ctime: u32) { + self.ctime = ctime; + } + + pub fn mtime(&self) -> u32 { + self.mtime + } + + pub fn set_mtime(&mut self, mtime: u32) { + self.mtime = mtime; + } + + pub fn dtime(&self) -> u32 { + self.dtime + } + + pub fn set_dtime(&mut self, dtime: u32) { + self.dtime = dtime; + } + + pub fn gid(&self) -> u16 { + self.gid + } + + pub fn set_gid(&mut self, gid: u16) { + self.gid = gid; + } + + pub fn links_count(&self) -> u16 { + self.links_count + } + + pub fn set_links_count(&mut self, links_count: u16) { + self.links_count = links_count; + } + + pub fn blocks_count(&self) -> u64 { + let mut blocks = self.blocks as u64; + if self.osd2.l_i_blocks_high != 0 { + blocks |= (self.osd2.l_i_blocks_high as u64) << 32; + } + blocks + } + + pub fn set_blocks_count(&mut self, blocks: u64) { + self.blocks = (blocks & 0xFFFFFFFF) as u32; + self.osd2.l_i_blocks_high = (blocks >> 32) as u16; + } + + pub fn flags(&self) -> u32 { + self.flags + } + + pub fn set_flags(&mut self, flags: u32) { + self.flags = flags; + } + + pub fn osd1(&self) -> u32 { + self.osd1 + } + + pub fn set_osd1(&mut self, osd1: u32) { + self.osd1 = osd1; + } + + pub fn block(&self) -> [u32; 15] { + self.block + } + + pub fn set_block(&mut self, block: [u32; 15]) { + self.block = block; + } + + pub fn generation(&self) -> u32 { + self.generation + } + + pub fn set_generation(&mut self, generation: u32) { + self.generation = generation; + } + + pub fn file_acl(&self) -> u32 { + self.file_acl + } + + pub fn set_file_acl(&mut self, file_acl: u32) { + self.file_acl = file_acl; + } + + pub fn size_hi(&self) -> u32 { + self.size_hi + } + + pub fn set_size_hi(&mut self, size_hi: u32) { + self.size_hi = size_hi; + } + + pub fn faddr(&self) -> u32 { + self.faddr + } + + pub fn set_faddr(&mut self, faddr: u32) { + self.faddr = faddr; + } + + pub fn osd2(&self) -> Linux2 { + self.osd2 + } + + pub fn set_osd2(&mut self, osd2: Linux2) { + self.osd2 = osd2; + } + + pub fn i_extra_isize(&self) -> u16 { + self.i_extra_isize + } + + pub fn set_i_extra_isize(&mut self, i_extra_isize: u16) { + self.i_extra_isize = i_extra_isize; + } + + pub fn i_checksum_hi(&self) -> u16 { + self.i_checksum_hi + } + + pub fn set_i_checksum_hi(&mut self, i_checksum_hi: u16) { + self.i_checksum_hi = i_checksum_hi; + } + + pub fn i_ctime_extra(&self) -> u32 { + self.i_ctime_extra + } + + pub fn set_i_ctime_extra(&mut self, i_ctime_extra: u32) { + self.i_ctime_extra = i_ctime_extra; + } + + pub fn i_mtime_extra(&self) -> u32 { + self.i_mtime_extra + } + + pub fn set_i_mtime_extra(&mut self, i_mtime_extra: u32) { + self.i_mtime_extra = i_mtime_extra; + } + + pub fn i_atime_extra(&self) -> u32 { + self.i_atime_extra + } + + pub fn set_i_atime_extra(&mut self, i_atime_extra: u32) { + self.i_atime_extra = i_atime_extra; + } + + pub fn i_crtime(&self) -> u32 { + self.i_crtime + } + + pub fn set_i_crtime(&mut self, i_crtime: u32) { + self.i_crtime = i_crtime; + } + + pub fn i_crtime_extra(&self) -> u32 { + self.i_crtime_extra + } + + pub fn set_i_crtime_extra(&mut self, i_crtime_extra: u32) { + self.i_crtime_extra = i_crtime_extra; + } + + pub fn i_version_hi(&self) -> u32 { + self.i_version_hi + } + + pub fn set_i_version_hi(&mut self, i_version_hi: u32) { + self.i_version_hi = i_version_hi; + } +} + +impl Ext4Inode { + pub fn file_type(&self) -> InodeFileType { + InodeFileType::from_bits_truncate(self.mode & EXT4_INODE_MODE_TYPE_MASK) + } + + pub fn file_perm(&self) -> InodePerm { + InodePerm::from_bits_truncate(self.mode & EXT4_INODE_MODE_PERM_MASK) + } + + pub fn is_dir(&self) -> bool { + self.file_type() == InodeFileType::S_IFDIR + } + + pub fn is_file(&self) -> bool { + self.file_type() == InodeFileType::S_IFREG + } + + pub fn is_link(&self) -> bool { + self.file_type() == InodeFileType::S_IFLNK + } + + pub fn can_read(&self) -> bool { + self.file_perm().contains(InodePerm::S_IREAD) + } + + pub fn can_write(&self) -> bool { + self.file_perm().contains(InodePerm::S_IWRITE) + } + + pub fn can_exec(&self) -> bool { + self.file_perm().contains(InodePerm::S_IEXEC) + } + + pub fn set_file_type(&mut self, kind: InodeFileType) { + self.mode |= kind.bits(); + } + + pub fn set_file_perm(&mut self, perm: InodePerm) { + self.mode |= perm.bits(); + } +} + +/// Reference to an inode. +#[derive(Clone)] +pub struct Ext4InodeRef { + pub inode_num: u32, + pub inode: Ext4Inode, +} + +impl Ext4Inode { + /// Get the depth of the extent tree from an inode. + pub fn root_header_depth(&self) -> u16 { + self.root_extent_header().depth + } + + pub fn root_extent_header_ref(&self) -> &Ext4ExtentHeader { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { &*header_ptr } + } + + pub fn root_extent_header(&self) -> Ext4ExtentHeader { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { *header_ptr } + } + + pub fn root_extent_header_mut(&mut self) -> &mut Ext4ExtentHeader { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *header_ptr } + } + + pub fn root_extent_mut_at(&mut self, pos: usize) -> &mut Ext4Extent { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *(header_ptr.add(1) as *mut Ext4Extent).add(pos) } + } + + pub fn root_extent_ref_at(&mut self, pos: usize) -> &Ext4Extent { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { &*(header_ptr.add(1) as *const Ext4Extent).add(pos) } + } + + pub fn root_extent_at(&mut self, pos: usize) -> Ext4Extent { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { *(header_ptr.add(1) as *const Ext4Extent).add(pos) } + } + + pub fn root_first_index_mut(&mut self) -> &mut Ext4ExtentIndex { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *(header_ptr.add(1) as *mut Ext4ExtentIndex) } + } + + pub fn extent_tree_init(&mut self) { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { + (*header_ptr).set_magic(); + (*header_ptr).set_entries_count(0); + (*header_ptr).set_max_entries_count(4); + (*header_ptr).set_depth(0); + (*header_ptr).set_generation(0); + } + } + + fn get_checksum(&self, super_block: &Ext4Superblock) -> u32 { + let inode_size = super_block.inode_size; + let mut v: u32 = self.osd2.l_i_checksum_lo as u32; + if inode_size > 128 { + v |= (self.i_checksum_hi as u32) << 16; + } + v + } + #[allow(unused)] + pub fn set_inode_checksum_value( + &mut self, + super_block: &Ext4Superblock, + inode_id: u32, + checksum: u32, + ) { + let inode_size = super_block.inode_size(); + + self.osd2.l_i_checksum_lo = (checksum & 0xffff) as u16; + if inode_size > 128 { + self.i_checksum_hi = (checksum >> 16) as u16; + } + } + fn copy_to_slice(&self, slice: &mut [u8]) { + unsafe { + let inode_ptr = self as *const Ext4Inode as *const u8; + let array_ptr = slice.as_ptr() as *mut u8; + core::ptr::copy_nonoverlapping(inode_ptr, array_ptr, 0x9c); + } + } + #[allow(unused)] + pub fn get_inode_checksum(&mut self, inode_id: u32, super_block: &Ext4Superblock) -> u32 { + let inode_size = super_block.inode_size(); + + let orig_checksum = self.get_checksum(super_block); + let mut checksum = 0; + + let ino_index = inode_id; + let ino_gen = self.generation; + + // Preparation: temporarily set bg checksum to 0 + self.osd2.l_i_checksum_lo = 0; + self.i_checksum_hi = 0; + + checksum = ext4_crc32c( + EXT4_CRC32_INIT, + &super_block.uuid, + super_block.uuid.len() as u32, + ); + checksum = ext4_crc32c(checksum, &ino_index.to_le_bytes(), 4); + checksum = ext4_crc32c(checksum, &ino_gen.to_le_bytes(), 4); + + let mut raw_data = [0u8; 0x100]; + self.copy_to_slice(&mut raw_data); + + // inode checksum + checksum = ext4_crc32c(checksum, &raw_data, inode_size as u32); + + self.set_inode_checksum_value(super_block, inode_id, checksum); + + if inode_size == 128 { + checksum &= 0xFFFF; + } + + checksum + } + + pub fn set_inode_checksum(&mut self, super_block: &Ext4Superblock, inode_id: u32) { + let inode_size = super_block.inode_size(); + let checksum = self.get_inode_checksum(inode_id, super_block); + + self.osd2.l_i_checksum_lo = ((checksum << 16) >> 16) as u16; + if inode_size > 128 { + self.i_checksum_hi = (checksum >> 16) as u16; + } + } + + pub fn sync_inode_to_disk(&self, block_device: Arc, inode_pos: usize) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(inode_pos, data); + } +} + +impl Ext4InodeRef { + pub fn set_attr(&mut self, attr: &FileAttr) { + self.inode.set_size(attr.size); + self.inode.set_blocks_count(attr.blocks); + self.inode.set_atime(attr.atime); + self.inode.set_mtime(attr.mtime); + self.inode.set_ctime(attr.ctime); + self.inode.set_i_crtime(attr.crtime); + self.inode.set_file_type(attr.kind); + self.inode.set_file_perm(attr.perm); + self.inode.set_links_count(attr.nlink as u16); + self.inode.set_uid(attr.uid as u16); + self.inode.set_gid(attr.gid as u16); + self.inode.set_faddr(attr.rdev); + self.inode.set_flags(attr.flags); + } +} + +impl Ext4Inode { + // access() does not answer the "can I read/write/execute + // this file?" question. It answers a slightly different question: + // "(assuming I'm a setuid binary) can the user who invoked me + // read/write/execute this file?", which gives set-user-ID programs + // the possibility to prevent malicious users from causing them to + // read files which users shouldn't be able to read. + // https://man7.org/linux/man-pages/man2/access.2.html + // Check if a user can access the inode with the given UID, GID, and umask + pub fn check_access(&self, uid: u16, gid: u16, access_mode: u16, umask: u16) -> bool { + // Extract the owner, group, and other permission bits from the inode's mode + let owner_perm = (self.mode & 0o700) >> 6; + let group_perm = (self.mode & 0o070) >> 3; + let other_perm = self.mode & 0o007; + + // Determine which permission bits to check based on the given UID and GID + let perm = if self.uid == uid { + owner_perm + } else if self.gid == gid { + group_perm + } else { + other_perm + }; + + // Adjust the permission bits based on the umask + let adjusted_perm = perm & !((umask & 0o700) >> 6); + + // Check if the adjusted permission bits allow the requested access + let check_read = + (access_mode & R_OK as u16) == 0 || (adjusted_perm & R_OK as u16) == R_OK as u16; + let check_write = + (access_mode & W_OK as u16) == 0 || (adjusted_perm & W_OK as u16) == W_OK as u16; + let check_execute = + (access_mode & X_OK as u16) == 0 || (adjusted_perm & X_OK as u16) == X_OK as u16; + + check_read && check_write && check_execute + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_check_access_owner() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1000; + let gid = 1000; + let umask = 0o022; // Default umask + let access_mode = R_OK | X_OK; + + assert!(inode.check_access(uid, gid, umask, access_mode as u16)); + } + + #[test] + fn test_check_access_group() { + let inode = Ext4Inode { + mode: 0o750, // rwxr-x--- + uid: 1000, + gid: 1001, + ..Default::default() + }; + + let uid = 1002; + let gid = 1001; + let umask = 0o022; // Default umask + let access_mode = R_OK | X_OK; + + assert!(inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_check_access_other() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1002; + let gid = 1003; + let umask = 0o022; // Default umask + let access_mode = R_OK; + + assert!(inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_check_access_denied() { + let inode = Ext4Inode { + mode: 0o700, // rwx------ + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1002; + let gid = 1003; + let umask = 0o022; // Default umask + let access_mode = R_OK; + + assert!(!inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_file_type() { + let inode = Ext4Inode { + mode: 0x8000, // Regular file + ..Default::default() + }; + assert!(inode.is_file()); + assert!(!inode.is_dir()); + assert!(!inode.is_link()); + } + + #[test] + fn test_file_permissions() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + ..Default::default() + }; + assert!(inode.can_read()); + assert!(inode.can_write()); + assert!(inode.can_exec()); + } + + #[test] + fn test_set_file_type_and_perm() { + let mut inode = Ext4Inode { + mode: 0, + ..Default::default() + }; + inode.set_file_type(InodeFileType::S_IFREG); + assert_eq!(inode.mode, InodeFileType::S_IFREG.bits()); // Regular file with rwx permissions + inode.set_file_perm(InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC); + assert_eq!(inode.mode, InodeFileType::S_IFREG.bits() | (InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC).bits()); // Regular file with rwx permissions + } +} diff --git a/lib/ext4_rs/src/ext4_defs/mod.rs b/lib/ext4_rs/src/ext4_defs/mod.rs new file mode 100644 index 0000000..7bec257 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/mod.rs @@ -0,0 +1,23 @@ +pub mod consts; +pub mod block_group; +pub mod direntry; +pub mod block; +pub mod file; +pub mod extents; +pub mod inode; +pub mod mount_point; +pub mod super_block; +pub mod ext4; + + + +pub use consts::*; +pub use block_group::*; +pub use direntry::*; +pub use block::*; +pub use file::*; +pub use extents::*; +pub use inode::*; +pub use mount_point::*; +pub use super_block::*; +pub use ext4::*; \ No newline at end of file diff --git a/lib/ext4_rs/src/ext4_defs/mount_point.rs b/lib/ext4_rs/src/ext4_defs/mount_point.rs new file mode 100644 index 0000000..eebfc25 --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/mount_point.rs @@ -0,0 +1,22 @@ +use crate::prelude::*; + +#[derive(Clone)] +pub struct Ext4MountPoint { + /// Mount done flag. + pub mounted: bool, + /// Mount point name + pub mount_name: String, +} +impl Ext4MountPoint { + pub fn new(name: &str) -> Self { + Self { + mounted: false, + mount_name: String::from(name), + } + } +} +impl Debug for Ext4MountPoint { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Ext4MountPoint {{ mount_name: {:?} }}", self.mount_name) + } +} diff --git a/lib/ext4_rs/src/ext4_defs/super_block.rs b/lib/ext4_rs/src/ext4_defs/super_block.rs new file mode 100644 index 0000000..4e0774e --- /dev/null +++ b/lib/ext4_rs/src/ext4_defs/super_block.rs @@ -0,0 +1,253 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ext4Superblock { + pub inodes_count: u32, // 节点数 + blocks_count_lo: u32, // 块数 + reserved_blocks_count_lo: u32, // 保留块数 + free_blocks_count_lo: u32, // 空闲块数 + free_inodes_count: u32, // 空闲节点数 + pub first_data_block: u32, // 第一个数据块 + log_block_size: u32, // 块大小 + log_cluster_size: u32, // 废弃的片段大小 + blocks_per_group: u32, // 每组块数 + frags_per_group: u32, // 废弃的每组片段数 + pub inodes_per_group: u32, // 每组节点数 + mount_time: u32, // 挂载时间 + write_time: u32, // 写入时间 + mount_count: u16, // 挂载次数 + max_mount_count: u16, // 最大挂载次数 + magic: u16, // 魔数,0xEF53 + state: u16, // 文件系统状态 + errors: u16, // 检测到错误时的行为 + minor_rev_level: u16, // 次版本号 + last_check_time: u32, // 最后检查时间 + check_interval: u32, // 检查间隔 + pub creator_os: u32, // 创建者操作系统 + pub rev_level: u32, // 版本号 + def_resuid: u16, // 保留块的默认uid + def_resgid: u16, // 保留块的默认gid + + // 仅适用于EXT4_DYNAMIC_REV超级块的字段 + first_inode: u32, // 第一个非保留节点 + pub inode_size: u16, // 节点结构的大小 + block_group_index: u16, // 此超级块的块组索引 + features_compatible: u32, // 兼容特性集 + features_incompatible: u32, // 不兼容特性集 + pub features_read_only: u32, // 只读兼容特性集 + pub uuid: [u8; 16], // 卷的128位uuid + volume_name: [u8; 16], // 卷名 + last_mounted: [u8; 64], // 最后挂载的目录 + algorithm_usage_bitmap: u32, // 用于压缩的算法 + + // 性能提示。只有当EXT4_FEATURE_COMPAT_DIR_PREALLOC标志打开时,才进行目录预分配 + s_prealloc_blocks: u8, // 尝试预分配的块数 + s_prealloc_dir_blocks: u8, // 为目录预分配的块数 + s_reserved_gdt_blocks: u16, // 在线增长时每组保留的描述符数 + + // 如果EXT4_FEATURE_COMPAT_HAS_JOURNAL设置,表示支持日志 + journal_uuid: [u8; 16], // 日志超级块的UUID + journal_inode_number: u32, // 日志文件的节点号 + journal_dev: u32, // 日志文件的设备号 + last_orphan: u32, // 待删除节点的链表头 + hash_seed: [u32; 4], // HTREE散列种子 + default_hash_version: u8, // 默认的散列版本 + journal_backup_type: u8, + pub desc_size: u16, // 组描述符的大小 + default_mount_opts: u32, // 默认的挂载选项 + first_meta_bg: u32, // 第一个元数据块组 + mkfs_time: u32, // 文件系统创建的时间 + journal_blocks: [u32; 17], // 日志节点的备份 + + // 如果EXT4_FEATURE_COMPAT_64BIT设置,表示支持64位 + blocks_count_hi: u32, // 块数 + reserved_blocks_count_hi: u32, // 保留块数 + free_blocks_count_hi: u32, // 空闲块数 + min_extra_isize: u16, // 所有节点至少有#字节 + want_extra_isize: u16, // 新节点应该保留#字节 + flags: u32, // 杂项标志 + raid_stride: u16, // RAID步长 + mmp_interval: u16, // MMP检查的等待秒数 + mmp_block: u64, // 多重挂载保护的块 + raid_stripe_width: u32, // 所有数据磁盘上的块数(N * 步长) + log_groups_per_flex: u8, // FLEX_BG组的大小 + checksum_type: u8, + reserved_pad: u16, + kbytes_written: u64, // 写入的千字节数 + snapshot_inum: u32, // 活动快照的节点号 + snapshot_id: u32, // 活动快照的顺序ID + snapshot_r_blocks_count: u64, // 为活动快照的未来使用保留的块数 + snapshot_list: u32, // 磁盘上快照列表的头节点号 + error_count: u32, // 文件系统错误的数目 + first_error_time: u32, // 第一次发生错误的时间 + first_error_ino: u32, // 第一次发生错误的节点号 + first_error_block: u64, // 第一次发生错误的块号 + first_error_func: [u8; 32], // 第一次发生错误的函数 + first_error_line: u32, // 第一次发生错误的行号 + last_error_time: u32, // 最近一次发生错误的时间 + last_error_ino: u32, // 最近一次发生错误的节点号 + last_error_line: u32, // 最近一次发生错误的行号 + last_error_block: u64, // 最近一次发生错误的块号 + last_error_func: [u8; 32], // 最近一次发生错误的函数 + mount_opts: [u8; 64], + usr_quota_inum: u32, // 用于跟踪用户配额的节点 + grp_quota_inum: u32, // 用于跟踪组配额的节点 + overhead_clusters: u32, // 文件系统中的开销块/簇 + backup_bgs: [u32; 2], // 有sparse_super2超级块的组 + encrypt_algos: [u8; 4], // 使用的加密算法 + encrypt_pw_salt: [u8; 16], // 用于string2key算法的盐 + lpf_ino: u32, // lost+found节点的位置 + padding: [u32; 100], // 块的末尾的填充 + checksum: u32, // crc32c(superblock) +} + +impl Ext4Superblock { + /// Returns the size of inode structure. + pub fn inode_size(&self) -> u16 { + self.inode_size + } + + /// Returns the size of inode structure. + pub fn inode_size_file(&self, inode: &Ext4Inode) -> u64 { + let mode = inode.mode; + let mut v = inode.size as u64; + if self.rev_level > 0 && (mode & EXT4_INODE_MODE_TYPE_MASK) == EXT4_INODE_MODE_FILE as u16 { + let hi = (inode.size_hi as u64) << 32; + v |= hi; + } + v + } + + pub fn free_inodes_count(&self) -> u32 { + self.free_inodes_count + } + + /// Returns total number of inodes. + pub fn total_inodes(&self) -> u32 { + self.inodes_count + } + + /// Returns the number of blocks in each block group. + pub fn blocks_per_group(&self) -> u32 { + self.blocks_per_group + } + + /// Returns the size of block. + pub fn block_size(&self) -> u32 { + 1024 << self.log_block_size + } + + /// Returns the number of inodes in each block group. + pub fn inodes_per_group(&self) -> u32 { + self.inodes_per_group + } + + /// Returns the first data block. + pub fn first_data_block(&self) -> u32{ + self.first_data_block + } + + /// Returns the number of block groups. + pub fn block_group_count(&self) -> u32 { + let blocks_count = (self.blocks_count_hi as u64) << 32 | self.blocks_count_lo as u64; + + let blocks_per_group = self.blocks_per_group as u64; + + let mut block_group_count = blocks_count / blocks_per_group; + + if (blocks_count % blocks_per_group) != 0 { + block_group_count += 1; + } + + block_group_count as u32 + } + + pub fn blocks_count(&self) -> u32 { + ((self.blocks_count_hi.to_le() as u64) << 32) as u32 | self.blocks_count_lo + } + + pub fn desc_size(&self) -> u16 { + let size = self.desc_size; + + if size < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + } else { + size + } + } + + pub fn extra_size(&self) -> u16 { + self.want_extra_isize + } + + pub fn get_inodes_in_group_cnt(&self, bgid: u32) -> u32 { + let block_group_count = self.block_group_count(); + let inodes_per_group = self.inodes_per_group; + + let total_inodes = self.inodes_count; + if bgid < block_group_count - 1 { + inodes_per_group + } else { + total_inodes - ((block_group_count - 1) * inodes_per_group) + } + } + + pub fn decrease_free_inodes_count(&mut self) { + self.free_inodes_count -= 1; + } + + pub fn free_blocks_count(&self) -> u64 { + self.free_blocks_count_lo as u64 | ((self.free_blocks_count_hi as u64) << 32).to_le() + } + + pub fn set_free_blocks_count(&mut self, free_blocks: u64) { + self.free_blocks_count_lo = (free_blocks & 0xffffffff) as u32; + + self.free_blocks_count_hi = (free_blocks >> 32) as u32; + } + + pub fn sync_to_disk(&self, block_device: Arc) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(SUPERBLOCK_OFFSET, data); + } + + pub fn sync_to_disk_with_csum(&mut self, block_device: Arc) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + let checksum = ext4_crc32c(EXT4_CRC32_INIT, data, 0x3fc); + + self.checksum = checksum; + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(SUPERBLOCK_OFFSET, data); + } +} + +impl Ext4Superblock { + /// Returns the checksum of the block bitmap + pub fn ext4_balloc_bitmap_csum(&self, bitmap: &[u8]) -> u32 { + let mut csum = 0; + let blocks_per_group = self.blocks_per_group; + let uuid = self.uuid; + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, bitmap, blocks_per_group / 8); + csum + } + + /// Returns the checksum of the inode bitmap + pub fn ext4_ialloc_bitmap_csum(&self, bitmap: &[u8]) -> u32 { + let mut csum = 0; + let inodes_per_group = self.inodes_per_group; + let uuid = self.uuid; + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, bitmap, (inodes_per_group + 7) / 8); + csum + } +} diff --git a/lib/ext4_rs/src/ext4_impls/balloc.rs b/lib/ext4_rs/src/ext4_impls/balloc.rs new file mode 100644 index 0000000..57e2d4a --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/balloc.rs @@ -0,0 +1,388 @@ +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; + +impl Ext4 { + /// Compute number of block group from block address. + /// + /// Params: + /// + /// `baddr` - Absolute address of block. + /// + /// # Returns + /// `u32` - Block group index + pub fn get_bgid_of_block(&self, baddr: u64) -> u32 { + let mut baddr = baddr; + if self.super_block.first_data_block() != 0 && baddr != 0 { + baddr -= 1; + } + (baddr / self.super_block.blocks_per_group() as u64) as u32 + } + + /// Compute the starting block address of a block group. + /// + /// Params: + /// `bgid` - Block group index + /// + /// Returns: + /// `u64` - Block address + pub fn get_block_of_bgid(&self, bgid: u32) -> u64 { + let mut baddr = 0; + if self.super_block.first_data_block() != 0 { + baddr += 1; + } + baddr + bgid as u64 * self.super_block.blocks_per_group() as u64 + } + + /// Convert block address to relative index in block group. + /// + /// Params: + /// `baddr` - Block number to convert. + /// + /// Returns: + /// `u32` - Relative number of block. + pub fn addr_to_idx_bg(&self, baddr: u64) -> u32 { + let mut baddr = baddr; + if self.super_block.first_data_block() != 0 && baddr != 0 { + baddr -= 1; + } + (baddr % self.super_block.blocks_per_group() as u64) as u32 + } + + /// Convert relative block address in group to absolute address. + /// + /// # Arguments + /// + /// * `index` - Relative block address. + /// * `bgid` - Block group. + /// + /// # Returns + /// + /// * `Ext4Fsblk` - Absolute block address. + pub fn bg_idx_to_addr(&self, index: u32, bgid: u32) -> Ext4Fsblk { + let mut index = index; + if self.super_block.first_data_block() != 0 { + index += 1; + } + (self.super_block.blocks_per_group() as u64 * bgid as u64) + index as u64 + } + + + /// Allocate a new block. + /// + /// Params: + /// `inode_ref` - Reference to the inode. + /// `goal` - Absolute address of the block. + /// + /// Returns: + /// `Result` - The physical block number allocated. + pub fn balloc_alloc_block( + &self, + inode_ref: &mut Ext4InodeRef, + goal: Option, + ) -> Result { + let mut alloc: Ext4Fsblk = 0; + let super_block = &self.super_block; + let blocks_per_group = super_block.blocks_per_group(); + let mut bgid; + let mut idx_in_bg; + + if let Some(goal) = goal { + bgid = self.get_bgid_of_block(goal); + idx_in_bg = self.addr_to_idx_bg(goal); + } else { + bgid = 1; + idx_in_bg = 0; + } + + let block_group_count = super_block.block_group_count(); + let mut count = block_group_count; + + while count > 0 { + // Load block group reference + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), super_block, bgid as usize); + + let free_blocks = block_group.get_free_blocks_count(); + if free_blocks == 0 { + // Try next block group + bgid = (bgid + 1) % block_group_count; + count -= 1; + + if count == 0 { + log::trace!("No free blocks available in all block groups"); + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + continue; + } + + // Compute indexes + let first_in_bg = self.get_block_of_bgid(bgid); + let first_in_bg_index = self.addr_to_idx_bg(first_in_bg); + + if idx_in_bg < first_in_bg_index { + idx_in_bg = first_in_bg_index; + } + + // Load block with bitmap + let bmp_blk_adr = block_group.get_block_bitmap_block(super_block); + let mut bitmap_block = + Block::load(&self.block_device, bmp_blk_adr as usize * BLOCK_SIZE); + + // Check if goal is free + if ext4_bmap_is_bit_clr(&bitmap_block.data, idx_in_bg) { + ext4_bmap_bit_set(&mut bitmap_block.data, idx_in_bg); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(idx_in_bg, bgid); + + /* Update free block counts */ + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + + // Try to find free block near to goal + let blk_in_bg = blocks_per_group; + let end_idx = min((idx_in_bg + 63) & !63, blk_in_bg); + + for tmp_idx in (idx_in_bg + 1)..end_idx { + if ext4_bmap_is_bit_clr(&bitmap_block.data, tmp_idx) { + ext4_bmap_bit_set(&mut bitmap_block.data, tmp_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(tmp_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + } + + // Find free bit in bitmap + let mut rel_blk_idx = 0; + if ext4_bmap_bit_find_clr(&bitmap_block.data, idx_in_bg, blk_in_bg, &mut rel_blk_idx) { + ext4_bmap_bit_set(&mut bitmap_block.data, rel_blk_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(rel_blk_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + + // No free block found in this group, try other block groups + bgid = (bgid + 1) % block_group_count; + count -= 1; + } + + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + + /// Allocate a new block start from a specific bgid + /// + /// Params: + /// `inode_ref` - Reference to the inode. + /// `start_bgid` - Start bgid of free block search + /// + /// Returns: + /// `Result` - The physical block number allocated. + pub fn balloc_alloc_block_from( + &self, + inode_ref: &mut Ext4InodeRef, + start_bgid: &mut u32, + ) -> Result { + let mut alloc: Ext4Fsblk = 0; + let super_block = &self.super_block; + let blocks_per_group = super_block.blocks_per_group(); + + let mut bgid = *start_bgid; + let mut idx_in_bg = 0; + + + let block_group_count = super_block.block_group_count(); + let mut count = block_group_count; + + while count > 0 { + // Load block group reference + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), super_block, bgid as usize); + + let free_blocks = block_group.get_free_blocks_count(); + if free_blocks == 0 { + // Try next block group + bgid = (bgid + 1) % block_group_count; + count -= 1; + + if count == 0 { + log::trace!("No free blocks available in all block groups"); + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + continue; + } + + // Compute indexes + let first_in_bg = self.get_block_of_bgid(bgid); + let first_in_bg_index = self.addr_to_idx_bg(first_in_bg); + + if idx_in_bg < first_in_bg_index { + idx_in_bg = first_in_bg_index; + } + + // Load block with bitmap + let bmp_blk_adr = block_group.get_block_bitmap_block(super_block); + let mut bitmap_block = + Block::load(&self.block_device, bmp_blk_adr as usize * BLOCK_SIZE); + + // Check if goal is free + if ext4_bmap_is_bit_clr(&bitmap_block.data, idx_in_bg) { + ext4_bmap_bit_set(&mut bitmap_block.data, idx_in_bg); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(idx_in_bg, bgid); + + /* Update free block counts */ + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + + // Try to find free block near to goal + let blk_in_bg = blocks_per_group; + let end_idx = min((idx_in_bg + 63) & !63, blk_in_bg); + + for tmp_idx in (idx_in_bg + 1)..end_idx { + if ext4_bmap_is_bit_clr(&bitmap_block.data, tmp_idx) { + ext4_bmap_bit_set(&mut bitmap_block.data, tmp_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(tmp_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + } + + // Find free bit in bitmap + let mut rel_blk_idx = 0; + if ext4_bmap_bit_find_clr(&bitmap_block.data, idx_in_bg, blk_in_bg, &mut rel_blk_idx) { + ext4_bmap_bit_set(&mut bitmap_block.data, rel_blk_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(rel_blk_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + + // No free block found in this group, try other block groups + bgid = (bgid + 1) % block_group_count; + count -= 1; + } + + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + + fn update_free_block_counts( + &self, + inode_ref: &mut Ext4InodeRef, + block_group: &mut Ext4BlockGroup, + bgid: usize, + ) -> Result<()> { + let mut super_block = self.super_block; + let block_size = BLOCK_SIZE as u64; + + // Update superblock free blocks count + let mut super_blk_free_blocks = super_block.free_blocks_count(); + super_blk_free_blocks -= 1; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + // Update inode blocks (different block size!) count + let mut inode_blocks = inode_ref.inode.blocks_count(); + inode_blocks += block_size / EXT4_INODE_BLOCK_SIZE as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + // Update block group free blocks count + let mut fb_cnt = block_group.get_free_blocks_count(); + fb_cnt -= 1; + block_group.set_free_blocks_count(fb_cnt as u32); + block_group.sync_to_disk_with_csum(self.block_device.clone(), bgid, &super_block); + + Ok(()) + } + + #[allow(unused)] + pub fn balloc_free_blocks(&self, inode_ref: &mut Ext4InodeRef, start: Ext4Fsblk, count: u32) { + // log::trace!("balloc_free_blocks start {:x?} count {:x?}", start, count); + let mut count = count as usize; + let mut start = start; + + let mut super_block = self.super_block; + + let blocks_per_group = super_block.blocks_per_group(); + + let bgid = start / blocks_per_group as u64; + + let mut bg_first = start / blocks_per_group as u64; + let mut bg_last = (start + count as u64 - 1) / blocks_per_group as u64; + + while bg_first <= bg_last { + let idx_in_bg = start % blocks_per_group as u64; + + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let block_bitmap_block = bg.get_block_bitmap_block(&super_block); + let mut raw_data = self + .block_device + .read_offset(block_bitmap_block as usize * BLOCK_SIZE); + let mut data: &mut Vec = &mut raw_data; + + let mut free_cnt = BLOCK_SIZE * 8 - idx_in_bg as usize; + + if count > free_cnt { + } else { + free_cnt = count; + } + + ext4_bmap_bits_free(data, idx_in_bg as u32, free_cnt as u32); + + count -= free_cnt; + start += free_cnt as u64; + + bg.set_block_group_balloc_bitmap_csum(&super_block, data); + self.block_device + .write_offset(block_bitmap_block as usize * BLOCK_SIZE, data); + + /* Update superblock free blocks count */ + let mut super_blk_free_blocks = super_block.free_blocks_count(); + + super_blk_free_blocks += free_cnt as u64; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Update inode blocks (different block size!) count */ + let mut inode_blocks = inode_ref.inode.blocks_count(); + + inode_blocks -= (free_cnt * (BLOCK_SIZE / EXT4_INODE_BLOCK_SIZE)) as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + /* Update block group free blocks count */ + let mut fb_cnt = bg.get_free_blocks_count(); + fb_cnt += free_cnt as u64; + bg.set_free_blocks_count(fb_cnt as u32); + bg.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + bg_first += 1; + } + } +} diff --git a/lib/ext4_rs/src/ext4_impls/dir.rs b/lib/ext4_rs/src/ext4_impls/dir.rs new file mode 100644 index 0000000..11fc832 --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/dir.rs @@ -0,0 +1,438 @@ +use crate::prelude::*; +use crate::return_errno_with_message; + +use crate::ext4_defs::*; + +impl Ext4 { + /// Find a directory entry in a directory + /// + /// Params: + /// parent_inode: u32 - inode number of the parent directory + /// name: &str - name of the entry to find + /// result: &mut Ext4DirSearchResult - result of the search + /// + /// Returns: + /// `Result` - status of the search + pub fn dir_find_entry( + &self, + parent_inode: u32, + name: &str, + result: &mut Ext4DirSearchResult, + ) -> Result { + // load parent inode + let parent = self.get_inode_ref(parent_inode); + assert!(parent.inode.is_dir()); + + // start from the first logical block + let mut iblock = 0; + // physical block id + let mut fblock: Ext4Fsblk = 0; + + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let total_blocks: u64 = inode_size / BLOCK_SIZE as u64; + + // iterate all blocks + while iblock < total_blocks { + let search_path = self.find_extent(&parent, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + fblock = path.pblock; + + // load physical block + let mut ext4block = + Block::load(&self.block_device, fblock as usize * BLOCK_SIZE); + + // find entry in block + let r = self.dir_find_in_block(&ext4block, name, result); + + if r.is_ok() { + result.pblock_id = fblock as usize; + return Ok(EOK); + } + } else { + return_errno_with_message!(Errno::ENOENT, "dir search fail") + } + // go to next block + iblock += 1 + } + + return_errno_with_message!(Errno::ENOENT, "dir search fail"); + } + + /// Find a directory entry in a block + /// + /// Params: + /// block: &mut Block - block to search in + /// name: &str - name of the entry to find + /// + /// Returns: + /// result: Ext4DirEntry - result of the search + pub fn dir_find_in_block( + &self, + block: &Block, + name: &str, + result: &mut Ext4DirSearchResult, + ) -> Result { + let mut offset = 0; + let mut prev_de_offset = 0; + + // start from the first entry + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = block.read_offset_as(offset); + if !de.unused() && de.compare_name(name) { + result.dentry = de; + result.offset = offset; + result.prev_offset = prev_de_offset; + return Ok(de); + } + + prev_de_offset = offset; + // go to next entry + offset += de.entry_len() as usize; + } + return_errno_with_message!(Errno::ENOENT, "dir find in block failed"); + } + + /// Get dir entries of a inode + /// + /// Params: + /// inode: u32 - inode number of the directory + /// + /// Returns: + /// `Vec` - list of directory entries + pub fn dir_get_entries(&self, inode: u32) -> Vec { + let mut entries = Vec::new(); + + // load inode + let inode_ref = self.get_inode_ref(inode); + assert!(inode_ref.inode.is_dir()); + + // calculate total blocks + let inode_size = inode_ref.inode.size(); + let total_blocks = inode_size / BLOCK_SIZE as u64; + + // start from the first logical block + let mut iblock = 0; + + // iterate all blocks + while iblock < total_blocks { + // get physical block id of a logical block id + let search_path = self.find_extent(&inode_ref, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + let fblock = path.pblock; + + // load physical block + let ext4block = + Block::load(&self.block_device, fblock as usize * BLOCK_SIZE); + let mut offset = 0; + + // iterate all entries in a block + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = ext4block.read_offset_as(offset); + if !de.unused() { + entries.push(de); + } + offset += de.entry_len() as usize; + } + } + + // go ot next block + iblock += 1; + } + entries + } + + pub fn dir_set_csum(&self, dst_blk: &mut Block, ino_gen: u32) { + let parent_de: Ext4DirEntry = dst_blk.read_offset_as(0); + + let tail_offset = BLOCK_SIZE - size_of::(); + let mut tail: Ext4DirEntryTail = *dst_blk.read_offset_as_mut(tail_offset); + + tail.tail_set_csum(&self.super_block, &parent_de, &dst_blk.data[..], ino_gen); + + tail.copy_to_slice(&mut dst_blk.data); + } + + /// Add a new entry to a directory + /// + /// Params: + /// parent: &mut Ext4InodeRef - parent directory inode reference + /// child: &mut Ext4InodeRef - child inode reference + /// path: &str - path of the new entry + /// + /// Returns: + /// `Result` - status of the operation + pub fn dir_add_entry( + &self, + parent: &mut Ext4InodeRef, + child: &Ext4InodeRef, + name: &str, + ) -> Result { + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let block_size = self.super_block.block_size(); + let total_blocks: u64 = inode_size / block_size as u64; + + // iterate all blocks + let mut iblock = 0; + while iblock < total_blocks { + // get physical block id of a logical block id + let pblock = self.get_pblock_idx(parent, iblock as u32)?; + + // load physical block + let mut ext4block = + Block::load(&self.block_device, pblock as usize * BLOCK_SIZE); + + let result = self.try_insert_to_existing_block(&mut ext4block, name, child.inode_num); + + if result.is_ok() { + // set checksum + self.dir_set_csum(&mut ext4block, parent.inode.generation()); + ext4block.sync_blk_to_disk(self.block_device.clone()); + + return Ok(EOK); + } + + // go ot next block + iblock += 1; + } + + // no space in existing blocks, need to add new block + let new_block = self.append_inode_pblk(parent)?; + + // load new block + let mut new_ext4block = + Block::load(&self.block_device, new_block as usize * BLOCK_SIZE); + + // write new entry to the new block + // must succeed, as we just allocated the block + let de_type = DirEntryType::EXT4_DE_DIR; + self.insert_to_new_block(&mut new_ext4block, child.inode_num, name, de_type); + + // set checksum + self.dir_set_csum(&mut new_ext4block, parent.inode.generation()); + new_ext4block.sync_blk_to_disk(self.block_device.clone()); + + Ok(EOK) + } + + /// Try to insert a new entry to an existing block + /// + /// Params: + /// block: &mut Block - block to insert the new entry + /// name: &str - name of the new entry + /// inode: u32 - inode number of the new entry + /// + /// Returns: + /// `Result` - status of the operation + pub fn try_insert_to_existing_block( + &self, + block: &mut Block, + name: &str, + child_inode: u32, + ) -> Result { + // required length aligned to 4 bytes + let required_len = { + let mut len = size_of::() + name.len(); + if len % 4 != 0 { + len += 4 - (len % 4); + } + len + }; + + let mut offset = 0; + + // Start from the first entry + while offset < BLOCK_SIZE - size_of::() { + let mut de = Ext4DirEntry::try_from(&block.data[offset..]).unwrap(); + + if de.unused() { + continue; + } + + let inode = de.inode; + let rec_len = de.entry_len; + + let used_len = de.name_len as usize; + let mut sz = core::mem::size_of::() + used_len; + if used_len % 4 != 0 { + sz += 4 - used_len % 4; + } + + let free_space = rec_len as usize - sz; + + // If there is enough free space + if free_space >= required_len { + // Create new directory entry + let mut new_entry = Ext4DirEntry::default(); + + // Update existing entry length and copy both entries back to block data + de.entry_len = sz as u16; + + let de_type = DirEntryType::EXT4_DE_DIR; + new_entry.write_entry(free_space as u16, child_inode, name, de_type); + + // update parent_de and new_de to blk_data + de.copy_to_slice(&mut block.data, offset); + new_entry.copy_to_slice(&mut block.data, offset + sz); + + // Sync to disk + block.sync_blk_to_disk(self.block_device.clone()); + + return Ok(EOK); + } + + // Move to the next entry + offset += de.entry_len() as usize; + } + + return_errno_with_message!(Errno::ENOSPC, "No space in block for new entry"); + } + + /// Insert a new entry to a new block + /// + /// Params: + /// block: &mut Block - block to insert the new entry + /// name: &str - name of the new entry + /// inode: u32 - inode number of the new entry + pub fn insert_to_new_block( + &self, + block: &mut Block, + inode: u32, + name: &str, + de_type: DirEntryType, + ) { + // write new entry + let mut new_entry = Ext4DirEntry::default(); + let el = BLOCK_SIZE - size_of::(); + new_entry.write_entry(el as u16, inode, name, de_type); + new_entry.copy_to_slice(&mut block.data, 0); + + copy_dir_entry_to_array(&new_entry, &mut block.data, 0); + + // init tail for new block + let tail = Ext4DirEntryTail::new(); + tail.copy_to_slice(&mut block.data); + } + + pub fn dir_remove_entry(&self, parent: &mut Ext4InodeRef, path: &str) -> Result { + // get remove_entry pos in parent and its prev entry + let mut result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent.inode_num, path, &mut result)?; + + let mut ext4block = Block::load(&self.block_device, result.pblock_id * BLOCK_SIZE); + + let de_del_entry_len = result.dentry.entry_len(); + + // prev entry + let pde: &mut Ext4DirEntry = ext4block.read_offset_as_mut(result.prev_offset); + + pde.entry_len += de_del_entry_len; + + let de_del: &mut Ext4DirEntry = ext4block.read_offset_as_mut(result.offset); + + de_del.inode = 0; + + self.dir_set_csum(&mut ext4block, parent.inode.generation()); + ext4block.sync_blk_to_disk(self.block_device.clone()); + + Ok(EOK) + } + + pub fn dir_has_entry(&self, dir_inode: u32) -> bool { + // load parent inode + let parent = self.get_inode_ref(dir_inode); + assert!(parent.inode.is_dir()); + + // start from the first logical block + let mut iblock = 0; + // physical block id + let mut fblock: Ext4Fsblk = 0; + + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let total_blocks: u64 = inode_size / BLOCK_SIZE as u64; + + // iterate all blocks + while iblock < total_blocks { + let search_path = self.find_extent(&parent, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + fblock = path.pblock; + + // load physical block + let ext4block = + Block::load(&self.block_device, fblock as usize * BLOCK_SIZE); + + // start from the first entry + let mut offset = 0; + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = ext4block.read_offset_as(offset); + offset += de.entry_len as usize; + if de.inode == 0 { + continue; + } + // skip . and .. + if de.get_name() == "." || de.get_name() == ".." { + continue; + } + return true; + } + } + // go to next block + iblock += 1 + } + + false + } + + pub fn dir_remove(&self, parent: u32, path: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent, path, &mut search_result)?; + + let mut parent_inode_ref = self.get_inode_ref(parent); + let mut child_inode_ref = self.get_inode_ref(search_result.dentry.inode); + + if self.dir_has_entry(child_inode_ref.inode_num){ + return_errno_with_message!(Errno::ENOTSUP, "rm dir with children not supported") + } + + self.truncate_inode(&mut child_inode_ref, 0)?; + + self.unlink(&mut parent_inode_ref, &mut child_inode_ref, path)?; + + self.write_back_inode(&mut parent_inode_ref); + + // to do + // ext4_inode_set_del_time + // ext4_inode_set_links_cnt + // ext4_fs_free_inode(&child) + + Ok(EOK) + } +} + +pub fn copy_dir_entry_to_array(header: &Ext4DirEntry, array: &mut [u8], offset: usize) { + unsafe { + let de_ptr = header as *const Ext4DirEntry as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::() / core::mem::size_of::(); + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } +} diff --git a/lib/ext4_rs/src/ext4_impls/ext4.rs b/lib/ext4_rs/src/ext4_impls/ext4.rs new file mode 100644 index 0000000..d5422ac --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/ext4.rs @@ -0,0 +1,124 @@ +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::*; + +use crate::ext4_defs::*; +impl Ext4 { + /// Opens and loads an Ext4 from the `block_device`. + pub fn open(block_device: Arc) -> Self { + // Load the superblock + let block = Block::load(&block_device, SUPERBLOCK_OFFSET); + let super_block: Ext4Superblock = block.read_as(); + + Ext4 { + block_device, + super_block, + } + } + + // with dir result search path offset + pub fn generic_open( + &self, + path: &str, + parent_inode_num: &mut u32, + create: bool, + ftype: u16, + name_off: &mut u32, + ) -> Result { + let mut is_goal = false; + + let mut parent = parent_inode_num; + + let mut search_path = path; + + let mut dir_search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + loop { + while search_path.starts_with('/') { + *name_off += 1; // Skip the slash + search_path = &search_path[1..]; + } + + let len = path_check(search_path, &mut is_goal); + + let current_path = &search_path[..len]; + + if len == 0 || search_path.is_empty() { + break; + } + + search_path = &search_path[len..]; + + let r = self.dir_find_entry(*parent, current_path, &mut dir_search_result); + + // log::trace!("find in parent {:x?} r {:?} name {:?}", parent, r, current_path); + if let Err(e) = r { + if e.error() != Errno::ENOENT || !create { + return_errno_with_message!(Errno::ENOENT, "No such file or directory"); + } + + let mut inode_mode = 0; + if is_goal { + inode_mode = ftype; + } else { + inode_mode = InodeFileType::S_IFDIR.bits(); + } + + let new_inode_ref = self.create(*parent, current_path, inode_mode)?; + + // Update parent to the new inode + *parent = new_inode_ref.inode_num; + + // Now, update dir_search_result to reflect the new inode + dir_search_result.dentry.inode = new_inode_ref.inode_num; + + continue; + } + + + if is_goal { + break; + }else{ + // update parent + *parent = dir_search_result.dentry.inode; + } + *name_off += len as u32; + } + + if is_goal { + return Ok(dir_search_result.dentry.inode); + } + + Ok(dir_search_result.dentry.inode) + } + + #[allow(unused)] + pub fn dir_mk(&self, path: &str) -> Result { + let mut nameoff = 0; + + let filetype = InodeFileType::S_IFDIR; + + // todo get this path's parent + + // start from root + let mut parent = ROOT_INODE; + + let r = self.generic_open(path, &mut parent, true, filetype.bits(), &mut nameoff); + Ok(EOK) + } + + pub fn unlink( + &self, + parent: &mut Ext4InodeRef, + child: &mut Ext4InodeRef, + name: &str, + ) -> Result { + self.dir_remove_entry(parent, name)?; + + let is_dir = child.inode.is_dir(); + + self.ialloc_free_inode(child.inode_num, is_dir); + + Ok(EOK) + } +} diff --git a/lib/ext4_rs/src/ext4_impls/extents.rs b/lib/ext4_rs/src/ext4_impls/extents.rs new file mode 100644 index 0000000..bb831c7 --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/extents.rs @@ -0,0 +1,973 @@ +use crate::prelude::*; +use crate::return_errno_with_message; + +use crate::ext4_defs::*; + +impl Ext4 { + /// Find an extent in the extent tree. + /// + /// Params: + /// inode_ref: &Ext4InodeRef - inode reference + /// lblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - search path + /// + /// 如果 depth > 0,则查找extent_index,查找目标 lblock 对应的 extent。 + /// 如果 depth = 0,则直接在root节点中查找 extent,查找目标 lblock 对应的 extent。 + pub fn find_extent(&self, inode_ref: &Ext4InodeRef, lblock: Ext4Lblk) -> Result { + let mut search_path = SearchPath::new(); + + // Load the root node + let root_data: &[u8; 60] = + unsafe { core::mem::transmute::<&[u32; 15], &[u8; 60]>(&inode_ref.inode.block) }; + let mut node = ExtentNode::load_from_data(root_data, true).unwrap(); + + let mut depth = node.header.depth; + + // Traverse down the tree if depth > 0 + let mut pblock_of_node = 0; + while depth > 0 { + let index_pos = node.binsearch_idx(lblock); + if let Some(pos) = index_pos { + let index = node.get_index(pos)?; + let next_block = index.leaf_lo; + + search_path.path.push(ExtentPathNode { + header: node.header, + index: Some(index), + extent: None, + position: pos, + pblock: next_block as u64, + pblock_of_node, + }); + + let next_block = search_path.path.last().unwrap().index.unwrap().leaf_lo; + let mut next_data = self + .block_device + .read_offset(next_block as usize * BLOCK_SIZE); + node = ExtentNode::load_from_data_mut(&mut next_data, false)?; + depth -= 1; + search_path.depth += 1; + pblock_of_node = next_block as usize; + } else { + return_errno_with_message!(Errno::ENOENT, "Extentindex not found"); + } + } + + // Handle the case where depth is 0 + if let Some((extent, pos)) = node.binsearch_extent(lblock) { + search_path.path.push(ExtentPathNode { + header: node.header, + index: None, + extent: Some(extent), + position: pos, + pblock: lblock as u64 - extent.get_first_block() as u64 + extent.get_pblock(), + pblock_of_node, + }); + search_path.maxdepth = node.header.depth; + + Ok(search_path) + } else { + search_path.path.push(ExtentPathNode { + header: node.header, + index: None, + extent: None, + position: 0, + pblock: 0, + pblock_of_node, + }); + Ok(search_path) + } + } + + /// Insert an extent into the extent tree. + pub fn insert_extent( + &self, + inode_ref: &mut Ext4InodeRef, + newex: &mut Ext4Extent, + ) -> Result<()> { + let newex_first_block = newex.first_block; + + let mut search_path = self.find_extent(inode_ref, newex_first_block)?; + + let depth = search_path.depth as usize; + let node = &search_path.path[depth]; // Get the node at the current depth + + let at_root = node.pblock_of_node == 0; + let header = node.header; + + // Node is empty (no extents) + if header.entries_count == 0 { + // If the node is empty, insert the new extent directly + self.insert_new_extent(inode_ref, &mut search_path, newex)?; + return Ok(()); + } + + // Insert to exsiting extent + if let Some(mut ex) = node.extent { + let pos = node.position; + let last_extent_pos = header.entries_count as usize - 1; + + // Try to Insert to found_ext + // found_ext: |<---found_ext--->| |<---ext2--->| + // 20 30 50 60 + // insert: |<---found_ext---><---newex--->| |<---ext2--->| + // 20 30 40 50 60 + // merge: |<---newex--->| |<---ext2--->| + // 20 40 50 60 + if self.can_merge(&ex, newex) { + self.merge_extent(&search_path, &mut ex, newex)?; + + if at_root { + // we are at root + *inode_ref.inode.root_extent_mut_at(node.position) = ex; + } + return Ok(()); + } + + // Insert right + // found_ext: |<---found_ext--->| |<---next_extent--->| + // 10 20 30 40 + // insert: |<---found_ext--->|<---newex---><---next_extent--->| + // 10 20 30 40 + // merge: |<---found_ext--->|<---newex--->| + // 10 20 40 + if pos < last_extent_pos + && ((ex.first_block + ex.block_count as u32) < newex.first_block) + { + if let Some(next_extent) = self.get_extent_from_node(node, pos + 1) { + if self.can_merge(&next_extent, newex) { + self.merge_extent(&search_path, newex, &next_extent)?; + return Ok(()); + } + } + } + + // Insert left + // found_ext: |<---found_ext--->| |<---ext2--->| + // 20 30 40 50 + // insert: |<---prev_extent---><---newex--->|<---found_ext--->|....|<---ext2--->| + // 0 10 20 30 40 50 + // merge: |<---newex--->|<---found_ext--->|....|<---ext2--->| + // 0 20 30 40 50 + if pos > 0 && (newex.first_block + newex.block_count as u32) < ex.first_block { + if let Some(mut prev_extent) = self.get_extent_from_node(node, pos - 1) { + if self.can_merge(&prev_extent, newex) { + self.merge_extent(&search_path, &mut prev_extent, newex)?; + return Ok(()); + } + } + } + } + + // Check if there's space to insert the new extent + // full full + // Before: |<---ext1--->|<---ext2--->| + // 10 20 30 + + // full full + // insert: |<---ext1--->|<---ext2--->|<---newex--->| + // 10 20 30 35 + if header.entries_count < header.max_entries_count { + self.insert_new_extent(inode_ref, &mut search_path, newex)?; + } else { + // Create a new leaf node + self.create_new_leaf(inode_ref, &mut search_path, newex)?; + } + + Ok(()) + } + + /// Get extent from the node at the given position. + fn get_extent_from_node(&self, node: &ExtentPathNode, pos: usize) -> Option { + let data = self + .block_device + .read_offset(node.pblock as usize * BLOCK_SIZE); + let extent_node = ExtentNode::load_from_data(&data, false).unwrap(); + + extent_node.get_extent(pos) + } + + /// Check if two extents can be merged. + /// + /// This function determines whether two extents, `ex1` and `ex2`, can be merged + /// into a single extent. Extents are contiguous ranges of blocks in the ext4 + /// filesystem that map logical block numbers to physical block numbers. + /// + /// # Arguments + /// + /// * `ex1` - The first extent to check. + /// * `ex2` - The second extent to check. + /// + /// # Returns + /// + /// * `true` if the extents can be merged. + /// * `false` otherwise. + /// + /// # Merge Conditions + /// + /// 1. **Same Unwritten State**: + /// - The `is_unwritten` state of both extents must be the same. + /// - Unwritten extents are placeholders for blocks that are allocated but not initialized. + /// + /// 2. **Contiguous Block Ranges**: + /// - The logical block range of the first extent must immediately precede + /// the logical block range of the second extent. + /// + /// 3. **Maximum Length**: + /// - The total length of the merged extent must not exceed the maximum allowed + /// extent length (`EXT_INIT_MAX_LEN`). + /// - If the extents are unwritten, the total length must also not exceed + /// the maximum length for unwritten extents (`EXT_UNWRITTEN_MAX_LEN`). + /// + /// 4. **Contiguous Physical Blocks**: + /// - The physical block range of the first extent must immediately precede + /// the physical block range of the second extent. This ensures that the + /// physical storage is contiguous. + fn can_merge(&self, ex1: &Ext4Extent, ex2: &Ext4Extent) -> bool { + // Check if the extents have the same unwritten state + if ex1.is_unwritten() != ex2.is_unwritten() { + return false; + } + let ext1_ee_len = ex1.get_actual_len(); + let ext2_ee_len = ex2.get_actual_len(); + + // Check if the block ranges are contiguous + if ex1.first_block + ext1_ee_len as u32 != ex2.first_block { + return false; + } + + // Check if the merged length would exceed the maximum allowed length + if ext1_ee_len + ext2_ee_len > EXT_INIT_MAX_LEN { + return false; + } + + // Check if the physical blocks are contiguous + if ex1.get_pblock() + ext1_ee_len as u64 == ex2.get_pblock() { + return true; + } + false + } + + fn merge_extent( + &self, + search_path: &SearchPath, + left_ext: &mut Ext4Extent, + right_ext: &Ext4Extent, + ) -> Result<()> { + + let unwritten = left_ext.is_unwritten(); + let len = left_ext.get_actual_len() + right_ext.get_actual_len(); + left_ext.set_actual_len(len); + if unwritten { + left_ext.mark_unwritten(); + } + let depth = search_path.depth as usize; + + let header = search_path.path[depth].header; + + if header.max_entries_count > 4 { + let node = &search_path.path[depth]; + let block = node.pblock_of_node; + let new_ex_offset = core::mem::size_of::() + core::mem::size_of::() * (node.position); + let mut ext4block = Block::load(&self.block_device, block * BLOCK_SIZE); + let left_ext:&mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); + + let unwritten = left_ext.is_unwritten(); + let len = left_ext.get_actual_len() + right_ext.get_actual_len(); + left_ext.set_actual_len(len); + if unwritten { + left_ext.mark_unwritten(); + } + + ext4block.sync_blk_to_disk(self.block_device.clone()); + } + + + Ok(()) + } + + fn insert_new_extent( + &self, + inode_ref: &mut Ext4InodeRef, + search_path: &mut SearchPath, + new_extent: &mut Ext4Extent, + ) -> Result<()> { + let depth = search_path.depth as usize; + let node = &mut search_path.path[depth]; // Get the node at the current depth + let header = node.header; + + // insert at root + if depth == 0 { + // Node is empty (no extents) + if header.entries_count == 0 { + *inode_ref.inode.root_extent_mut_at(node.position) = *new_extent; + inode_ref.inode.root_extent_header_mut().entries_count += 1; + + self.write_back_inode(inode_ref); + return Ok(()); + } + // Not empty, insert at search result pos + 1 + log::trace!("insert newex at pos {:x?} current entry_count {:x?} ex {:x?}", node.position + 1 , header.entries_count, new_extent); + *inode_ref.inode.root_extent_mut_at(node.position + 1) = *new_extent; + inode_ref.inode.root_extent_header_mut().entries_count += 1; + return Ok(()); + }else{ + // insert at nonroot + // log::trace!("insert newex at nonroot pos {:x?} current entry_count {:x?} ex {:x?}", node.position + 1 , header.entries_count, new_extent); + + // load block + let node_block = node.pblock_of_node; + let mut ext4block = + Block::load(&self.block_device, node_block * BLOCK_SIZE); + let new_ex_offset = core::mem::size_of::() + core::mem::size_of::() * (node.position + 1); + + // insert new extent + let ex: &mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); + *ex = *new_extent; + let header: &mut Ext4ExtentHeader = ext4block.read_offset_as_mut(0); + + // update entry count + header.entries_count += 1; + + // sync to disk + ext4block.sync_blk_to_disk(self.block_device.clone()); + + return Ok(()); + } + + return_errno_with_message!(Errno::ENOTSUP, "Not supported insert extent at nonroot"); + } + + // finds empty index and adds new leaf. if no free index is found, then it requests in-depth growing. + fn create_new_leaf( + &self, + inode_ref: &mut Ext4InodeRef, + search_path: &mut SearchPath, + new_extent: &mut Ext4Extent, + ) -> Result<()> { + // log::info!("search path {:x?}", search_path); + + // tree is full, time to grow in depth + self.ext_grow_indepth(inode_ref); + + // insert again + self.insert_extent(inode_ref, new_extent) + + } + + + // allocates new block + // moves top-level data (index block or leaf) into the new block + // initializes new top-level, creating index that points to the + // just created block + fn ext_grow_indepth(&self, inode_ref: &mut Ext4InodeRef) -> Result<()>{ + // Try to prepend new index to old one + let new_block = self.balloc_alloc_block(inode_ref, None)?; + + // load new block + let mut new_ext4block = + Block::load(&self.block_device, new_block as usize * BLOCK_SIZE); + + // move top-level index/leaf into new block + let data_to_copy = &inode_ref.inode.block; + let data_to_copy = data_to_copy.as_ptr() as *const u8; + unsafe{core::ptr::copy_nonoverlapping(data_to_copy, new_ext4block.data.as_mut_ptr(), 60)}; + + // zero out unused area in the extent block + new_ext4block.data[60..].fill(0); + + // set new block header + let mut new_header = Ext4ExtentHeader::load_from_u8_mut(&mut new_ext4block.data); + new_header.set_magic(); + let space = (BLOCK_SIZE - core::mem::size_of::()) / core::mem::size_of::(); + new_header.set_max_entries_count(space as u16); + log::info!("new_header max entries {:x?}", new_header.max_entries_count); + + // Update top-level index: num,max,pointer + let mut root_header = inode_ref.inode.root_extent_header_mut(); + root_header.set_entries_count(1); + root_header.add_depth(); + + let root_depth = root_header.depth; + let root_first_extent_block = inode_ref.inode.root_extent_at(0).first_block; + let mut root_first_index = inode_ref.inode.root_first_index_mut(); + root_first_index.store_pblock(new_block); + if root_depth == 0 { + // Root extent block becomes index block + root_first_index.first_block = root_first_extent_block; + } + + + new_ext4block.sync_blk_to_disk(self.block_device.clone()); + self.write_back_inode(inode_ref); + + + Ok(()) + } + +} + +impl Ext4 { + // Assuming init state + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | | | + // v v v + // + // depth 1 (internal node) + // +--------+...+--------+ +--------+...+--------+ ...... + // | idx1 |...| idxn | | idx1 |...| idxn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // | | | | + // v v v v + // + // depth 2 (leaf nodes) + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| extn | | ext1 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + pub fn extent_remove_space( + &self, + inode_ref: &mut Ext4InodeRef, + from: u32, + to: u32, + ) -> Result { + // log::info!("Remove space from {:x?} to {:x?}", from, to); + let mut search_path = self.find_extent(inode_ref, from)?; + + // for i in search_path.path.iter() { + // log::info!("from Path: {:x?}", i); + // } + + let depth = search_path.depth as usize; + + /* If we do remove_space inside the range of an extent */ + let mut ex = search_path.path[depth].extent.unwrap(); + if ex.get_first_block() < from + && to < (ex.get_first_block() + ex.get_actual_len() as u32 - 1) + { + let mut newex = Ext4Extent::default(); + let unwritten = ex.is_unwritten(); + let ee_block = ex.first_block; + let block_count = ex.block_count; + let newblock = to + 1 - ee_block + ex.get_pblock() as u32; + ex.block_count = from as u16 - ee_block as u16; + + if unwritten { + ex.mark_unwritten(); + } + newex.first_block = to + 1; + newex.block_count = (ee_block + block_count as u32 - 1 - to) as u16; + newex.start_lo = newblock; + newex.start_hi = ((newblock as u64) >> 32) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + return Ok(EOK); + } + + // log::warn!("Remove space in depth: {:x?}", depth); + + let mut i = depth as isize; + + while i >= 0 { + // we are at the leaf node + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // |path + // v + // idx2 + // depth 1 (internal node) + // +--------+--------+--------+ ...... + // | idx1 | idx2 | idx3 | ...... + // +--------+--------+--------+ ...... + // |path + // v + // ext2 + // depth 2 (leaf nodes) + // +--------+--------+..+--------+ + // | ext1 | ext2 |..|last_ext| + // +--------+--------+..+--------+ + // ^ ^ + // | | + // from to(exceed last ext, rest of the extents will be removed) + if i as usize == depth { + let node_pblock = search_path.path[i as usize].pblock_of_node; + + let header = search_path.path[i as usize].header; + let entries_count = header.entries_count; + + // we are at root + if node_pblock == 0 { + let first_ex = inode_ref.inode.root_extent_at(0); + let last_ex = inode_ref.inode.root_extent_at(entries_count as usize - 1); + + let mut leaf_from = first_ex.first_block; + let mut leaf_to = last_ex.first_block + last_ex.get_actual_len() as u32 - 1; + if leaf_from < from { + leaf_from = from; + } + if leaf_to > to { + leaf_to = to; + } + // log::trace!("from {:x?} to {:x?} leaf_from {:x?} leaf_to {:x?}", from, to, leaf_from, leaf_to); + self.ext_remove_leaf(inode_ref, &mut search_path, leaf_from, leaf_to)?; + + i -= 1; + continue; + } + let ext4block = + Block::load(&self.block_device, node_pblock * BLOCK_SIZE); + + let header = search_path.path[i as usize].header; + let entries_count = header.entries_count; + + let first_ex: Ext4Extent = ext4block.read_offset_as(size_of::()); + let last_ex: Ext4Extent = ext4block.read_offset_as( + size_of::() + + size_of::() * (entries_count - 1) as usize, + ); + + let mut leaf_from = first_ex.first_block; + let mut leaf_to = last_ex.first_block + last_ex.get_actual_len() as u32 - 1; + + if leaf_from < from { + leaf_from = from; + } + if leaf_to > to { + leaf_to = to; + } + // log::trace!( + // "from {:x?} to {:x?} leaf_from {:x?} leaf_to {:x?}", + // from, + // to, + // leaf_from, + // leaf_to + // ); + + self.ext_remove_leaf(inode_ref, &mut search_path, leaf_from, leaf_to)?; + + i -= 1; + continue; + } + + // log::trace!("---at level---{:?}\n", i); + + // we are at index + // example i=1, depth=2 + // depth 0 (root node) - 处理的索引节点 + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // |path | 下一个要处理的节点(more_to_rm?) + // v v + // idx2 + // + // depth 1 (internal node) + // +--------++--------+...+--------+ + // | idx1 || idx2 |...| idxn | + // +--------++--------+...+--------+ + // |path + // v + // ext2 + // depth 2 (leaf nodes) + // +--------+--------+..+--------+ + // | ext1 | ext2 |..|last_ext| + // +--------+--------+..+--------+ + let header = search_path.path[i as usize].header; + if self.more_to_rm(&search_path.path[i as usize], to) { + // todo + // load next idx + + // go to this node's child + i += 1; + } else { + if i > 0 { + // empty + if header.entries_count == 0 { + self.ext_remove_idx(inode_ref, &mut search_path, i as u16 - 1)?; + } + } + + let idx = i; + if idx - 1 < 0 { + break; + } + i -= 1; + } + } + + Ok(EOK) + } + + pub fn ext_remove_leaf( + &self, + inode_ref: &mut Ext4InodeRef, + path: &mut SearchPath, + from: u32, + to: u32, + ) -> Result { + // log::trace!("Remove leaf from {:x?} to {:x?}", from, to); + + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | | | + // v v v + // ^ + // Current position + let depth = inode_ref.inode.root_header_depth(); + let mut header = path.path[depth as usize].header; + + let mut new_entry_count = header.entries_count; + let mut ex2 = Ext4Extent::default(); + + /* find where to start removing */ + let pos = path.path[depth as usize].position; + let entry_count = header.entries_count; + + // depth 1 (internal node) + // +--------+...+--------+ +--------+...+--------+ ...... + // | idx1 |...| idxn | | idx1 |...| idxn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // | | | | + // v v v v + // ^ + // Current loaded node + + // load node data + let node_disk_pos = path.path[depth as usize].pblock_of_node * BLOCK_SIZE; + + let mut ext4block = if node_disk_pos == 0 { + // we are at root + Block::load_inode_root_block(&inode_ref.inode.block) + } else { + Block::load(&self.block_device, node_disk_pos) + }; + + // depth 2 (leaf nodes) + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| extn | | ext1 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ + // Current start extent + + // start from pos + for i in pos..entry_count as usize { + let ex: &mut Ext4Extent = ext4block + .read_offset_as_mut(size_of::() + i * size_of::()); + + if ex.first_block > to { + break; + } + + let mut new_len = 0; + let mut start = ex.first_block; + let mut new_start = ex.first_block; + + let mut len = ex.get_actual_len(); + let mut newblock = ex.get_pblock(); + + // Initial state: + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| ext2 | | ext3 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ ^ + // from to + + // Case 1: Remove a portion within the extent + if start < from { + len -= from as u16 - start as u16; + new_len = from - start; + start = from; + } else { + // Case 2: Adjust extent that partially overlaps the 'to' boundary + if start + len as u32 - 1 > to { + new_len = start + len as u32 - 1 - to; + len -= new_len as u16; + new_start = to + 1; + newblock += (to + 1 - start) as u64; + ex2 = *ex; + } + } + + // After removing range from `from` to `to`: + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...[removed]| |[removed]|...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ ^ + // from to + // new_start + + // Remove blocks within the extent + self.ext_remove_blocks(inode_ref, ex, start, start + len as u32 - 1); + + ex.first_block = new_start; + // log::trace!("after remove leaf ex first_block {:x?}", ex.first_block); + + if new_len == 0 { + new_entry_count -= 1; + } else { + let unwritten = ex.is_unwritten(); + ex.store_pblock(newblock as u64); + ex.block_count = new_len as u16; + + if unwritten { + ex.mark_unwritten(); + } + } + } + + // Move remaining extents to the start: + // Before: + // +--------+--------+...+--------+ + // | ext3 | ext4 |...| extn | + // +--------+--------+...+--------+ + // ^ ^ + // rm rm + // After: + // +--------+.+--------+--------+... + // | ext1 |.| extn | [empty]|... + // +--------+.+--------+--------+... + + // Move any remaining extents to the starting position of the node. + if ex2.first_block > 0 { + let start_index = size_of::() + pos * size_of::(); + let end_index = + size_of::() + entry_count as usize * size_of::(); + let remaining_extents: Vec = ext4block.data[start_index..end_index].to_vec(); + ext4block.data[size_of::() + ..size_of::() + remaining_extents.len()] + .copy_from_slice(&remaining_extents); + } + + // Update the entries count in the header + header.entries_count = new_entry_count; + + /* + * If the extent pointer is pointed to the first extent of the node, and + * there's still extents presenting, we may need to correct the indexes + * of the paths. + */ + if pos == 0 && new_entry_count > 0 { + self.ext_correct_indexes(path)?; + } + + /* if this leaf is free, then we should + * remove it from index block above */ + if new_entry_count == 0 { + // if we are at root? + if path.path[depth as usize].pblock_of_node == 0 { + return Ok(EOK); + } + self.ext_remove_idx(inode_ref, path, depth - 1)?; + } else if depth > 0 { + // go to next index + path.path[depth as usize - 1].position += 1; + } + + Ok(EOK) + } + + fn ext_remove_index_block(&self, inode_ref: &mut Ext4InodeRef, index: &mut Ext4ExtentIndex) { + let block_to_free = index.get_pblock(); + + // log::trace!("remove index's block {:x?}", block_to_free); + self.balloc_free_blocks(inode_ref, block_to_free as _, 1); + } + + fn ext_remove_idx( + &self, + inode_ref: &mut Ext4InodeRef, + path: &mut SearchPath, + depth: u16, + ) -> Result { + // log::trace!("Remove index at depth {:x?}", depth); + + // Initial state: + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // ^ + // Current index to remove (pos=1) + + // Removing index: + // +--------+--------+--------+ + // | idx1 |[empty] | idx3 | + // +--------+--------+--------+ + // ^ + // Current index to remove + + // After moving remaining indexes: + // +--------+--------+--------+ + // | idx1 | idx3 |[empty] | + // +--------+--------+--------+ + // ^ + // Current index to remove + + let mut i = depth as usize; + let mut header = path.path[i].header; + + // 获取要删除的索引块 + let leaf_block = path.path[i].index.unwrap().get_pblock(); + + // 如果当前索引不是最后一个索引,将后续的索引前移 + if path.path[i].position != header.entries_count as usize - 1 { + let start_pos = size_of::() + + path.path[i].position * size_of::(); + let end_pos = size_of::() + + (header.entries_count as usize) * size_of::(); + + let node_disk_pos = path.path[i].pblock_of_node * BLOCK_SIZE; + let mut ext4block = Block::load(&self.block_device, node_disk_pos); + + let remaining_indexes: Vec = + ext4block.data[start_pos + size_of::()..end_pos].to_vec(); + ext4block.data[start_pos..start_pos + remaining_indexes.len()] + .copy_from_slice(&remaining_indexes); + let remaining_size = remaining_indexes.len(); + + // 清空剩余位置 + let empty_start = start_pos + remaining_size; + let empty_end = end_pos; + ext4block.data[empty_start..empty_end].fill(0); + } + + // 更新头部的entries_count + header.entries_count -= 1; + + // 释放索引块 + self.ext_remove_index_block(inode_ref, &mut path.path[i].index.unwrap()); + + // Updating parent index if necessary: + // +--------+--------+--------+ + // | idx1 | idx3 |[empty] | + // +--------+--------+--------+ + // ^ + // Updated parent index if necessary + + // 如果当前层不是根,需要检查是否需要更新父节点索引 + while i > 0 { + if path.path[i].position != 0 { + break; + } + + let parent_idx = i - 1; + let parent_index = &mut path.path[parent_idx].index.unwrap(); + let current_index = &path.path[i].index.unwrap(); + + parent_index.first_block = current_index.first_block; + self.write_back_inode(inode_ref); + + i -= 1; + } + + Ok(EOK) + } + + /// Correct the first block of the parent index. + fn ext_correct_indexes(&self, path: &mut SearchPath) -> Result { + // if child get removed from parent, we need to update the parent's first_block + let mut depth = path.depth as usize; + + // depth 2: + // +--------+--------+--------+ + // |[empty] | ext2 | ext3 | + // +--------+--------+--------+ + // ^ + // pos=0, ext1_first_block=0(removed) parent index first block=0 + + // depth 2: + // +--------+--------+--------+ + // | ext2 | ext3 |[empty] | + // +--------+--------+--------+ + // ^ + // pos=0, now first_block=ext2_first_block + + // 更新父节点索引: + // depth 1: + // +-----------------------+ + // | idx1_2 |...| idx1_n | + // +-----------------------+ + // ^ + // 更新父节点索引(first_block) + + // depth 0: + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | + // 更新根节点索引(first_block) + + while depth > 0 { + let parent_idx = depth - 1; + + // 获取当前层的 extent + if let Some(child_extent) = path.path[depth].extent { + // 获取父节点 + let parent_node = &mut path.path[parent_idx]; + // 获取父节点的索引,并更新 first_block + if let Some(ref mut parent_index) = parent_node.index { + parent_index.first_block = child_extent.first_block; + } + } + + depth -= 1; + } + + Ok(EOK) + } + + fn ext_remove_blocks( + &self, + inode_ref: &mut Ext4InodeRef, + ex: &mut Ext4Extent, + from: u32, + to: u32, + ) { + let len = to - from + 1; + let num = from - ex.first_block; + let start: u32 = ex.get_pblock() as u32 + num; + self.balloc_free_blocks(inode_ref, start as _, len); + } + + pub fn more_to_rm(&self, path: &ExtentPathNode, to: u32) -> bool { + let header = path.header; + + // No Sibling exists + if header.entries_count == 1 { + return false; + } + + let pos = path.position; + if pos > header.entries_count as usize - 1 { + return false; + } + + // Check if index is out of bounds + if let Some(index) = path.index { + let last_index_pos = header.entries_count as usize - 1; + let node_disk_pos = path.pblock_of_node * BLOCK_SIZE; + let ext4block = Block::load(&self.block_device, node_disk_pos); + let last_index: Ext4ExtentIndex = + ext4block.read_offset_as(size_of::() * last_index_pos); + + if path.position > last_index_pos || index.first_block > last_index.first_block { + return false; + } + + // Check if index's first_block is greater than 'to' + if index.first_block > to { + return false; + } + } + + true + } +} diff --git a/lib/ext4_rs/src/ext4_impls/file.rs b/lib/ext4_rs/src/ext4_impls/file.rs new file mode 100644 index 0000000..695d51d --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/file.rs @@ -0,0 +1,453 @@ +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::path_check; +use crate::ext4_defs::*; + +impl Ext4 { + /// Link a child inode to a parent directory + /// + /// Params: + /// parent: &mut Ext4InodeRef - parent directory inode reference + /// child: &mut Ext4InodeRef - child inode reference + /// name: &str - name of the child inode + /// + /// Returns: + /// `Result` - status of the operation + pub fn link( + &self, + parent: &mut Ext4InodeRef, + child: &mut Ext4InodeRef, + name: &str, + ) -> Result { + // Add a directory entry in the parent directory pointing to the child inode + + // at this point should insert to existing block + self.dir_add_entry(parent, child, name)?; + self.write_back_inode_without_csum(parent); + + // If this is the first link. add '.' and '..' entries + if child.inode.is_dir() { + // let child_ref = child.clone(); + let new_child_ref = Ext4InodeRef { + inode_num: child.inode_num, + inode: child.inode, + }; + + // at this point child need a new block + self.dir_add_entry(child, &new_child_ref, ".")?; + + // at this point should insert to existing block + self.dir_add_entry(child, &new_child_ref, "..")?; + + child.inode.set_links_count(2); + let link_cnt = parent.inode.links_count() + 1; + parent.inode.set_links_count(link_cnt); + + return Ok(EOK); + } + + // Increment the link count of the child inode + let link_cnt = child.inode.links_count() + 1; + child.inode.set_links_count(link_cnt); + + Ok(EOK) + } + + /// create a new inode and link it to the parent directory + /// + /// Params: + /// parent: u32 - inode number of the parent directory + /// name: &str - name of the new file + /// mode: u16 - file mode + /// + /// Returns: + pub fn create(&self, parent: u32, name: &str, inode_mode: u16) -> Result { + let mut parent_inode_ref = self.get_inode_ref(parent); + + // let mut child_inode_ref = self.create_inode(inode_mode)?; + let init_child_ref = self.create_inode(inode_mode)?; + + self.write_back_inode_without_csum(&init_child_ref); + // load new + let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num); + + self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + self.write_back_inode(&mut child_inode_ref); + + Ok(child_inode_ref) + } + + pub fn create_inode(&self, inode_mode: u16) -> Result { + + let inode_file_type = match InodeFileType::from_bits(inode_mode) { + Some(file_type) => file_type, + None => InodeFileType::S_IFREG, + }; + + let is_dir = inode_file_type == InodeFileType::S_IFDIR; + + // allocate inode + let inode_num = self.alloc_inode(is_dir)?; + + // initialize inode + let mut inode = Ext4Inode::default(); + + // set mode + inode.set_mode(inode_mode | 0o777); + + // set extra size + let inode_size = self.super_block.inode_size(); + let extra_size = self.super_block.extra_size(); + if inode_size > EXT4_GOOD_OLD_INODE_SIZE { + inode.set_i_extra_isize(extra_size); + } + + // set extent + inode.set_flags(EXT4_INODE_FLAG_EXTENTS as u32); + inode.extent_tree_init(); + + let inode_ref = Ext4InodeRef { + inode_num, + inode, + }; + + Ok(inode_ref) + } + + + /// create a new inode and link it to the parent directory + /// + /// Params: + /// parent: u32 - inode number of the parent directory + /// name: &str - name of the new file + /// mode: u16 - file mode + /// uid: u32 - user id + /// gid: u32 - group id + /// + /// Returns: + pub fn create_with_attr(&self, parent: u32, name: &str, inode_mode: u16, uid:u16, gid: u16) -> Result { + let mut parent_inode_ref = self.get_inode_ref(parent); + + // let mut child_inode_ref = self.create_inode(inode_mode)?; + let mut init_child_ref = self.create_inode(inode_mode)?; + + init_child_ref.inode.set_uid(uid); + init_child_ref.inode.set_gid(gid); + + self.write_back_inode_without_csum(&init_child_ref); + // load new + let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num); + + self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + self.write_back_inode(&mut child_inode_ref); + + Ok(child_inode_ref) + } + + /// Read data from a file at a given offset + /// + /// Params: + /// inode: u32 - inode number of the file + /// offset: usize - offset from where to read + /// read_buf: &mut [u8] - buffer to read the data into + /// + /// Returns: + /// `Result` - number of bytes read + pub fn read_at(&self, inode: u32, offset: usize, read_buf: &mut [u8]) -> Result { + // read buf is empty, return 0 + let mut read_buf_len = read_buf.len(); + if read_buf_len == 0 { + return Ok(0); + } + + // get the inode reference + let inode_ref = self.get_inode_ref(inode); + + // get the file size + let file_size = inode_ref.inode.size(); + + // if the offset is greater than the file size, return 0 + if offset >= file_size as usize { + return Ok(0); + } + + // adjust the read buffer size if the read buffer size is greater than the file size + if offset + read_buf_len > file_size as usize { + read_buf_len = file_size as usize - offset; + } + + // adjust the read buffer size if the read buffer size is greater than the file size + let size_to_read = min(read_buf_len, file_size as usize - offset); + + // calculate the start block and unaligned size + let iblock_start = offset / BLOCK_SIZE; + let iblock_last = (offset + size_to_read + BLOCK_SIZE - 1) / BLOCK_SIZE; // round up to include the last partial block + let unaligned_start_offset = offset % BLOCK_SIZE; + + // Buffer to keep track of read bytes + let mut cursor = 0; + let mut total_bytes_read = 0; + let mut iblock = iblock_start; + + // Unaligned read at the beginning + if unaligned_start_offset > 0 { + let adjust_read_size = min(BLOCK_SIZE - unaligned_start_offset, size_to_read); + + // get iblock physical block id + let pblock_idx = self.get_pblock_idx(&inode_ref, iblock as u32)?; + + // read data + let data = self + .block_device + .read_offset(pblock_idx as usize * BLOCK_SIZE); + + // copy data to read buffer + read_buf[cursor..cursor + adjust_read_size].copy_from_slice( + &data[unaligned_start_offset..unaligned_start_offset + adjust_read_size], + ); + + // update cursor and total bytes read + cursor += adjust_read_size; + total_bytes_read += adjust_read_size; + iblock += 1; + } + + // Continue with full block reads + while total_bytes_read < size_to_read { + let read_length = core::cmp::min(BLOCK_SIZE, size_to_read - total_bytes_read); + + // get iblock physical block id + let pblock_idx = self.get_pblock_idx(&inode_ref, iblock as u32)?; + + // read data + let data = self + .block_device + .read_offset(pblock_idx as usize * BLOCK_SIZE); + + // copy data to read buffer + read_buf[cursor..cursor + read_length].copy_from_slice(&data[..read_length]); + + // update cursor and total bytes read + cursor += read_length; + total_bytes_read += read_length; + iblock += 1; + } + + Ok(min(total_bytes_read, size_to_read)) + } + + /// Write data to a file at a given offset + /// + /// Params: + /// inode: u32 - inode number of the file + /// offset: usize - offset from where to write + /// write_buf: &[u8] - buffer to write the data from + /// + /// Returns: + /// `Result` - number of bytes written + pub fn write_at(&self, inode: u32, offset: usize, write_buf: &[u8]) -> Result { + // write buf is empty, return 0 + let write_buf_len = write_buf.len(); + if write_buf_len == 0 { + return Ok(0); + } + + // get the inode reference + let mut inode_ref = self.get_inode_ref(inode); + + // Get the file size + let file_size = inode_ref.inode.size(); + + // Calculate the start and end block index + let iblock_start = offset / BLOCK_SIZE; + let iblock_last = (offset + write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; // round up to include the last partial block + + // start block index + let mut iblk_idx = iblock_start; + let ifile_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + + // Calculate the unaligned size + let unaligned = offset % BLOCK_SIZE; + + // Buffer to keep track of written bytes + let mut written = 0; + + // Start bgid + let mut start_bgid = 1; + + // Unaligned write + if unaligned > 0 { + let len = min(write_buf_len, BLOCK_SIZE - unaligned); + // Get the physical block id, if the block is not present, append a new block + let pblock_idx = if iblk_idx < ifile_blocks as usize { + self.get_pblock_idx(&inode_ref, iblk_idx as u32)? + } else { + // physical block not exist, append a new block + self.append_inode_pblk_from(&mut inode_ref, &mut start_bgid)? + }; + + let mut block = + Block::load(&self.block_device, pblock_idx as usize * BLOCK_SIZE); + + block.write_offset(unaligned, &write_buf[..len], len); + block.sync_blk_to_disk(self.block_device.clone()); + drop(block); + + + written += len; + iblk_idx += 1; + } + + // Aligned write + let mut fblock_start = 0; + let mut fblock_count = 0; + + while written < write_buf_len { + while iblk_idx < iblock_last && written < write_buf_len { + // Get the physical block id, if the block is not present, append a new block + let pblock_idx = if iblk_idx < ifile_blocks as usize { + self.get_pblock_idx(&inode_ref, iblk_idx as u32)? + } else { + // physical block not exist, append a new block + self.append_inode_pblk_from(&mut inode_ref, &mut start_bgid)? + }; + if fblock_start == 0 { + fblock_start = pblock_idx; + } + + // Check if the block is contiguous + if fblock_start + fblock_count != pblock_idx { + break; + } + + fblock_count += 1; + iblk_idx += 1; + } + + // Write contiguous blocks at once + let len = min( + fblock_count as usize * BLOCK_SIZE, + write_buf_len - written, + ); + + for i in 0..fblock_count { + let block_offset = fblock_start as usize * BLOCK_SIZE + i as usize * BLOCK_SIZE; + let mut block = Block::load(&self.block_device, block_offset); + let write_size = min(BLOCK_SIZE, write_buf_len - written); + block.write_offset(0, &write_buf[written..written + write_size], write_size); + block.sync_blk_to_disk(self.block_device.clone()); + drop(block); + written += write_size; + } + + fblock_start = 0; + fblock_count = 0; + } + + // Final unaligned write if any + if written < write_buf_len { + let len = write_buf_len - written; + // Get the physical block id, if the block is not present, append a new block + let pblock_idx = if iblk_idx < ifile_blocks as usize { + self.get_pblock_idx(&inode_ref, iblk_idx as u32)? + } else { + // physical block not exist, append a new block + self.append_inode_pblk(&mut inode_ref)? + }; + + let mut block = + Block::load(&self.block_device, pblock_idx as usize * BLOCK_SIZE); + block.write_offset(0, &write_buf[written..], len); + block.sync_blk_to_disk(self.block_device.clone()); + drop(block); + + written += len; + } + + // Update file size if necessary + if offset + write_buf_len > file_size as usize { + // log::trace!("set file size {:x}", offset + write_buf_len); + inode_ref + .inode + .set_size((offset + write_buf_len) as u64); + + self.write_back_inode(&mut inode_ref); + } + + Ok(written) + } + + /// File remove + /// + /// Params: + /// path: file path start from root + /// + /// Returns: + /// `Result` - status of the operation + pub fn file_remove(&self, path: &str) -> Result { + // start from root + let mut parent_inode_num = ROOT_INODE; + + let mut nameoff = 0; + let child_inode = self.generic_open(path, &mut parent_inode_num, false, 0, &mut nameoff)?; + + let mut child_inode_ref = self.get_inode_ref(child_inode); + let child_link_cnt = child_inode_ref.inode.links_count(); + if child_link_cnt == 1 { + self.truncate_inode(&mut child_inode_ref, 0)?; + } + + // get child name + let mut is_goal = false; + let p = &path[nameoff as usize..]; + let len = path_check(p, &mut is_goal); + + // load parent + let mut parent_inode_ref = self.get_inode_ref(parent_inode_num); + + let r = self.unlink( + &mut parent_inode_ref, + &mut child_inode_ref, + &p[..len], + )?; + + + Ok(EOK) + } + + /// File truncate + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// new_size: u64 - new size of the file + /// + /// Returns: + /// `Result` - status of the operation + pub fn truncate_inode(&self, inode_ref: &mut Ext4InodeRef, new_size: u64) -> Result { + let old_size = inode_ref.inode.size(); + + assert!(old_size > new_size); + + if old_size == new_size { + return Ok(EOK); + } + + let block_size = BLOCK_SIZE as u64; + let new_blocks_cnt = ((new_size + block_size - 1) / block_size) as u32; + let old_blocks_cnt = ((old_size + block_size - 1) / block_size) as u32; + let diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt; + + if diff_blocks_cnt > 0{ + self.extent_remove_space(inode_ref, new_blocks_cnt, EXT_MAX_BLOCKS)?; + } + + inode_ref.inode.set_size(new_size); + self.write_back_inode(inode_ref); + + Ok(EOK) + } +} diff --git a/lib/ext4_rs/src/ext4_impls/ialloc.rs b/lib/ext4_rs/src/ext4_impls/ialloc.rs new file mode 100644 index 0000000..9a61333 --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/ialloc.rs @@ -0,0 +1,122 @@ +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; + +impl Ext4 { + pub fn ialloc_alloc_inode(&self, is_dir: bool) -> Result { + let mut bgid = 0; + let bg_count = self.super_block.block_group_count(); + let mut super_block = self.super_block; + + while bgid <= bg_count { + if bgid == bg_count { + bgid = 0; + continue; + } + + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let mut free_inodes = bg.get_free_inodes_count(); + + if free_inodes > 0 { + let inode_bitmap_block = bg.get_inode_bitmap_block(&super_block); + + let mut raw_data = self + .block_device + .read_offset(inode_bitmap_block as usize * BLOCK_SIZE); + + let inodes_in_bg = super_block.get_inodes_in_group_cnt(bgid); + + let mut bitmap_data = &mut raw_data[..]; + + let mut idx_in_bg = 0; + + ext4_bmap_bit_find_clr(bitmap_data, 0, inodes_in_bg, &mut idx_in_bg); + ext4_bmap_bit_set(bitmap_data, idx_in_bg); + + // update bitmap in disk + self.block_device + .write_offset(inode_bitmap_block as usize * BLOCK_SIZE, bitmap_data); + + bg.set_block_group_ialloc_bitmap_csum(&super_block, bitmap_data); + + /* Modify filesystem counters */ + free_inodes -= 1; + bg.set_free_inodes_count(&super_block, free_inodes); + + /* Increment used directories counter */ + if is_dir { + let used_dirs = bg.get_used_dirs_count(&super_block) + 1; + bg.set_used_dirs_count(&super_block, used_dirs); + } + + /* Decrease unused inodes count */ + let mut unused = bg.get_itable_unused(&super_block); + let free = inodes_in_bg - unused; + if idx_in_bg >= free { + unused = inodes_in_bg - (idx_in_bg + 1); + bg.set_itable_unused(&super_block, unused); + } + + bg.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + /* Update superblock */ + super_block.decrease_free_inodes_count(); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Compute the absolute i-nodex number */ + let inodes_per_group = super_block.inodes_per_group(); + let inode_num = bgid * inodes_per_group + (idx_in_bg + 1); + + return Ok(inode_num); + } + + bgid += 1; + } + + return_errno_with_message!(Errno::ENOSPC, "alloc inode fail"); + } + + pub fn ialloc_free_inode(&self, index: u32, is_dir: bool) { + // Compute index of block group + let bgid = self.get_bgid_of_inode(index); + let block_device = self.block_device.clone(); + + let mut super_block = self.super_block; + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + // Load inode bitmap block + let inode_bitmap_block = bg.get_inode_bitmap_block(&self.super_block); + let mut bitmap_data = self + .block_device + .read_offset(inode_bitmap_block as usize * BLOCK_SIZE); + + // Find index within group and clear bit + let index_in_group = self.inode_to_bgidx(index); + ext4_bmap_bit_clr(&mut bitmap_data, index_in_group); + + // Set new checksum after modification + // update bitmap in disk + self.block_device + .write_offset(inode_bitmap_block as usize * BLOCK_SIZE, &bitmap_data); + bg.set_block_group_ialloc_bitmap_csum(&super_block, &bitmap_data); + + // Update free inodes count in block group + let free_inodes = bg.get_free_inodes_count() + 1; + bg.set_free_inodes_count(&self.super_block, free_inodes); + + // If inode was a directory, decrement the used directories count + if is_dir { + let used_dirs = bg.get_used_dirs_count(&self.super_block) - 1; + bg.set_used_dirs_count(&self.super_block, used_dirs); + } + + bg.sync_to_disk_with_csum(block_device.clone(), bgid as usize, &super_block); + + super_block.decrease_free_inodes_count(); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + } +} diff --git a/lib/ext4_rs/src/ext4_impls/inode.rs b/lib/ext4_rs/src/ext4_impls/inode.rs new file mode 100644 index 0000000..3162df6 --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/inode.rs @@ -0,0 +1,229 @@ +use bitflags::Flags; + +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; + +impl Ext4 { + pub fn get_bgid_of_inode(&self, inode_num: u32) -> u32 { + inode_num / self.super_block.inodes_per_group() + } + + pub fn inode_to_bgidx(&self, inode_num: u32) -> u32 { + inode_num % self.super_block.inodes_per_group() + } + + /// Get inode disk position. + pub fn inode_disk_pos(&self, inode_num: u32) -> usize { + let super_block = self.super_block; + let inodes_per_group = super_block.inodes_per_group; + let inode_size = super_block.inode_size as u64; + let group = (inode_num - 1) / inodes_per_group; + let index = (inode_num - 1) % inodes_per_group; + let block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, group as usize); + let inode_table_blk_num = block_group.get_inode_table_blk_num(); + + inode_table_blk_num as usize * BLOCK_SIZE + index as usize * inode_size as usize + } + + /// Load the inode reference from the disk. + pub fn get_inode_ref(&self, inode_num: u32) -> Ext4InodeRef { + let offset = self.inode_disk_pos(inode_num); + + let mut ext4block = Block::load(&self.block_device, offset); + + let inode: &mut Ext4Inode = ext4block.read_as_mut(); + + Ext4InodeRef { + inode_num, + inode: *inode, + } + } + + /// write back inode with checksum + pub fn write_back_inode(&self, inode_ref: &mut Ext4InodeRef) { + let inode_pos = self.inode_disk_pos(inode_ref.inode_num); + + // make sure self.super_block is up-to-date + inode_ref + .inode + .set_inode_checksum(&self.super_block, inode_ref.inode_num); + inode_ref + .inode + .sync_inode_to_disk(self.block_device.clone(), inode_pos); + } + + /// write back inode with checksum + pub fn write_back_inode_without_csum(&self, inode_ref: &Ext4InodeRef) { + let inode_pos = self.inode_disk_pos(inode_ref.inode_num); + + inode_ref + .inode + .sync_inode_to_disk(self.block_device.clone(), inode_pos); + } + + /// Get physical block id of a logical block. + /// + /// Params: + /// inode_ref: &Ext4InodeRef - inode reference + /// lblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - physical block id + pub fn get_pblock_idx(&self, inode_ref: &Ext4InodeRef, lblock: Ext4Lblk) -> Result { + let search_path = self.find_extent(inode_ref, lblock); + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + let fblock = path.pblock; + + return Ok(fblock); + } + + return_errno_with_message!(Errno::EIO, "search extent fail"); + } + + /// Allocate a new block + pub fn allocate_new_block(&self, inode_ref: &mut Ext4InodeRef) -> Result { + let mut super_block = self.super_block; + let inodes_per_group = super_block.inodes_per_group(); + let bgid = (inode_ref.inode_num - 1) / inodes_per_group; + let index = (inode_ref.inode_num - 1) % inodes_per_group; + + // load block group + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let block_bitmap_block = block_group.get_block_bitmap_block(&super_block); + + let mut block_bmap_raw_data = self + .block_device + .read_offset(block_bitmap_block as usize * BLOCK_SIZE); + let mut data: &mut Vec = &mut block_bmap_raw_data; + let mut rel_blk_idx = 0; + + ext4_bmap_bit_find_clr(data, index, 0x8000, &mut rel_blk_idx); + ext4_bmap_bit_set(data, rel_blk_idx); + + block_group.set_block_group_balloc_bitmap_csum(&super_block, data); + self.block_device + .write_offset(block_bitmap_block as usize * BLOCK_SIZE, data); + + /* Update superblock free blocks count */ + let mut super_blk_free_blocks = super_block.free_blocks_count(); + super_blk_free_blocks -= 1; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Update inode blocks (different block size!) count */ + let mut inode_blocks = inode_ref.inode.blocks_count(); + inode_blocks += (BLOCK_SIZE / EXT4_INODE_BLOCK_SIZE) as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + /* Update block group free blocks count */ + let mut fb_cnt = block_group.get_free_blocks_count(); + fb_cnt -= 1; + block_group.set_free_blocks_count(fb_cnt as u32); + block_group.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + Ok(rel_blk_idx as Ext4Fsblk) + } + + /// Append a new block to the inode and update the extent tree. + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// iblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - physical block id of the new block + pub fn append_inode_pblk(&self, inode_ref: &mut Ext4InodeRef) -> Result { + let inode_size = inode_ref.inode.size(); + let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + + let mut newex: Ext4Extent = Ext4Extent::default(); + + let new_block = self.balloc_alloc_block(inode_ref, None)?; + + newex.first_block = iblock; + newex.store_pblock(new_block); + newex.block_count = min(1, EXT_MAX_BLOCKS - iblock) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + // Update the inode size + let mut inode_size = inode_ref.inode.size(); + inode_size += BLOCK_SIZE as u64; + inode_ref.inode.set_size(inode_size); + self.write_back_inode(inode_ref); + + Ok(new_block) + } + + /// Append a new block to the inode and update the extent tree.From a specific bgid + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// bgid: Start bgid of free block search + /// + /// Returns: + /// `Result` - physical block id of the new block + pub fn append_inode_pblk_from(&self, inode_ref: &mut Ext4InodeRef, start_bgid: &mut u32) -> Result { + let inode_size = inode_ref.inode.size(); + let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + + let mut newex: Ext4Extent = Ext4Extent::default(); + + let new_block = self.balloc_alloc_block_from(inode_ref, start_bgid)?; + + newex.first_block = iblock; + newex.store_pblock(new_block); + newex.block_count = min(1, EXT_MAX_BLOCKS - iblock) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + // Update the inode size + let mut inode_size = inode_ref.inode.size(); + inode_size += BLOCK_SIZE as u64; + inode_ref.inode.set_size(inode_size); + self.write_back_inode(inode_ref); + + Ok(new_block) + } + + /// Allocate a new inode + /// + /// Params: + /// inode_mode: u16 - inode mode + /// + /// Returns: + /// `Result` - inode number + pub fn alloc_inode(&self, is_dir: bool) -> Result { + // Allocate inode + let inode_num = self.ialloc_alloc_inode(is_dir)?; + + Ok(inode_num) + } + + pub fn correspond_inode_mode(&self, filetype: u8) -> u16 { + let file_type = DirEntryType::from_bits(filetype).unwrap(); + match file_type { + DirEntryType::EXT4_DE_REG_FILE => InodeFileType::S_IFREG.bits(), + DirEntryType::EXT4_DE_DIR => InodeFileType::S_IFDIR.bits(), + DirEntryType::EXT4_DE_SYMLINK => InodeFileType::S_IFLNK.bits(), + DirEntryType::EXT4_DE_CHRDEV => InodeFileType::S_IFCHR.bits(), + DirEntryType::EXT4_DE_BLKDEV => InodeFileType::S_IFBLK.bits(), + DirEntryType::EXT4_DE_FIFO => InodeFileType::S_IFIFO.bits(), + DirEntryType::EXT4_DE_SOCK => InodeFileType::S_IFSOCK.bits(), + _ => { + // FIXME: unsupported filetype + InodeFileType::S_IFREG.bits() + } + } + } +} diff --git a/lib/ext4_rs/src/ext4_impls/mod.rs b/lib/ext4_rs/src/ext4_impls/mod.rs new file mode 100644 index 0000000..8c64124 --- /dev/null +++ b/lib/ext4_rs/src/ext4_impls/mod.rs @@ -0,0 +1,15 @@ +pub mod extents; +pub mod ext4; +pub mod inode; +pub mod dir; +pub mod file; +pub mod ialloc; +pub mod balloc; + +pub use extents::*; +pub use ext4::*; +pub use inode::*; +pub use dir::*; +pub use file::*; +pub use ialloc::*; +pub use balloc::*; \ No newline at end of file diff --git a/lib/ext4_rs/src/fuse_interface/mod.rs b/lib/ext4_rs/src/fuse_interface/mod.rs new file mode 100644 index 0000000..ceceeb3 --- /dev/null +++ b/lib/ext4_rs/src/fuse_interface/mod.rs @@ -0,0 +1,683 @@ +use crate::prelude::*; + +use crate::ext4_defs::*; +use crate::return_errno; +use crate::return_errno_with_message; +use crate::utils::path_check; + +// export some definitions +pub use crate::ext4_defs::Ext4; +pub use crate::ext4_defs::BLOCK_SIZE; +pub use crate::ext4_defs::BlockDevice; +pub use crate::ext4_defs::InodeFileType; + +/// fuser interface for ext4 +impl Ext4 { + /// Look up a directory entry by name and get its attributes. + pub fn fuse_lookup(&self, parent: u64, name: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + self.dir_find_entry(parent as u32, name, &mut search_result)?; + + let inode_num = search_result.dentry.inode; + + let inode_ref = self.get_inode_ref(inode_num); + let file_attr = FileAttr::from_inode_ref(&inode_ref); + + Ok(file_attr) + } + + /// Get file attributes. + pub fn fuse_getattr(&self, ino: u64) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + let file_attr = FileAttr::from_inode_ref(&inode_ref); + Ok(file_attr) + } + + /// Set file attributes. + pub fn fuse_setattr( + &self, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + atime: Option, + mtime: Option, + ctime: Option, + fh: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, + flags: Option, + ) { + let mut inode_ref = self.get_inode_ref(ino as u32); + + let mut attr = FileAttr::default(); + + if let Some(mode) = mode { + let inode_file_type = + InodeFileType::from_bits(mode as u16 & EXT4_INODE_MODE_TYPE_MASK).unwrap(); + attr.kind = inode_file_type; + let inode_perm = InodePerm::from_bits(mode as u16 & EXT4_INODE_MODE_PERM_MASK).unwrap(); + attr.perm = inode_perm; + } + + if let Some(uid) = uid { + attr.uid = uid + } + + if let Some(gid) = gid { + attr.gid = gid + } + + if let Some(size) = size { + attr.size = size + } + + if let Some(atime) = atime { + attr.atime = atime + } + + if let Some(mtime) = mtime { + attr.mtime = mtime + } + + if let Some(ctime) = ctime { + attr.ctime = ctime + } + + if let Some(crtime) = crtime { + attr.crtime = crtime + } + + if let Some(chgtime) = chgtime { + attr.chgtime = chgtime + } + + if let Some(bkuptime) = bkuptime { + attr.bkuptime = bkuptime + } + + if let Some(flags) = flags { + attr.flags = flags + } + + inode_ref.set_attr(&attr); + + self.write_back_inode(&mut inode_ref); + } + + /// Read symbolic link. + fn fuse_readlink(&mut self, ino: u64) -> Result> { + let inode_ref = self.get_inode_ref(ino as u32); + let file_size = inode_ref.inode.size(); + let mut read_buf = vec![0; file_size as usize]; + let read_size = self.read_at(ino as u32, 0, &mut read_buf)?; + Ok(read_buf) + } + + + /// Create a regular file, character device, block device, fifo or socket node. + pub fn fuse_mknod( + &self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + rdev: u32, + ) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let inode_ref = self.create(parent as u32, name, mode as u16)?; + Ok(inode_ref) + } + + + /// Create a regular file, character device, block device, fifo or socket node. + pub fn fuse_mknod_with_attr( + &self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + rdev: u32, + uid: u32, + gid: u32, + ) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let inode_ref = self.create_with_attr(parent as u32, name, mode as u16, uid as u16, gid as u16)?; + Ok(inode_ref) + } + + /// Create a directory. + pub fn fuse_mkdir(&mut self, parent: u64, name: &str, mode: u32, umask: u32) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let file_type = InodeFileType::from_bits(mode as u16).unwrap(); + if file_type != InodeFileType::S_IFDIR { + // The mode is not a directory + return_errno_with_message!(Errno::EINVAL, "Invalid mode for directory creation"); + } + let inode_ref = self.create(parent as u32, name, mode as u16)?; + Ok(EOK) + } + + /// Create a directory. + pub fn fuse_mkdir_with_attr(&mut self, parent: u64, name: &str, mode: u32, umask: u32, uid:u32, gid:u32) -> Result { + + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + + // mkdir via fuse passes a mode of 0. so we need to set default mode + let file_type = match InodeFileType::from_bits(mode as u16) { + Some(file_type) => file_type, + None => InodeFileType::S_IFDIR, + }; + let mode = file_type.bits(); + let inode_ref = self.create_with_attr(parent as u32, name, mode, uid as u16, gid as u16)?; + + Ok(inode_ref) + } + + /// Remove a file. + pub fn fuse_unlink(&self, parent: u64, name: &str) -> Result { + // unlink actual remove a file + + // get child inode num + let mut parent_inode = parent as u32; + let mut nameoff = 0; + let child_inode = self.generic_open(name, &mut parent_inode, false, 0, &mut nameoff)?; + + let mut child_inode_ref = self.get_inode_ref(child_inode); + let child_link_cnt = child_inode_ref.inode.links_count(); + if child_link_cnt == 1 { + self.truncate_inode(&mut child_inode_ref, 0)?; + } + + // get child name + let mut is_goal = false; + let p = &name[nameoff as usize..]; + let len = path_check(p, &mut is_goal); + + // load parent + let mut parent_inode_ref = self.get_inode_ref(parent_inode); + + let r = self.unlink( + &mut parent_inode_ref, + &mut child_inode_ref, + &p[..len], + )?; + + Ok(EOK) + } + /// Remove a directory. + pub fn fuse_rmdir(&mut self, parent: u64, name: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent as u32, name, &mut search_result)?; + + let mut parent_inode_ref = self.get_inode_ref(parent as u32); + let mut child_inode_ref = self.get_inode_ref(search_result.dentry.inode); + + self.truncate_inode(&mut child_inode_ref, 0)?; + + self.unlink(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + + // to do + // ext4_inode_set_del_time + // ext4_inode_set_links_cnt + // ext4_fs_free_inode(&child) + + Ok(EOK) + } + /// Create a symbolic link. + pub fn fuse_symlink(&mut self, parent: u64, link_name: &str, target: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, link_name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + + let mut mode = 0o777; + let file_type = InodeFileType::S_IFLNK; + mode |= file_type.bits(); + + let inode_ref = self.create(parent as u32, link_name, mode)?; + Ok(EOK) + } + /// Create a hard link. + /// Params: + /// ino: the inode number of the source file + /// newparent: the inode number of the new parent directory + /// newname: the name of the new file + /// + /// + pub fn fuse_link(&mut self, ino: u64, newparent: u64, newname: &str) -> Result { + let mut parent_inode_ref = self.get_inode_ref(newparent as u32); + let mut child_inode_ref = self.get_inode_ref(ino as u32); + + // to do if child already exists we should not add . and .. in child directory + self.link(&mut parent_inode_ref, &mut child_inode_ref, newname)?; + + Ok(EOK) + } + + /// Open a file. + /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are + /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, + /// etc) in fh, and use this in other all other file operations (read, write, flush, + /// release, fsync). Filesystem may also implement stateless file I/O and not store + /// anything in fh. There are also some flags (direct_io, keep_cache) which the + /// filesystem may set, to change the way the file is opened. See fuse_file_info + /// structure in for more details. + pub fn fuse_open(&mut self, ino: u64, flags: i32) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + + // check permission + let file_type = inode_ref.inode.file_type(); + let file_perm = inode_ref.inode.file_perm(); + + let can_read = file_perm.contains(InodePerm::S_IREAD); + let can_write = file_perm.contains(InodePerm::S_IWRITE); + let can_execute = file_perm.contains(InodePerm::S_IEXEC); + + // If trying to open the file in write mode, check for write permissions + if ((flags & O_WRONLY != 0) || (flags & O_RDWR != 0)) && !can_write { + return_errno_with_message!(Errno::EACCES, "Permission denied can not write"); + } + // If trying to open the file in read mode, check for read permissions + if ((flags & O_RDONLY != 0) || (flags & O_RDWR != 0)) && !can_read { + return_errno_with_message!(Errno::EACCES, "Permission denied can not read"); + } + + // If trying to open the file in read mode, check for read permissions + if ((flags & O_EXCL != 0) || (flags & O_RDWR != 0)) && !can_execute { + return_errno_with_message!(Errno::EACCES, "Permission denied can not exec"); + } + + Ok(EOK) + } + + /// Read data. + /// Read should send exactly the number of bytes requested except on EOF or error, + /// otherwise the rest of the data will be substituted with zeroes. An exception to + /// this is when the file has been opened in 'direct_io' mode, in which case the + /// return value of the read system call will reflect the return value of this + /// operation. fh will contain the value set by the open method, or will be undefined + /// if the open method didn't set any value. + /// + /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// lock_owner: only supported with ABI >= 7.9 + pub fn fuse_read( + &self, + ino: u64, + fh: u64, + offset: i64, + size: u32, + flags: i32, + lock_owner: Option, + ) -> Result> { + let mut data = vec![0u8; size as usize]; + let read_size = self.read_at(ino as u32, offset as usize, &mut data)?; + let r = data[..read_size].to_vec(); + Ok(r) + } + + /// Write data. + /// Write should return exactly the number of bytes requested except on error. An + /// exception to this is when the file has been opened in 'direct_io' mode, in + /// which case the return value of the write system call will reflect the return + /// value of this operation. fh will contain the value set by the open method, or + /// will be undefined if the open method didn't set any value. + /// + /// write_flags: will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, + /// the pid, uid, gid, and fh may not match the value that would have been sent if write cachin + /// is disabled + /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// lock_owner: only supported with ABI >= 7.9 + pub fn fuse_write( + &self, + ino: u64, + fh: u64, + offset: i64, + data: &[u8], + write_flags: u32, + flags: i32, + lock_owner: Option, + ) -> Result { + let write_size = self.write_at(ino as u32, offset as usize, data)?; + Ok(write_size) + } + + /// Open a directory. + /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and + /// use this in other all other directory stream operations (readdir, releasedir, + /// fsyncdir). Filesystem may also implement stateless directory I/O and not store + /// anything in fh, though that makes it impossible to implement standard conforming + /// directory stream operations in case the contents of the directory can change + /// between opendir and releasedir. + pub fn fuse_opendir(&mut self, ino: u64, flags: i32) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + + // 检查是否为目录 + if !inode_ref.inode.is_dir() { + return_errno_with_message!(Errno::ENOTDIR, "Not a directory"); + } + + // // 检查权限(例如,只允许读取目录) + // let file_perm = inode_ref.inode.file_perm(); + // if !file_perm.contains(InodePerm::S_IREAD) { + // return_errno_with_message!(Errno::EACCES, "Permission denied"); + // } + + // 成功打开目录,返回文件句柄(这里假设返回 inode 编号作为文件句柄) + Ok(ino as usize) + } + + /// Read directory. + /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// requested size. Send an empty buffer on end of stream. fh will contain the + /// value set by the opendir method, or will be undefined if the opendir method + /// didn't set any value. + pub fn fuse_readdir(&self, ino: u64, fh: u64, offset: i64) -> Result> { + let mut entries = self.dir_get_entries(ino as u32); + entries = entries[offset as usize..].to_vec(); + Ok(entries) + } + + /// Create and open a file. + /// If the file does not exist, first create it with the specified mode, and then + /// open it. Open flags (with the exception of O_NOCTTY) are available in flags. + /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, + /// and use this in other all other file operations (read, write, flush, release, + /// fsync). There are also some flags (direct_io, keep_cache) which the + /// filesystem may set, to change the way the file is opened. See fuse_file_info + /// structure in for more details. If this method is not + /// implemented or under Linux kernel versions earlier than 2.6.15, the mknod() + /// and open() methods will be called instead. + pub fn fuse_create( + &mut self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + flags: i32, + ) -> Result { + // check file exist + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + let inode_ref = self.get_inode_ref(search_result.dentry.inode); + + // check permission + let file_perm = inode_ref.inode.file_perm(); + + let can_read = file_perm.contains(InodePerm::S_IREAD); + let can_write = file_perm.contains(InodePerm::S_IWRITE); + let can_execute = file_perm.contains(InodePerm::S_IEXEC); + + // If trying to open the file in write mode, check for write permissions + if ((flags & O_WRONLY != 0) || (flags & O_RDWR != 0)) && !can_write { + return_errno_with_message!(Errno::EACCES, "Permission denied can not write"); + } + + // If trying to open the file in read mode, check for read permissions + if (flags & O_RDONLY != 0) || (flags & O_RDWR != 0) && !can_read{ + return_errno_with_message!(Errno::EACCES, "Permission denied can not read"); + } + + // If trying to open the file in read mode, check for read permissions + if (flags & O_EXCL != 0) || (flags & O_RDWR != 0) && !can_execute { + return_errno_with_message!(Errno::EACCES, "Permission denied can not exec"); + } + + return Ok(EOK); + } else { + //create file + let inode_ref = self.create(parent as u32, name, mode as u16)?; + } + + Ok(EOK) + } + + /// Check file access permissions. + /// This will be called for the access() system call. If the 'default_permissions' + /// mount option is given, this method is not called. This method is not called + /// under Linux kernel versions 2.4.x + /// int access(const char *pathname, int mode); + /// int faccessat(int dirfd, const char *pathname, int mode, int flags); + /// + /// uid and gid come from request + pub fn fuse_access(&mut self, ino: u64, uid: u16, gid: u16, mode: u16, mask: i32) -> bool { + let inode_ref = self.get_inode_ref(ino as u32); + + inode_ref.inode.check_access(uid, gid, mode, mask as u16) + } + + /// Get file system statistics. + /// Linux stat syscall defines: + /// int stat(const char *restrict pathname, struct stat *restrict statbuf); + /// int fstatat(int dirfd, const char *restrict pathname, struct stat *restrict statbuf, int flags); + pub fn fuse_statfs(&mut self, ino: u64) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + let linux_stat = LinuxStat::from_inode_ref(&inode_ref); + Ok(linux_stat) + } + + /// Initialize filesystem. + /// Called before any other filesystem method. + /// The kernel module connection can be configured using the KernelConfig object + pub fn fuse_init(&mut self) -> Result { + Ok(EOK) + } + + /// Clean up filesystem. + /// Called on filesystem exit. + pub fn fuse_destroy(&mut self) -> Result { + Ok(EOK) + } + + /// Rename a file. + fn fuse_rename(&mut self, parent: u64, name: &str, newparent: u64, newname: &str, flags: u32) { + unimplemented!(); + } + + /// Flush method. + /// This is called on each close() of the opened file. Since file descriptors can + /// be duplicated (dup, dup2, fork), for one open call there may be many flush + /// calls. Filesystems shouldn't assume that flush will always be called after some + /// writes, or that if will be called at all. fh will contain the value set by the + /// open method, or will be undefined if the open method didn't set any value. + /// NOTE: the name of the method is misleading, since (unlike fsync) the filesystem + /// is not forced to flush pending writes. One reason to flush data, is if the + /// filesystem wants to return write errors. If the filesystem supports file locking + /// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'. + fn fuse_flush(&mut self, ino: u64, fh: u64, lock_owner: u64) { + unimplemented!(); + } + + /// Release an open file. + /// Release is called when there are no more references to an open file: all file + /// descriptors are closed and all memory mappings are unmapped. For every open + /// call there will be exactly one release call. The filesystem may reply with an + /// error, but error values are not returned to close() or munmap() which triggered + /// the release. fh will contain the value set by the open method, or will be undefined + /// if the open method didn't set any value. flags will contain the same flags as for + /// open. + fn fuse_release( + &mut self, + _ino: u64, + _fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + ) { + unimplemented!(); + } + + /// Synchronize file contents. + /// If the datasync parameter is non-zero, then only the user data should be flushed, + /// not the meta data. + fn fuse_fsync(&mut self, ino: u64, fh: u64, datasync: bool) { + unimplemented!(); + } + + /// Read directory. + /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// requested size. Send an empty buffer on end of stream. fh will contain the + /// value set by the opendir method, or will be undefined if the opendir method + /// didn't set any value. + fn fuse_readdirplus(&mut self, ino: u64, fh: u64, offset: i64) { + unimplemented!(); + } + + /// Release an open directory. + /// For every opendir call there will be exactly one releasedir call. fh will + /// contain the value set by the opendir method, or will be undefined if the + /// opendir method didn't set any value. + fn fuse_releasedir(&mut self, _ino: u64, _fh: u64, _flags: i32) { + unimplemented!(); + } + + /// Synchronize directory contents. + /// If the datasync parameter is set, then only the directory contents should + /// be flushed, not the meta data. fh will contain the value set by the opendir + /// method, or will be undefined if the opendir method didn't set any value. + fn fuse_fsyncdir(&mut self, ino: u64, fh: u64, datasync: bool) { + unimplemented!(); + } + + /// Set an extended attribute. + fn fuse_setxattr(&mut self, ino: u64, name: &str, _value: &[u8], flags: i32, position: u32) { + unimplemented!(); + } + + /// Get an extended attribute. + /// If `size` is 0, the size of the value should be sent with `reply.size()`. + /// If `size` is not 0, and the value fits, send it with `reply.data()`, or + /// `reply.error(ERANGE)` if it doesn't. + fn fuse_getxattr(&mut self, ino: u64, name: &str, size: u32) { + unimplemented!(); + } + + /// List extended attribute names. + /// If `size` is 0, the size of the value should be sent with `reply.size()`. + /// If `size` is not 0, and the value fits, send it with `reply.data()`, or + /// `reply.error(ERANGE)` if it doesn't. + fn fuse_listxattr(&mut self, ino: u64, size: u32) { + unimplemented!(); + } + + /// Remove an extended attribute. + fn fuse_removexattr(&mut self, ino: u64, name: &str) { + unimplemented!(); + } + + /// Test for a POSIX file lock. + fn fuse_getlk( + &mut self, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + ) { + unimplemented!(); + } + + /// Acquire, modify or release a POSIX file lock. + /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but + /// otherwise this is not always the case. For checking lock ownership, + /// 'fi->owner' must be used. The l_pid field in 'struct flock' should only be + /// used to fill in this field in getlk(). Note: if the locking methods are not + /// implemented, the kernel will still allow file locking to work locally. + /// Hence these are only interesting for network filesystems and similar. + fn fuse_setlk( + &mut self, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + sleep: bool, + ) { + unimplemented!(); + } + + /// Map block index within file to block index within device. + /// Note: This makes sense only for block device backed filesystems mounted + /// with the 'blkdev' option + fn fuse_bmap(&mut self, ino: u64, blocksize: u32, idx: u64) { + unimplemented!(); + } + + /// control device + fn fuse_ioctl( + &mut self, + ino: u64, + fh: u64, + flags: u32, + cmd: u32, + in_data: &[u8], + out_size: u32, + ) { + unimplemented!(); + } + + /// Poll for events + // #[cfg(feature = "abi-7-11")] + // fn fuse_poll( + // &mut self, + // ino: u64, + // fh: u64, + // kh: u64, + // events: u32, + // flags: u32, + // ) { + // } + + /// Preallocate or deallocate space to a file + fn fuse_fallocate(&mut self, ino: u64, fh: u64, offset: i64, length: i64, mode: i32) { + unimplemented!(); + } + + /// Reposition read/write file offset + fn fuse_lseek(&mut self, ino: u64, fh: u64, offset: i64, whence: i32) { + unimplemented!(); + } + + /// Copy the specified range from the source inode to the destination inode + fn fuse_copy_file_range( + &mut self, + ino_in: u64, + fh_in: u64, + offset_in: i64, + ino_out: u64, + fh_out: u64, + offset_out: i64, + len: u64, + flags: u32, + ) { + unimplemented!(); + } +} diff --git a/lib/ext4_rs/src/lib.rs b/lib/ext4_rs/src/lib.rs new file mode 100644 index 0000000..f311b7b --- /dev/null +++ b/lib/ext4_rs/src/lib.rs @@ -0,0 +1,23 @@ +#![feature(error_in_core)] +#![no_std] +#![allow(unused)] + +extern crate alloc; + +pub mod utils; +pub mod prelude; + +pub use utils::*; +pub use prelude::*; + + +mod ext4_defs; +mod ext4_impls; + + +pub mod simple_interface; +pub mod fuse_interface; + + +pub use simple_interface::*; +pub use fuse_interface::*; diff --git a/lib/ext4_rs/src/main.rs b/lib/ext4_rs/src/main.rs new file mode 100644 index 0000000..e26ca85 --- /dev/null +++ b/lib/ext4_rs/src/main.rs @@ -0,0 +1,175 @@ +#![feature(error_in_core)] +#![allow(unused)] + +extern crate alloc; + +mod prelude; +mod utils; + +use prelude::*; +use utils::*; + +mod ext4_defs; +mod ext4_impls; + +mod fuse_interface; +mod simple_interface; + +use ext4_defs::*; +use fuse_interface::*; +use simple_interface::*; + +use log::{Level, LevelFilter, Metadata, Record}; + +macro_rules! with_color { + ($color_code:expr, $($arg:tt)*) => {{ + format_args!("\u{1B}[{}m{}\u{1B}[m", $color_code as u8, format_args!($($arg)*)) + }}; +} + +struct SimpleLogger; + +impl log::Log for SimpleLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Trace + } + + fn log(&self, record: &Record) { + let level = record.level(); + let args_color = match level { + Level::Error => ColorCode::Red, + Level::Warn => ColorCode::Yellow, + Level::Info => ColorCode::Green, + Level::Debug => ColorCode::Cyan, + Level::Trace => ColorCode::BrightBlack, + }; + + if self.enabled(record.metadata()) { + println!( + "{} - {}", + record.level(), + with_color!(args_color, "{}", record.args()) + ); + } + } + + fn flush(&self) {} +} + +#[repr(u8)] +enum ColorCode { + Red = 31, + Green = 32, + Yellow = 33, + Cyan = 36, + BrightBlack = 90, +} + +#[derive(Debug)] +pub struct Disk {} + +impl BlockDevice for Disk { + fn read_offset(&self, offset: usize) -> Vec { + // log::info!("read_offset: {:x?}", offset); + use std::fs::OpenOptions; + use std::io::{Read, Seek}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + let mut buf = vec![0u8; BLOCK_SIZE as usize]; + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.read_exact(&mut buf); + + buf + } + + fn write_offset(&self, offset: usize, data: &[u8]) { + use std::fs::OpenOptions; + use std::io::{Seek, Write}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.write_all(&data); + } +} + +fn main() { + log::set_logger(&SimpleLogger).unwrap(); + log::set_max_level(LevelFilter::Trace); + let disk = Arc::new(Disk {}); + let ext4 = Ext4::open(disk); + + // file read + let path = "test_files/0.txt"; + // 1G + const READ_SIZE: usize = (0x100000 * 1024); + let mut read_buf = vec![0u8; READ_SIZE as usize]; + let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); + let mut data = vec![0u8; READ_SIZE as usize]; + let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); + log::info!("read data {:?}", &data[..10]); + + + + let path = "test_files/linktest"; + let mut read_buf = vec![0u8; READ_SIZE as usize]; + // 2 is root inode + let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); + let mut data = vec![0u8; READ_SIZE as usize]; + let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); + log::info!("read data {:?}", &data[..10]); + + // dir make + log::info!("----mkdir----"); + for i in 0..10 { + let path = format!("dirtest{}", i); + let path = path.as_str(); + log::info!("mkdir making {:?}", path); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); + } + let path = "dir1/dir2/dir3/dir4/dir5/dir6"; + log::info!("mkdir making {:?}", path); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); + + // dir ls + let entries = ext4.dir_get_entries(ROOT_INODE); + log::info!("dir ls root"); + for entry in entries { + log::info!("{:?}", entry.get_name()); + } + + // file remove + let path = "test_files/file_to_remove"; + let r = ext4.file_remove(&path); + + // dir remove + let path = "dir_to_remove"; + let r = ext4.dir_remove(ROOT_INODE, &path); + + // file create/write + log::info!("----create file----"); + let inode_mode = InodeFileType::S_IFREG.bits(); + let inode_perm = (InodePerm::S_IREAD | InodePerm::S_IWRITE).bits(); + let inode_ref = ext4.create(ROOT_INODE, "4G.txt", inode_mode | inode_perm).unwrap(); + log::info!("----write file----"); + const WRITE_SIZE: usize = (0x100000 * (4096)); + let write_buf = vec![0x41 as u8; WRITE_SIZE]; + let r = ext4.write_at(inode_ref.inode_num, 0, &write_buf); + + // check + let path = "4G.txt"; + let mut read_buf = vec![0u8; WRITE_SIZE as usize]; + let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); + let mut data = vec![0u8; WRITE_SIZE as usize]; + let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); + log::info!("read data {:?}", &data[..10]); + +} diff --git a/lib/ext4_rs/src/prelude.rs b/lib/ext4_rs/src/prelude.rs new file mode 100644 index 0000000..8a2e150 --- /dev/null +++ b/lib/ext4_rs/src/prelude.rs @@ -0,0 +1,29 @@ +#![allow(unused)] +#![feature(error_in_core)] + +extern crate alloc; + +pub(crate) use alloc::boxed::Box; +pub(crate) use alloc::collections::BTreeMap; +pub(crate) use alloc::collections::BTreeSet; +pub(crate) use alloc::collections::LinkedList; +pub(crate) use alloc::collections::VecDeque; +pub(crate) use alloc::ffi::CString; +pub(crate) use alloc::string::String; +pub(crate) use alloc::string::ToString; +pub(crate) use alloc::sync::Arc; +pub(crate) use alloc::sync::Weak; +pub(crate) use alloc::vec; +pub(crate) use alloc::vec::Vec; +pub(crate) use core::any::Any; +pub(crate) use core::ffi::CStr; +pub(crate) use core::fmt::Debug; +pub(crate) use core::mem::size_of; +pub(crate) use core::cmp::min; + + +pub(crate) use bitflags::bitflags; +pub(crate) use log::{debug, info, trace, warn}; + +pub(crate) use crate::utils::errors::*; +pub(crate) type Result = core::result::Result; diff --git a/lib/ext4_rs/src/simple_interface/mod.rs b/lib/ext4_rs/src/simple_interface/mod.rs new file mode 100644 index 0000000..24f7992 --- /dev/null +++ b/lib/ext4_rs/src/simple_interface/mod.rs @@ -0,0 +1,164 @@ +use core::panic::RefUnwindSafe; + +use crate::prelude::*; + +use crate::ext4_defs::*; +use crate::return_errno; +use crate::return_errno_with_message; +use crate::utils::path_check; + +// export some definitions +pub use crate::ext4_defs::Ext4; +pub use crate::ext4_defs::BlockDevice; +pub use crate::ext4_defs::InodeFileType; +pub use crate::ext4_defs::Ext4DirSearchResult; +pub use crate::ext4_defs::Ext4DirEntry; +pub use crate::ext4_defs::Ext4InodeRef; +pub use crate::ext4_defs::Ext4DirEntryTail; +pub use crate::ext4_defs::Ext4Fsblk; +pub use crate::ext4_defs::Ext4Inode; +pub use crate::ext4_defs::Block; +pub use crate::ext4_defs::BLOCK_SIZE; + +/// simple interface for ext4 +impl Ext4 { + + /// Parse the file access flags (such as "r", "w", "a", etc.) and convert them to system constants. + /// + /// This method parses common file access flags into their corresponding bitwise constants defined in `libc`. + /// + /// # Arguments + /// * `flags` - The string representation of the file access flags (e.g., "r", "w", "a", "r+", etc.). + /// + /// # Returns + /// * `Result` - The corresponding bitwise flag constants (e.g., `O_RDONLY`, `O_WRONLY`, etc.), or an error if the flags are invalid. + fn ext4_parse_flags(&self, flags: &str) -> Result { + match flags { + "r" | "rb" => Ok(O_RDONLY), + "w" | "wb" => Ok(O_WRONLY | O_CREAT | O_TRUNC), + "a" | "ab" => Ok(O_WRONLY | O_CREAT | O_APPEND), + "r+" | "rb+" | "r+b" => Ok(O_RDWR), + "w+" | "wb+" | "w+b" => Ok(O_RDWR | O_CREAT | O_TRUNC), + "a+" | "ab+" | "a+b" => Ok(O_RDWR | O_CREAT | O_APPEND), + _ => Err(Ext4Error::new(Errno::EINVAL)), + } + } + + /// Open a file at the specified path and return the corresponding inode number. + /// + /// Open a file by searching for the given path starting from the root directory (`ROOT_INODE`). + /// If the file does not exist and the `O_CREAT` flag is specified, the file will be created. + /// + /// # Arguments + /// * `path` - The path of the file to open. + /// * `flags` - The access flags (e.g., "r", "w", "a", etc.). + /// + /// # Returns + /// * `Result` - Returns the inode number of the opened file if successful. + pub fn ext4_file_open( + &self, + path: &str, + flags: &str, + ) -> Result { + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFREG; + + let iflags = self.ext4_parse_flags(flags).unwrap(); + + let filetype = InodeFileType::S_IFDIR; + + let mut create = false; + if iflags & O_CREAT != 0 { + create = true; + } + + self.generic_open(path, &mut parent_inode_num, create, filetype.bits(), &mut 0) + } + + /// Create a new directory at the specified path. + /// + /// Checks if the directory already exists by searching from the root directory (`ROOT_INODE`). + /// If the directory does not exist, it creates the directory under the root directory and returns its inode number. + /// + /// # Arguments + /// * `path` - The path where the directory will be created. + /// + /// # Returns + /// * `Result` - The inode number of the newly created directory if successful, + /// or an error (`Errno::EEXIST`) if the directory already exists. + pub fn ext4_dir_mk(&self, path: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(ROOT_INODE, path, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFDIR; + + self.generic_open(path, &mut parent_inode_num, true, filetype.bits(), &mut 0) + } + + + /// Open a directory at the specified path and return the corresponding inode number. + /// + /// Opens a directory by searching for the given path starting from the root directory (`ROOT_INODE`). + /// + /// # Arguments + /// * `path` - The path of the directory to open. + /// + /// # Returns + /// * `Result` - Returns the inode number of the opened directory if successful. + pub fn ext4_dir_open( + &self, + path: &str, + ) -> Result { + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFDIR; + self.generic_open(path, &mut parent_inode_num, false, filetype.bits(), &mut 0) + } + + /// Read data from a file starting from a given offset. + /// + /// Reads data from the file starting at the specified inode (`ino`), with a given offset and size. + /// + /// # Arguments + /// * `ino` - The inode number of the file to read from. + /// * `size` - The number of bytes to read. + /// * `offset` - The offset from where to start reading. + /// + /// # Returns + /// * `Result>` - The data read from the file. + pub fn ext4_file_read( + &self, + ino: u64, + size: u32, + offset: i64, + ) -> Result> { + let mut data = vec![0u8; size as usize]; + let read_size = self.read_at(ino as u32, offset as usize, &mut data)?; + let r = data[..read_size].to_vec(); + Ok(r) + } + + /// Write data to a file starting at a given offset. + /// + /// Writes data to the file starting at the specified inode (`ino`) and offset. + /// + /// # Arguments + /// * `ino` - The inode number of the file to write to. + /// * `offset` - The offset in the file where the data will be written. + /// * `data` - The data to write to the file. + /// + /// # Returns + /// * `Result` - The number of bytes written to the file. + pub fn ext4_file_write( + &self, + ino: u64, + offset: i64, + data: &[u8], + ) -> Result { + let write_size = self.write_at(ino as u32, offset as usize, data)?; + Ok(write_size) + } + +} \ No newline at end of file diff --git a/lib/ext4_rs/src/utils/bitmap.rs b/lib/ext4_rs/src/utils/bitmap.rs new file mode 100644 index 0000000..fbdb81e --- /dev/null +++ b/lib/ext4_rs/src/utils/bitmap.rs @@ -0,0 +1,89 @@ +/// 检查位图中的某一位是否被设置 +/// 参数 bmap: 位图数组 +/// 参数 bit: 位图中的位索引 +pub fn ext4_bmap_is_bit_set(bmap: &[u8], bit: u32) -> bool { + bmap[(bit >> 3) as usize] & (1 << (bit & 7)) != 0 +} + +/// 检查位图中的某一位是否被清除 +/// 参数 bmap: 位图数组 +/// 参数 bit: 位图中的位索引 +pub fn ext4_bmap_is_bit_clr(bmap: &[u8], bit: u32) -> bool { + !ext4_bmap_is_bit_set(bmap, bit) +} + +/// 设置位图中的某一位 +/// 参数 bmap: 位图数组 +/// 参数 bit: 位图中的位索引 +pub fn ext4_bmap_bit_set(bmap: &mut [u8], bit: u32) { + bmap[(bit >> 3) as usize] |= 1 << (bit & 7); +} + +/// 清除位图中的某一位 +/// 参数 bmap: 位图数组 +/// 参数 bit: 位图中的位索引 +pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { + bmap[(bit >> 3) as usize] &= !(1 << (bit & 7)); +} + +/// 查询位图中的空闲位 +/// 参数 bmap: 位图数组 +/// 参数 sbit: 起始位索引 +/// 参数 ebit: 结束位索引 +/// 参数 bit_id: 用于存储空闲位的索引 +pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u32) -> bool { + let mut i: u32; + let mut bcnt = ebit - sbit; + + i = sbit; + + while i & 7 != 0 { + if bcnt == 0 { + return false; + } + + if ext4_bmap_is_bit_clr(bmap, i) { + *bit_id = sbit; + return true; + } + + i += 1; + bcnt -= 1; + } + + let mut sbit = i; + let mut bmap = &bmap[(sbit >> 3) as usize..]; + while bcnt >= 8 { + if bmap[0] != 0xFF { + for i in 0..8 { + if ext4_bmap_is_bit_clr(bmap, i) { + *bit_id = sbit + i; + return true; + } + } + } + + bmap = &bmap[1..]; + bcnt -= 8; + sbit += 8; + } + + for i in 0..bcnt { + if ext4_bmap_is_bit_clr(bmap, i) { + *bit_id = sbit + i; + return true; + } + } + + false +} + +/// 清除位图中的一段位 +/// 参数 bmap: Mutable reference to the bitmap array. +/// 参数 start_bit: The start index of the bit range to clear. +/// 参数 end_bit: The end index of the bit range to clear. +pub fn ext4_bmap_bits_free(bmap: &mut [u8], start_bit: u32, end_bit: u32) { + for bit in start_bit..=end_bit { + ext4_bmap_bit_clr(bmap, bit); + } +} \ No newline at end of file diff --git a/lib/ext4_rs/src/utils/crc.rs b/lib/ext4_rs/src/utils/crc.rs new file mode 100644 index 0000000..16404c2 --- /dev/null +++ b/lib/ext4_rs/src/utils/crc.rs @@ -0,0 +1,78 @@ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +pub const CRC32C_TAB: [u32; 256] = [ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, +]; + + +pub const EXT4_CRC32_INIT: u32 = 0xFFFFFFFF; + +/// 计算CRC32校验和 +/// 参数 crc 初始值 +/// 参数 buf 缓冲区 +/// 参数 size 缓冲区大小 +/// 参数 tab 查找表 +pub fn crc32(crc: u32, buf: &[u8], size: u32, tab: &[u32]) -> u32 { + let mut crc = crc; + let mut p = buf; + let mut size = size as usize; + + // 循环更新crc值 + while size > 0 { + crc = tab[(crc as u8 ^ p[0]) as usize] ^ (crc >> 8); + p = &p[1..]; + size -= 1; + } + + crc +} + +pub fn ext4_crc32c(crc: u32, buf: &[u8], size: u32) -> u32 { + crc32(crc, buf, size, &CRC32C_TAB) +} \ No newline at end of file diff --git a/lib/ext4_rs/src/utils/errors.rs b/lib/ext4_rs/src/utils/errors.rs new file mode 100644 index 0000000..0e82459 --- /dev/null +++ b/lib/ext4_rs/src/utils/errors.rs @@ -0,0 +1,94 @@ + + +/// Ext4Error number. +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Errno { + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + EINTR = 4, /* Interrupted system call */ + EIO = 5, /* I/O error */ + ENXIO = 6, /* No such device or address */ + E2BIG = 7, /* Argument list too long */ + EBADF = 9, /* Bad file number */ + EAGAIN = 11, /* Try again */ + ENOMEM = 12, /* Out of memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device or resource busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* No such device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* File table overflow */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Not a typewriter */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + ENAMETOOLONG = 36, /* File name too long */ + ENOTSUP = 95, /* Not supported */ +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(unused)] +pub struct Ext4Error { + errno: Errno, + msg: Option<&'static str>, +} + +impl Ext4Error { + pub const fn new(errno: Errno) -> Self { + Ext4Error { errno, msg: None } + } + + pub const fn with_message(errno: Errno, msg: &'static str) -> Self { + Ext4Error { + errno, + msg: Some(msg), + } + } + + pub const fn error(&self) -> Errno { + self.errno + } +} + +impl From for Ext4Error { + fn from(errno: Errno) -> Self { + Ext4Error::new(errno) + } +} + +impl From for Ext4Error { + fn from(_: core::str::Utf8Error) -> Self { + Ext4Error::with_message(Errno::EINVAL, "Invalid utf-8 string") + } +} + +impl From for Ext4Error { + fn from(_: alloc::string::FromUtf8Error) -> Self { + Ext4Error::with_message(Errno::EINVAL, "Invalid utf-8 string") + } +} + +#[macro_export] +macro_rules! return_errno { + ($errno: expr) => { + return Err(Ext4Error::new($errno)) + }; +} + +#[macro_export] +macro_rules! return_errno_with_message { + ($errno: expr, $message: expr) => { + return Err(Ext4Error::with_message($errno, $message)) + }; +} diff --git a/lib/ext4_rs/src/utils/mod.rs b/lib/ext4_rs/src/utils/mod.rs new file mode 100644 index 0000000..5ff44ba --- /dev/null +++ b/lib/ext4_rs/src/utils/mod.rs @@ -0,0 +1,11 @@ +pub mod bitmap; +pub mod crc; +pub mod path; +pub mod errors; + + + +pub use bitmap::*; +pub use crc::*; +pub use path::*; +pub use errors::*; \ No newline at end of file diff --git a/lib/ext4_rs/src/utils/path.rs b/lib/ext4_rs/src/utils/path.rs new file mode 100644 index 0000000..472fe40 --- /dev/null +++ b/lib/ext4_rs/src/utils/path.rs @@ -0,0 +1,63 @@ +use alloc::string::String; +use alloc::vec::Vec; + +pub fn path_check(path: &str, is_goal: &mut bool) -> usize { + // 遍历字符串中的每个字符及其索引 + for (i, c) in path.chars().enumerate() { + // 检查是否到达文件名的最大长度限制 + if i >= 255 { + break; + } + + // 检查字符是否是路径分隔符 + if c == '/' { + *is_goal = false; + return i; + } + + // 检查是否达到字符串结尾 + if c == '\0' { + *is_goal = true; + return i; + } + } + + // 如果没有找到 '/' 或 '\0',且长度小于最大文件名长度 + *is_goal = true; + path.len() +} + +#[cfg(test)] +mod path_tests { + use super::*; + #[test] + fn test_ext4_path_check() { + let mut is_goal = false; + + // 测试根路径 + assert_eq!(path_check("/", &mut is_goal), 0); + assert!(!is_goal, "Root path should not set is_goal to true"); + + // 测试普通路径 + assert_eq!(path_check("/home/user/file.txt", &mut is_goal), 0); + assert!(!is_goal, "Normal path should not set is_goal to true"); + + // 测试没有斜杠的路径 + let path = "file.txt"; + assert_eq!(path_check(path, &mut is_goal), path.len()); + assert!(is_goal, "Path without slashes should set is_goal to true"); + + // 测试路径末尾的 null 字符 + let path = "home\0"; + assert_eq!(path_check(path, &mut is_goal), 4); + assert!( + is_goal, + "Path with null character should set is_goal to true" + ); + + // // 测试超长文件名 + // let long_path = "a".repeat(EXT4_DIRECTORY_FILENAME_LEN + 10); + // assert_eq!(ext4_path_check(&long_path, &mut is_goal), EXT4_DIRECTORY_FILENAME_LEN); + // assert!(!is_goal, "Long filename should not set is_goal to true and should be truncated"); + } +} diff --git a/lib/lwext4_rust/.github/workflows/actions/setup-musl/action.yml b/lib/lwext4_rust/.github/workflows/actions/setup-musl/action.yml new file mode 100644 index 0000000..b8aee36 --- /dev/null +++ b/lib/lwext4_rust/.github/workflows/actions/setup-musl/action.yml @@ -0,0 +1,38 @@ +name: Download musl toolchain + +inputs: + arch: + description: 'Architecture' + required: true + type: string + +runs: + using: "composite" + steps: + - name: Cache musl + id: cache-musl + uses: actions/cache/restore@v3 + with: + path: ${{ inputs.arch }}-linux-musl-cross + key: ${{ inputs.arch }}-linux-musl-cross + - name: Download musl toolchain + if: steps.cache-musl.outputs.cache-hit != 'true' + shell: bash + run: | + MUSL_PATH=${{ inputs.arch }}-linux-musl-cross + wget https://musl.cc/${MUSL_PATH}.tgz + tar -xf ${MUSL_PATH}.tgz + - uses: actions/cache/save@v3 + if: steps.cache-musl.outputs.cache-hit != 'true' + with: + path: ${{ inputs.arch }}-linux-musl-cross + key: ${{ inputs.arch }}-linux-musl-cross + + - name: Add to PATH environment variable + shell: bash + run: | + echo "$PWD/${{ inputs.arch }}-linux-musl-cross/bin" >> $GITHUB_PATH + - name: Verify installation + shell: bash + run: | + ${{ inputs.arch }}-linux-musl-gcc --version diff --git a/lib/lwext4_rust/.github/workflows/actions/setup-qemu/action.yml b/lib/lwext4_rust/.github/workflows/actions/setup-qemu/action.yml new file mode 100644 index 0000000..0f8bc4d --- /dev/null +++ b/lib/lwext4_rust/.github/workflows/actions/setup-qemu/action.yml @@ -0,0 +1,46 @@ +name: Download and build QEMU + +inputs: + qemu-version: + description: 'QEMU version' + required: true + type: string + +runs: + using: "composite" + steps: + - name: Cache QEMU + id: cache-qemu + uses: actions/cache/restore@v3 + with: + path: qemu_build + key: qemu-${{ inputs.qemu-version }}-slirp-1 + - name: Download and build QEMU + if: steps.cache-qemu.outputs.cache-hit != 'true' + env: + QEMU_PATH: qemu-${{ inputs.qemu-version }} + PREFIX: ${{ github.workspace }}/qemu_build + shell: bash + run: | + sudo apt-get update && sudo apt-get install -y ninja-build libslirp-dev + wget https://download.qemu.org/$QEMU_PATH.tar.xz && tar -xJf $QEMU_PATH.tar.xz + cd $QEMU_PATH \ + && ./configure --prefix=$PREFIX --target-list=x86_64-softmmu,riscv64-softmmu,aarch64-softmmu --enable-slirp \ + && make -j > /dev/null 2>&1 \ + && make install + - uses: actions/cache/save@v3 + if: steps.cache-qemu.outputs.cache-hit != 'true' + with: + path: qemu_build + key: qemu-${{ inputs.qemu-version }}-slirp-1 + + - name: Install QEMU + shell: bash + run: | + echo "$PWD/qemu_build/bin" >> $GITHUB_PATH + - name: Verify installation + shell: bash + run: | + qemu-system-x86_64 --version + qemu-system-aarch64 --version + qemu-system-riscv64 --version diff --git a/lib/lwext4_rust/.github/workflows/build.yml b/lib/lwext4_rust/.github/workflows/build.yml new file mode 100644 index 0000000..4f82c62 --- /dev/null +++ b/lib/lwext4_rust/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: Build CI + +on: [push, pull_request] + +jobs: + + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + arch: [riscv64] + rust-toolchain: [nightly, nightly-2024-01-31] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust-toolchain }} + override: true + components: rust-src + target: riscv64gc-unknown-none-elf + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + + - uses: ./.github/workflows/actions/setup-musl + with: + arch: ${{ matrix.arch }} + - name: Build ${{ github.repository }} + run: | + cargo build -vv --target riscv64gc-unknown-none-elf diff --git a/lib/lwext4_rust/.github/workflows/test.yml b/lib/lwext4_rust/.github/workflows/test.yml new file mode 100644 index 0000000..f677212 --- /dev/null +++ b/lib/lwext4_rust/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: Test CI + +on: [push, pull_request] + +env: + qemu-version: 8.2.0 + rust-toolchain: nightly-2024-01-31 + +jobs: + + examples-test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + arch: [riscv64] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - uses: ./.github/workflows/actions/setup-qemu + with: + qemu-version: ${{ env.qemu-version }} + - uses: ./.github/workflows/actions/setup-musl + with: + arch: ${{ matrix.arch }} + - name: Run examples + run: | + cd examples + make run \ No newline at end of file diff --git a/lib/lwext4_rust/.gitignore b/lib/lwext4_rust/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/lib/lwext4_rust/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/lib/lwext4_rust/.vscode/settings.json b/lib/lwext4_rust/.vscode/settings.json new file mode 100644 index 0000000..bd806e5 --- /dev/null +++ b/lib/lwext4_rust/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rust-analyzer.cargo.extraEnv": { + "CMAKE_POLICY_VERSION_MINIMUM": "3.5" + }, + "cmake.sourceDirectory": "/home/mivik/Projects/lwext4_rust/c/lwext4" +} diff --git a/lib/lwext4_rust/Cargo.toml b/lib/lwext4_rust/Cargo.toml new file mode 100644 index 0000000..5963443 --- /dev/null +++ b/lib/lwext4_rust/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "lwext4_rust" +authors = ["Luoyuan Xiao ", "Mivik "] +version = "0.2.0" +edition = "2024" +license = "GPL-2.0" +repository = "https://github.com/Mivik/lwext4_rust" +description = "lwext4 in Rust" + +links = "lwext4" +build = "build.rs" + +[features] +default = ["print", "std"] +# print = ["printf-compat"] +print = [] +std = [] + +[dependencies] +log = "0.4" +# printf-compat = { git = "https://github.com/lights0123/printf-compat.git", rev = "5f5c9cc", default-features = false, optional = true } + +# See: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html +[build-dependencies] +bindgen = "0.72" diff --git a/lib/lwext4_rust/LICENSE.GPLv2 b/lib/lwext4_rust/LICENSE.GPLv2 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/lib/lwext4_rust/LICENSE.GPLv2 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/lib/lwext4_rust/README.md b/lib/lwext4_rust/README.md new file mode 100644 index 0000000..8f9d043 --- /dev/null +++ b/lib/lwext4_rust/README.md @@ -0,0 +1,66 @@ +# lwext4 in Rust +To provide the rust interface, [lwext4](https://github.com/gkostka/lwext4.git) is abstracted in Rust language. + +_lwext4 is an ext2/ext3/ext4 filesystem library in C for microcontrollers_ + +## Supported features + +* `lwext4_rust` for x86_64, riscv64 and aarch64 on Rust OS is supported +* File system mount and unmount operations +* Filetypes: regular, directories, softlinks +* Journal recovery & transactions +* memory as Block Cache + +## Quick start on Rust OS +![rust on arceos/starry](doc/pic/image.png) + +### Wrap up the file operations +Wrap up the file operation struct `Ext4File`. +It can provide the file system interfaces for the upper layer of Rust OS. + +For the specific code, please refer to [ext4fs.rs](https://github.com/elliott10/arceos/blob/ext4-starry-x86_64/modules/axfs/src/fs/ext4fs.rs) + +`Ext4File` operations include: `file_read`, `file_write`, `file_seek`, `file_open`, `file_close`, `file_rename`, `lwext4_dir_entries` ... + +### Implement interface for disk operations +These operate the physical disk through the interface of disk driver. + +``` rust +impl KernelDevOp for Disk { + type DevType = Disk; + fn read() {} + fn write() {} + fn seek() {} + fn flush() {} +} +``` + +### Create a file system object +New a file system object, initialize and mount the ext4 file system. + +``` rust +let ext4fs = +Ext4BlockWrapper::::new(disk).expect("failed to initialize EXT4 filesystem"); +``` + +### Compile lwext4_rust separately + +``` sh +cargo build -vv --target x86_64-unknown-none | riscv64gc-unknown-none-elf | aarch64-unknown-none-softfloat +``` +OR If you need to compile the lwext4 in C separately, + +please run `make musl-generic -C c/lwext4 ARCH=` + +## Dependencies +* Rust development environment +* C musl-based cross compile toolchains + - [x86_64-linux-musl-gcc](https://musl.cc/x86_64-linux-musl-cross.tgz) + - [riscv64-linux-musl-gcc](https://musl.cc/riscv64-linux-musl-cross.tgz) + - [aarch64-linux-musl-gcc](https://musl.cc/aarch64-linux-musl-cross.tgz) + +## Reference + + +* [lwext4](https://github.com/gkostka/lwext4.git) +* [arceos-lwip](https://github.com/Centaurus99/arceos-lwip.git) diff --git a/lib/lwext4_rust/build.rs b/lib/lwext4_rust/build.rs new file mode 100644 index 0000000..762689b --- /dev/null +++ b/lib/lwext4_rust/build.rs @@ -0,0 +1,106 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() { + let c_path = PathBuf::from("c/lwext4") + .canonicalize() + .expect("cannot canonicalize path"); + + let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let lwext4_lib = &format!("lwext4-{arch}"); + { + let status = Command::new("make") + .args([ + "musl-generic", + "-C", + c_path.to_str().expect("invalid path of lwext4"), + ]) + .arg(format!("ARCH={arch}")) + .arg(format!( + "ULIBC={}", + if cfg!(feature = "std") { "OFF" } else { "ON" } + )) + .status() + .expect("failed to execute process: make lwext4"); + assert!(status.success()); + } + { + let cc = &format!("{arch}-linux-gnu-gcc"); + let output = Command::new(cc) + .args(["-print-sysroot"]) + .output() + .expect("failed to execute process: gcc -print-sysroot"); + + let sysroot = core::str::from_utf8(&output.stdout).unwrap(); + let sysroot = sysroot.trim_end(); + // let sysroot_inc = &format!("-I{sysroot}/usr/include/"); + let sysroot_inc = &format!("-I/opt/riscv64-linux-musl-cross/riscv64-linux-musl/include"); + + generates_bindings_to_rust(sysroot_inc); + } + + println!("cargo:rustc-link-lib=static={lwext4_lib}"); + println!( + "cargo:rustc-link-search=native={}", + c_path.to_str().unwrap() + ); + println!("cargo:rerun-if-changed=c/wrapper.h"); + println!("cargo:rerun-if-changed={}/src", c_path.to_str().unwrap()); +} + +fn generates_bindings_to_rust(mpath: &str) { + let target = env::var("TARGET").unwrap(); + if target.ends_with("-softfloat") { + // Clang does not recognize the `-softfloat` suffix + unsafe { env::set_var("TARGET", target.replace("-softfloat", "")) }; + } + + let bindings = bindgen::Builder::default() + .use_core() + .wrap_unsafe_ops(true) + // The input header we would like to generate bindings for. + .header("c/wrapper.h") + //.clang_arg("--sysroot=/path/to/sysroot") + .clang_arg(mpath) + //.clang_arg("-I../../ulib/axlibc/include") + .clang_arg("-I./c/lwext4/include") + .clang_arg("-I./c/lwext4/build_musl-generic/include/") + .layout_tests(false) + // Tell cargo to invalidate the built crate whenever any of the included header files changed. + .parse_callbacks(Box::new(CustomCargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + .expect("Unable to generate bindings"); + + // Restore the original target environment variable + unsafe { env::set_var("TARGET", target) }; + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +#[derive(Debug)] +struct CustomCargoCallbacks; +impl bindgen::callbacks::ParseCallbacks for CustomCargoCallbacks { + fn header_file(&self, filename: &str) { + add_include(filename); + } + + fn include_file(&self, filename: &str) { + add_include(filename); + } + + fn read_env_var(&self, key: &str) { + println!("cargo:rerun-if-env-changed={key}"); + } +} + +fn add_include(filename: &str) { + if !Path::new(filename).ends_with("ext4_config.h") { + println!("cargo:rerun-if-changed={filename}"); + } +} diff --git a/lib/lwext4_rust/c/lwext4/.clang-format b/lib/lwext4_rust/c/lwext4/.clang-format new file mode 100644 index 0000000..bb6914d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/.clang-format @@ -0,0 +1,8 @@ +#clang-format-3.7 -style=file -i lwext4/* + +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: Always +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false diff --git a/lib/lwext4_rust/c/lwext4/.gitignore b/lib/lwext4_rust/c/lwext4/.gitignore new file mode 100644 index 0000000..50308cf --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/.gitignore @@ -0,0 +1,13 @@ +build_*/ +ext_images/ + +.cproject +.project +.settings/ + +.atom-gdb.json +.build-tools.cson +.gdbinit +tags + +/*.a diff --git a/lib/lwext4_rust/c/lwext4/.travis.yml b/lib/lwext4_rust/c/lwext4/.travis.yml new file mode 100644 index 0000000..4926786 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/.travis.yml @@ -0,0 +1,35 @@ + +language: c +compiler: gcc +sudo: required +dist: trusty + +install: + - uname -a + - sudo rm /var/lib/apt/lists/* -vfr + - sudo apt-get update -qq + - sudo apt-get install -qq cmake + - wget http://www.freddiechopin.info/en/download/category/11-bleeding-edge-toolchain?download=139%3Ableeding-edge-toolchain-151225-64-bit-linux -O /tmp/gcc-arm-none-eabi-5_3-151225-linux-x64.tar.xz + - tar -xf /tmp/gcc-arm-none-eabi-5_3-151225-linux-x64.tar.xz -C /tmp/ + - export SAVED_PATH=$PATH + +script: + - gcc --version + - make generic + - cd build_generic && make -j`nproc` + - cd .. + - export PATH=/tmp/gcc-arm-none-eabi-5_3-151225/bin:$SAVED_PATH + - arm-none-eabi-gcc --version + - make cortex-m4 + - cd build_cortex-m4 && make -j`nproc` + - cd .. + - make cortex-m3 + - cd build_cortex-m3 && make -j`nproc` + - cd .. + - make cortex-m0 + - cd build_cortex-m0 && make -j`nproc` + - cd .. + +notifications: + on_success: change + on_failure: always diff --git a/lib/lwext4_rust/c/lwext4/CHANGELOG b/lib/lwext4_rust/c/lwext4/CHANGELOG new file mode 100644 index 0000000..e4da0c0 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/CHANGELOG @@ -0,0 +1,69 @@ +lwext4-1.0.0 +============ +* new extent module implementation (handle unwritten extents correctly) +* xattr support +* journaling transactions & recover support +* improve configurations (with automatic generated config file) +* move stm32disco demo to separate repository +* test suite & tools improvements (more tests on autogenerated images) +* new filesystem tools: lwext4-mkfs, lwext4-mbr +* travis continious integration +* lot of bugfixes and minor improvements... + + +lwext4-0.8.0 +============ +* improve ext4_dir_entry_next +* clang code format based on config file +* ChibiOS demo for some stm32 boards +* Improve includes in lwext4 dir +* Add some const keyword where should be used + + +lwext4-0.7.0 +============ +* features supported: flex_bg, uninit_bg, dir_nlink +* config file improvements, 3 basic build modes: + * feature set ext2 - small footprint (~20KB .text) + * feature set ext3 - htree directory indexing (~25KB .text) + * feature set ext4 - all supported features enabled (~30KB .text) +* IO timing stats in stm32f429_demo +* more advanced cases in test suite +* support for meta_bg feature (unstable) +* crc32c module for meta_csum feature (not supported yet) +* small demo application improvments (readability) + + +lwext4-0.6.0 +============ +* Fixed stm32429demo enumerating issues +* Comb sort for directory indexing +* Cmake toolchain files for msp430 + +lwext4-0.5.0 +============ +* Build system refactoring +* Pedantic warning check for lwext4 files +* New toolchain files for cortex-m0, avexmega7, arm-sim +* Merge bugfixes from HelenOS mainline +* OS locks setup function + +lwext4-0.4.0 +============ +* Client-server based automatic test suite + +lwext4-0.3.0 +============ +* STM32F429 demo + +lwext4-0.2.0 +============ +* Full extents support +* Doxygen comments +* Bug fixing +* Demo app improvments + + +lwext4-0.1.1 +============ +* First version of the lwext4 filesystem library. diff --git a/lib/lwext4_rust/c/lwext4/CMakeLists.txt b/lib/lwext4_rust/c/lwext4/CMakeLists.txt new file mode 100644 index 0000000..15d4e69 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/CMakeLists.txt @@ -0,0 +1,107 @@ +cmake_minimum_required(VERSION 3.5) +project(lwext4 C) + + +include_directories(include) +include_directories(${PROJECT_BINARY_DIR}/include) +include_directories(blockdev/filedev) +include_directories(blockdev/filedev_win) + +set(BLOCKDEV_TYPE none) + +add_definitions(-DCONFIG_USE_DEFAULT_CONFIG=0) +add_definitions(-DVERSION="${VERSION}") + +#Examples +if (CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m0) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m3) + add_definitions(-DCONFIG_UNALIGNED_ACCESS=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL arm-sim) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL cortex-m4) + add_definitions(-DCONFIG_UNALIGNED_ACCESS=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL bf518) + #... +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL avrxmega7) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=1) +elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL msp430g2210) + add_definitions(-DCONFIG_DEBUG_PRINTF=0) + add_definitions(-DCONFIG_DEBUG_ASSERT=0) + #... +elseif(LIB_ONLY) + add_definitions(-DCONFIG_DEBUG_PRINTF=0) + add_definitions(-DCONFIG_DEBUG_ASSERT=0) + add_definitions(-DCONFIG_HAVE_OWN_OFLAGS=1) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=0) + add_definitions(-DCONFIG_USE_USER_MALLOC=0) + add_definitions(-DCONFIG_BLOCK_DEV_CACHE_SIZE=16) +else() + #Generic example target + if (WIN32) + set(BLOCKDEV_TYPE windows) + else() + set(BLOCKDEV_TYPE linux) + endif() + set (INSTALL_LIB 1) + + add_definitions(-DCONFIG_DEBUG_PRINTF=1) + add_definitions(-DCONFIG_DEBUG_ASSERT=1) + + add_definitions(-DCONFIG_HAVE_OWN_OFLAGS=1) + add_definitions(-DCONFIG_HAVE_OWN_ERRNO=1) + add_definitions(-DCONFIG_HAVE_OWN_ASSERT=1) + add_definitions(-DCONFIG_USE_USER_MALLOC=1) + add_definitions(-DCONFIG_BLOCK_DEV_CACHE_SIZE=16) + + add_subdirectory(fs_test) +endif() + +macro(output_configure) + get_property( + definitions + DIRECTORY + PROPERTY COMPILE_DEFINITIONS + ) + file(WRITE + ${PROJECT_BINARY_DIR}/include/generated/ext4_config.h + "") + foreach(item ${definitions}) + string(REGEX MATCH "^CONFIG_" match_res ${item}) + if(match_res) + string(REGEX REPLACE "=(.+)$" "" replace_res ${item}) + string(CONFIGURE + "#define ${replace_res} ${CMAKE_MATCH_1}" + output_str) + file(APPEND + ${PROJECT_BINARY_DIR}/include/generated/ext4_config.h + "${output_str}\n") + endif() + endforeach() +endmacro() +output_configure() + +add_subdirectory(blockdev) + +#Library build +add_subdirectory(src) +#Detect all possible warnings for lwext4 target +if (NOT CMAKE_COMPILER_IS_GNUCC) + set_target_properties(lwext4 PROPERTIES COMPILE_FLAGS "-fno-builtin-memcpy") +else() + set_target_properties(lwext4 PROPERTIES COMPILE_FLAGS "-Wall -Wextra -pedantic") +endif() + +#DISTRIBUTION +set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}") +set(CPACK_SOURCE_GENERATOR "TBZ2") +set(CPACK_SOURCE_PACKAGE_FILE_NAME + "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set(CPACK_SOURCE_IGNORE_FILES +"/build" ".git") +include(CPack) + + +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) diff --git a/lib/lwext4_rust/c/lwext4/LICENSE b/lib/lwext4_rust/c/lwext4/LICENSE new file mode 100644 index 0000000..ca992f8 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/LICENSE @@ -0,0 +1,345 @@ + +Some files in lwext4 contain a different license statement. Those +files are licensed under the license contained in the file itself. + +----------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/lib/lwext4_rust/c/lwext4/Makefile b/lib/lwext4_rust/c/lwext4/Makefile new file mode 100644 index 0000000..8d2411f --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/Makefile @@ -0,0 +1,89 @@ + +#Release +#Debug +BUILD_TYPE = Release + +ifneq ($(shell test -d .git), 0) +GIT_SHORT_HASH:= $(shell git rev-parse --short HEAD) +endif + +VERSION_MAJOR = 1 +VERSION_MINOR = 0 +VERSION_PATCH = 0 + +VERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)-$(GIT_SHORT_HASH) + +COMMON_DEFINITIONS = \ + -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ + -DVERSION_MAJOR=$(VERSION_MAJOR) \ + -DVERSION_MINOR=$(VERSION_MINOR) \ + -DVERSION_PATCH=$(VERSION_PATCH) \ + -DVERSION=$(VERSION) \ + -DLWEXT4_BUILD_SHARED_LIB=OFF \ + -DLWEXT4_ULIBC=$(ULIBC) \ + -DCMAKE_C_COMPILER_WORKS=1 \ + -DCMAKE_INSTALL_PREFIX=./install \ + -DLIB_ONLY=TRUE \ + +define generate_common + rm -R -f build_$(1) + mkdir build_$(1) + cd build_$(1) && cmake -G"Unix Makefiles" -DCMAKE_VERBOSE_MAKEFILE=ON \ + $(COMMON_DEFINITIONS) \ + $(2) \ + -DCMAKE_TOOLCHAIN_FILE=../toolchain/$(1).cmake .. +endef + +ARCH ?= x86_64 +#Output: src/liblwext4.a +musl-generic: + $(call generate_common,$@) + cd build_$@ && make lwext4 + cp build_$@/src/liblwext4.a ./liblwext4-$(ARCH).a + +generic: + $(call generate_common,$@) + +cortex-m0: + $(call generate_common,$@) + +cortex-m0+: + $(call generate_common,$@) + +cortex-m3: + $(call generate_common,$@) + +cortex-m4: + $(call generate_common,$@) + +cortex-m4f: + $(call generate_common,$@) + +cortex-m7: + $(call generate_common,$@) + +arm-sim: + $(call generate_common,$@) + +avrxmega7: + $(call generate_common,$@) + +msp430: + $(call generate_common,$@) + +mingw: + $(call generate_common,$@,-DWIN32=1) + +lib_only: + rm -R -f build_lib_only + mkdir build_lib_only + cd build_lib_only && cmake $(COMMON_DEFINITIONS) -DLIB_ONLY=TRUE .. + +all: + generic + +clean: + rm -R -f build_* + rm -R -f ext_images + +include fs_test.mk diff --git a/lib/lwext4_rust/c/lwext4/README.md b/lib/lwext4_rust/c/lwext4/README.md new file mode 100644 index 0000000..3abd860 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/README.md @@ -0,0 +1,244 @@ +[![Join the chat at https://gitter.im/gkostka/lwext4](https://badges.gitter.im/gkostka/lwext4.svg)](https://gitter.im/gkostka/lwext4?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![License (GPL v2.0)](https://img.shields.io/badge/license-GPL%20(v2.0)-blue.svg?style=flat-square)](http://opensource.org/licenses/GPL-2.0) +[![Build Status](https://travis-ci.org/gkostka/lwext4.svg)](https://travis-ci.org/gkostka/lwext4) +[![](http://img.shields.io/gratipay/user/gkostka.svg)](https://gratipay.com/gkostka/) + +![lwext4](https://cloud.githubusercontent.com/assets/8606098/11697327/68306d88-9eb9-11e5-8807-81a2887f077e.png) + +About +===== + + +The main goal of the lwext4 project is to provide ext2/3/4 filesystem for microcontrollers. It may be an interesting alternative for traditional MCU filesystem libraries (mostly based on FAT32). Library has some cool and unique features in microcontrollers world: + - directory indexing - fast file find and list operations + - extents - fast big file truncate + - journaling transactions & recovery - power loss resistance + +Lwext4 is an excellent choice for SD/MMC card, USB flash drive or any other wear +leveled memory types. However it is not good for raw flash devices. + +Feel free to contact me: +kostka.grzegorz@gmail.com + +Credits +===== + +The most of the source code of lwext4 was taken from HelenOS: +* http://helenos.org/ + +Some features are based on FreeBSD and Linux implementations. + +KaHo Ng (https://github.com/ngkaho1234): +* advanced extents implementation +* xattr support +* metadata checksum support +* journal recovery & transactions +* many bugfixes & improvements + +Lwext4 could be used also as fuse internals. Here is a nice project which uses lwext4 as a filesystem base: +* https://github.com/ngkaho1234/fuse-lwext4 + +Some of the source files are licensed under GPLv2. It makes whole +lwext4 GPLv2 licensed. To use library as a BSD3, GPLv2 licensed source +files must be removed first. At this point there are two files +licensed under GPLv2: +* ext4_xattr.c +* ext4_extents.c + +All other modules and headers are BSD-3-Clause licensed code. + + +Features +===== + +* filetypes: regular, directories, softlinks +* support for hardlinks +* multiple blocksize supported: 1KB, 2KB, 4KB ... 64KB +* little/big endian architectures supported +* multiple configurations (ext2/ext3/ext4) +* only C standard library dependency +* various CPU architectures supported (x86/64, cortex-mX, msp430 ...) +* small memory footprint +* flexible configurations + +Memory footprint +------------ + +Advanced ext4 filesystem features, like extents or journaling require some memory. +However most of the memory expensive features could be disabled at compile time. +Here is a brief summary for cortex-m4 processor: + +* .text: 20KB - only ext2 fs support , 50KB - full ext4 fs feature set +* .data: 8KB - minimum 8 x 1KB block cache, 12KB - when journaling and extents are enabled +* .stack: 2KB - is enough (not measured precisely) + +Blocks are allocated dynamically. Previous versions of library could work without +malloc but from 1.0.0 dynamic memory allocation is required. However, block cache +should not allocate more than CONFIG_BLOCK_DEV CACHE_SIZE. + +Supported ext2/3/4 features +===== +incompatible: +------------ +* filetype, recover, meta_bg, extents, 64bit, flex_bg: **yes** +* compression, journal_dev, mmp, ea_inode, dirdata, bg_meta_csum, largedir, inline_data: **no** + +compatible: +------------ +* has_journal, ext_attr, dir_index: **yes** +* dir_prealloc, imagic_inodes, resize_inode: **no** + +read-only: +------------ +* sparse_super, large_file, huge_file, gdt_csum, dir_nlink, extra_isize, metadata_csum: **yes** +* quota, bigalloc, btree_dir: **no** + +Project tree +===== +* blockdev - block devices set, supported blockdev +* fs_test - test suite, mkfs and demo application +* src - source files +* include - header files +* toolchain - cmake toolchain files +* CMakeLists.txt - CMake config file +* ext_images.7z - compressed ext2/3/4 100MB images +* fs_test.mk - automatic tests definitions +* Makefile - helper makefile to generate cmake and run test suite +* README.md - readme file + +Compile +===== +Dependencies +------------ +* Windows + +Download MSYS-2: https://sourceforge.net/projects/msys2/ + +Install required packages is MSYS2 Shell package manager: +```bash + pacman -S make gcc cmake p7zip + ``` + +* Linux + +Package installation (Debian): +```bash + apt-get install make gcc cmake p7zip + ``` + +Compile & install tools +------------ +```bash + make generic + cd build_generic + make + sudo make install + ``` + +lwext4-generic demo application +===== +Simple lwext4 library test application: +* load ext2/3/4 images +* load linux block device with ext2/3/4 part +* load windows volume with ext2/3/4 filesystem +* directory speed test +* file write/read speed test + +How to use for images/blockdevices: +```bash + lwext4-generic -i ext_images/ext2 + lwext4-generic -i ext_images/ext3 + lwext4-generic -i ext_images/ext4 + ``` + +Show full option set: +```bash + lwext4-generic --help + ``` + +Run automatic tests +===== + +Execute tests for 100MB unpacked images: +```bash + make test + ``` +Execute tests for autogenerated 1GB images (only on Linux targets) + fsck: +```bash + make test_all + ``` +Using lwext4-mkfs tool +===== +It is possible to create ext2/3/4 partition by internal library tool. + +Generate empty file (1GB): +```bash + dd if=/dev/zero of=ext_image bs=1M count=1024 + ``` +Create ext2 partition: +```bash + lwext4-mkfs -i ext_image -e 2 + ``` +Create ext3 partition: +```bash + lwext4-mkfs -i ext_image -e 3 + ``` +Create ext4 partition: +```bash + lwext4-mkfs -i ext_image -e 4 + ``` +Show full option set: +```bash + lwext4-mkfs --help + ``` + +Cross compile standalone library +===== +Toolchains needed: +------------ + +Lwext4 could be compiled for many targets. Here are an examples for 8/16/32/64 bit architectures. +* generic for x86 or amd64 +* arm-none-eabi-gcc for ARM cortex-m0/m3/m4 microcontrollers +* avr-gcc for AVR xmega microcontrollers +* bfin-elf-gcc for blackfin processors +* msp430-gcc for msp430 microcontrollers + +Library has been tested only for generic (amd64) & ARM Cortex M architectures. +For other targets compilation passes (with warnings somewhere) but tests are +not done yet. Lwext4 code is written with endianes respect. Big endian +behavior also hasn't been tested yet. + +Build avrxmega7 library: +------------ +```bash + make avrxmega7 + cd build_avrxmega7 + make lwext4 + ``` + +Build cortex-m0 library: +------------ +```bash + make cortex-m0 + cd build_cortex-m0 + make lwext4 + ``` + +Build cortex-m3 library: +------------ +```bash + make cortex-m3 + cd build_cortex-m3 + make lwext4 + ``` + +Build cortex-m4 library: +------------ +```bash + make cortex-m4 + cd build_cortex-m4 + make lwext4 +``` + + diff --git a/lib/lwext4_rust/c/lwext4/_config.yml b/lib/lwext4_rust/c/lwext4/_config.yml new file mode 100644 index 0000000..fc24e7a --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-hacker \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/blockdev/CMakeLists.txt b/lib/lwext4_rust/c/lwext4/blockdev/CMakeLists.txt new file mode 100644 index 0000000..a16e810 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/CMakeLists.txt @@ -0,0 +1,13 @@ +#Blockdev library + +if (WIN32) + aux_source_directory(linux BLOCKDEV_SRC) + aux_source_directory(windows BLOCKDEV_SRC) +elseif (BLOCKDEV_TYPE STREQUAL linux) + aux_source_directory(linux BLOCKDEV_SRC) +else() +endif() + +aux_source_directory(. BLOCKDEV_SRC) +add_library(blockdev ${BLOCKDEV_SRC}) + diff --git a/lib/lwext4_rust/c/lwext4/blockdev/blockdev.c b/lib/lwext4_rust/c/lwext4/blockdev/blockdev.c new file mode 100644 index 0000000..a44a2e1 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/blockdev.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include + + +/**********************BLOCKDEV INTERFACE**************************************/ +static int blockdev_open(struct ext4_blockdev *bdev); +static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int blockdev_close(struct ext4_blockdev *bdev); +static int blockdev_lock(struct ext4_blockdev *bdev); +static int blockdev_unlock(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(blockdev, 512, 0, blockdev_open, + blockdev_bread, blockdev_bwrite, blockdev_close, + blockdev_lock, blockdev_unlock); + +/******************************************************************************/ +static int blockdev_open(struct ext4_blockdev *bdev) +{ + /*blockdev_open: skeleton*/ + return EIO; +} + +/******************************************************************************/ + +static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + /*blockdev_bread: skeleton*/ + return EIO; +} + + +/******************************************************************************/ +static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + /*blockdev_bwrite: skeleton*/ + return EIO; +} +/******************************************************************************/ +static int blockdev_close(struct ext4_blockdev *bdev) +{ + /*blockdev_close: skeleton*/ + return EIO; +} + +static int blockdev_lock(struct ext4_blockdev *bdev) +{ + /*blockdev_lock: skeleton*/ + return EIO; +} + +static int blockdev_unlock(struct ext4_blockdev *bdev) +{ + /*blockdev_unlock: skeleton*/ + return EIO; +} + +/******************************************************************************/ +struct ext4_blockdev *ext4_blockdev_get(void) +{ + return &blockdev; +} +/******************************************************************************/ + diff --git a/lib/lwext4_rust/c/lwext4/blockdev/blockdev.h b/lib/lwext4_rust/c/lwext4/blockdev/blockdev.h new file mode 100644 index 0000000..17f947a --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/blockdev.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BLOCKDEV_H_ +#define BLOCKDEV_H_ + +#include +#include + +#include +#include + +/**@brief File blockdev get.*/ +struct ext4_blockdev *ext4_blockdev_get(void); + +#endif /* BLOCKDEV_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.c b/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.c new file mode 100644 index 0000000..dd0743b --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _LARGEFILE64_SOURCE +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include + +/**@brief Default filename.*/ +static const char *fname = "ext2"; + +/**@brief Image block size.*/ +#define EXT4_FILEDEV_BSIZE 512 + +/**@brief Image file descriptor.*/ +static FILE *dev_file; + +#define DROP_LINUXCACHE_BUFFERS 0 + +/**********************BLOCKDEV INTERFACE**************************************/ +static int file_dev_open(struct ext4_blockdev *bdev); +static int file_dev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int file_dev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int file_dev_close(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(file_dev, EXT4_FILEDEV_BSIZE, 0, file_dev_open, + file_dev_bread, file_dev_bwrite, file_dev_close, 0, 0); + +/******************************************************************************/ +static int file_dev_open(struct ext4_blockdev *bdev) +{ + dev_file = fopen(fname, "r+b"); + + if (!dev_file) + return EIO; + + /*No buffering at file.*/ + setbuf(dev_file, 0); + + if (fseeko(dev_file, 0, SEEK_END)) + return EFAULT; + + file_dev.part_offset = 0; + file_dev.part_size = ftello(dev_file); + file_dev.bdif->ph_bcnt = file_dev.part_size / file_dev.bdif->ph_bsize; + + return EOK; +} + +/******************************************************************************/ + +static int file_dev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + if (fseeko(dev_file, blk_id * bdev->bdif->ph_bsize, SEEK_SET)) + return EIO; + if (!blk_cnt) + return EOK; + if (!fread(buf, bdev->bdif->ph_bsize * blk_cnt, 1, dev_file)) + return EIO; + + return EOK; +} + +static void drop_cache(void) +{ +#if defined(__linux__) && DROP_LINUXCACHE_BUFFERS + int fd; + char *data = "3"; + + sync(); + fd = open("/proc/sys/vm/drop_caches", O_WRONLY); + write(fd, data, sizeof(char)); + close(fd); +#endif +} + +/******************************************************************************/ +static int file_dev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + if (fseeko(dev_file, blk_id * bdev->bdif->ph_bsize, SEEK_SET)) + return EIO; + if (!blk_cnt) + return EOK; + if (!fwrite(buf, bdev->bdif->ph_bsize * blk_cnt, 1, dev_file)) + return EIO; + + drop_cache(); + return EOK; +} +/******************************************************************************/ +static int file_dev_close(struct ext4_blockdev *bdev) +{ + fclose(dev_file); + return EOK; +} + +/******************************************************************************/ +struct ext4_blockdev *file_dev_get(void) +{ + return &file_dev; +} +/******************************************************************************/ +void file_dev_name_set(const char *n) +{ + fname = n; +} +/******************************************************************************/ diff --git a/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.h b/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.h new file mode 100644 index 0000000..ce4690d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/linux/file_dev.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef FILE_DEV_H_ +#define FILE_DEV_H_ + +#include +#include + +#include +#include + +/**@brief File blockdev get.*/ +struct ext4_blockdev *file_dev_get(void); + +/**@brief Set filename to open.*/ +void file_dev_name_set(const char *n); + +#endif /* FILE_DEV_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.c b/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.c new file mode 100644 index 0000000..9142132 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include + +/**@brief Default filename.*/ +static const char *fname = "ext2"; + +/**@brief IO block size.*/ +#define EXT4_IORAW_BSIZE 512 + +/**@brief Image file descriptor.*/ +static HANDLE dev_file; + +/**********************BLOCKDEV INTERFACE**************************************/ +static int file_open(struct ext4_blockdev *bdev); +static int file_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); +static int file_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); +static int file_close(struct ext4_blockdev *bdev); + +/******************************************************************************/ +EXT4_BLOCKDEV_STATIC_INSTANCE(_filedev, EXT4_IORAW_BSIZE, 0, file_open, + file_bread, file_bwrite, file_close, 0, 0); + +/******************************************************************************/ +static int file_open(struct ext4_blockdev *bdev) +{ + char path[64]; + DISK_GEOMETRY pdg; + uint64_t disk_size; + BOOL bResult = FALSE; + DWORD junk; + + sprintf(path, "\\\\.\\%s", fname); + + dev_file = + CreateFile(path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, NULL); + + if (dev_file == INVALID_HANDLE_VALUE) { + return EIO; + } + + bResult = + DeviceIoControl(dev_file, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &pdg, sizeof(pdg), &junk, (LPOVERLAPPED)NULL); + + if (bResult == FALSE) { + CloseHandle(dev_file); + return EIO; + } + + disk_size = pdg.Cylinders.QuadPart * (ULONG)pdg.TracksPerCylinder * + (ULONG)pdg.SectorsPerTrack * (ULONG)pdg.BytesPerSector; + + _filedev.bdif->ph_bsize = pdg.BytesPerSector; + _filedev.bdif->ph_bcnt = disk_size / pdg.BytesPerSector; + + _filedev.part_offset = 0; + _filedev.part_size = disk_size; + + return EOK; +} + +/******************************************************************************/ + +static int file_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + long hipart = blk_id >> (32 - 9); + long lopart = blk_id << 9; + long err; + + SetLastError(0); + lopart = SetFilePointer(dev_file, lopart, &hipart, FILE_BEGIN); + + if (lopart == -1 && NO_ERROR != (err = GetLastError())) { + return EIO; + } + + DWORD n; + + if (!ReadFile(dev_file, buf, blk_cnt * 512, &n, NULL)) { + err = GetLastError(); + return EIO; + } + return EOK; +} + +/******************************************************************************/ +static int file_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + long hipart = blk_id >> (32 - 9); + long lopart = blk_id << 9; + long err; + + SetLastError(0); + lopart = SetFilePointer(dev_file, lopart, &hipart, FILE_BEGIN); + + if (lopart == -1 && NO_ERROR != (err = GetLastError())) { + return EIO; + } + + DWORD n; + + if (!WriteFile(dev_file, buf, blk_cnt * 512, &n, NULL)) { + err = GetLastError(); + return EIO; + } + return EOK; +} + +/******************************************************************************/ +static int file_close(struct ext4_blockdev *bdev) +{ + CloseHandle(dev_file); + return EOK; +} + +/******************************************************************************/ +struct ext4_blockdev *file_windows_dev_get(void) +{ + return &_filedev; +} +/******************************************************************************/ +void file_windows_name_set(const char *n) +{ + fname = n; +} + +/******************************************************************************/ +#endif diff --git a/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.h b/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.h new file mode 100644 index 0000000..013d1f3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/blockdev/windows/file_windows.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef FILE_WINDOWS_H_ +#define FILE_WINDOWS_H_ + +#include +#include + +#include +#include + +/**@brief IO raw blockdev get.*/ +struct ext4_blockdev *file_windows_dev_get(void); + +/**@brief Set filrname to open.*/ +void file_windows_name_set(const char *n); + + +#endif /* FILE_WINDOWS_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/fs_test.mk b/lib/lwext4_rust/c/lwext4/fs_test.mk new file mode 100644 index 0000000..67f8e1d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test.mk @@ -0,0 +1,664 @@ + +ifeq ($(OS),Windows_NT) +LWEXT4_CLIENT = @build_generic\\fs_test\\lwext4-client +LWEXT4_SERVER = @build_generic\\fs_test\\lwext4-server +else +LWEXT4_CLIENT = @build_generic/fs_test/lwext4-client +LWEXT4_SERVER = @build_generic/fs_test/lwext4-server +endif + +TEST_DIR = /test + +t0: + @echo "T0: Device register test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + +t1: + @echo "T1: Single mount-umount test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + +t2: + @echo "T2: Multiple mount-umount test:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "umount /" + +t3: + @echo "T3: Test dir create/remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 0" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t4: + @echo "T4: 10 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 10" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t5: + @echo "T5: 100 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 100" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 100" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t6: + @echo "T6: 1000 files create + write + read + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 1000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_fremove $(TEST_DIR) /f 1000" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t7: + @echo "T7: 10 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 10" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t8: + @echo "T8: 100 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 100" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 100" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t9: + @echo "T9: 1000 dirs create + remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 1000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + $(LWEXT4_CLIENT) -c "multi_dremove $(TEST_DIR) /d 1000" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t10: + @echo "T10: 10 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t11: + @echo "T11: 100 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 100" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t12: + @echo "T12: 1000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 1000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t13: + @echo "T13: 10 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 10" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t14: + @echo "T14: 100 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 100" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 100 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 100" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t15: + @echo "T15: 1000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 1000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 1000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_entry_get 0 1000" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +t16: + @echo "T16: 8kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 8192 0" + + $(LWEXT4_CLIENT) -c "ftell 0 8192" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fread 0 0 8192 0" + + $(LWEXT4_CLIENT) -c "ftell 0 8192" + $(LWEXT4_CLIENT) -c "fsize 0 8192" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t17: + @echo "T17: 64kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 65536 0" + + $(LWEXT4_CLIENT) -c "ftell 0 65536" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fread 0 0 65536 0" + + $(LWEXT4_CLIENT) -c "ftell 0 65536" + $(LWEXT4_CLIENT) -c "fsize 0 65536" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t18: + @echo "T18: 512kB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 524288 0" + + $(LWEXT4_CLIENT) -c "ftell 0 524288" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fread 0 0 524288 0" + + $(LWEXT4_CLIENT) -c "ftell 0 524288" + $(LWEXT4_CLIENT) -c "fsize 0 524288" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t19: + @echo "T19: 4MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 4194304 0" + + $(LWEXT4_CLIENT) -c "ftell 0 4194304" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fread 0 0 4194304 0" + + $(LWEXT4_CLIENT) -c "ftell 0 4194304" + $(LWEXT4_CLIENT) -c "fsize 0 4194304" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t20: + @echo "T20: 32MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 33554432 0" + + $(LWEXT4_CLIENT) -c "ftell 0 33554432" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fread 0 0 33554432 0" + + $(LWEXT4_CLIENT) -c "ftell 0 33554432" + $(LWEXT4_CLIENT) -c "fsize 0 33554432" + + $(LWEXT4_CLIENT) -c "fclose 0" + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t21: + @echo "T21: 128MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 134217728 0" + + $(LWEXT4_CLIENT) -c "ftell 0 134217728" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fread 0 0 134217728 0" + + $(LWEXT4_CLIENT) -c "ftell 0 134217728" + $(LWEXT4_CLIENT) -c "fsize 0 134217728" + + $(LWEXT4_CLIENT) -c "fclose 0" + + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t22: + @echo "T22: 512MB file write/read:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "fopen 0 $(TEST_DIR)/test.txt wb+" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 0" + + $(LWEXT4_CLIENT) -c "fwrite 0 0 536870912 0" + + $(LWEXT4_CLIENT) -c "ftell 0 536870912" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fseek 0 0 0" + + $(LWEXT4_CLIENT) -c "ftell 0 0" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fread 0 0 536870912 0" + + $(LWEXT4_CLIENT) -c "ftell 0 536870912" + $(LWEXT4_CLIENT) -c "fsize 0 536870912" + + $(LWEXT4_CLIENT) -c "fclose 0" + + $(LWEXT4_CLIENT) -c "fremove $(TEST_DIR)/test.txt" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t23: + @echo "T23: 10000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 10000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 10000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 10000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t24: + @echo "T24: 50000 entries (files) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_fcreate $(TEST_DIR) /f 50000" + $(LWEXT4_CLIENT) -c "multi_fwrite $(TEST_DIR) /f 50000 1024" + $(LWEXT4_CLIENT) -c "multi_fread $(TEST_DIR) /f 50000 1024" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +t25: + @echo "T25: 10000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 10000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + +t26: + @echo "T26: 50000 entries (dir) dir recursive remove:" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "stats_save /" + $(LWEXT4_CLIENT) -c "dir_mk $(TEST_DIR)" + + $(LWEXT4_CLIENT) -c "multi_dcreate $(TEST_DIR) /d 50000" + $(LWEXT4_CLIENT) -c "dir_open 0 $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "dir_close 0" + + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "stats_check /" + $(LWEXT4_CLIENT) -c "umount /" + + +ct: + @echo "Clean test directory" + $(LWEXT4_CLIENT) -c "device_register 0 0 bdev" + $(LWEXT4_CLIENT) -c "mount bdev /" + $(LWEXT4_CLIENT) -c "dir_rm $(TEST_DIR)" + $(LWEXT4_CLIENT) -c "umount /" + +server_ext2: + $(LWEXT4_SERVER) -i ext_images/ext2 + +server_ext3: + $(LWEXT4_SERVER) -i ext_images/ext3 + +server_ext4: + $(LWEXT4_SERVER) -i ext_images/ext4 + +server_kill: + -killall lwext4-server + +fsck_images: + sudo fsck.ext2 ext_images/ext2 -v -f + sudo fsck.ext3 ext_images/ext3 -v -f + sudo fsck.ext4 ext_images/ext4 -v -f + +images_small: + rm -rf ext_images + mkdir ext_images + dd if=/dev/zero of=ext_images/ext2 bs=1M count=128 + dd if=/dev/zero of=ext_images/ext3 bs=1M count=128 + dd if=/dev/zero of=ext_images/ext4 bs=1M count=128 + sudo mkfs.ext2 ext_images/ext2 + sudo mkfs.ext3 ext_images/ext3 + sudo mkfs.ext4 -O ^metadata_csum ext_images/ext4 + +images_big: + rm -rf ext_images + mkdir ext_images + dd if=/dev/zero of=ext_images/ext2 bs=1M count=1024 + dd if=/dev/zero of=ext_images/ext3 bs=1M count=1024 + dd if=/dev/zero of=ext_images/ext4 bs=1M count=1024 + sudo mkfs.ext2 ext_images/ext2 + sudo mkfs.ext3 ext_images/ext3 + sudo mkfs.ext4 -O ^metadata_csum ext_images/ext4 + +test_set_small: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 +test_set_full: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 t21 t22 t23 t24 t25 t26 + +test_ext2_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext2 & + sleep 1 + make test_set_full + make server_kill + + +test_ext3_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext3 & + sleep 1 + make test_set_full + make server_kill + +test_ext4_full: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext4 & + sleep 1 + make test_set_full + make server_kill + +test_all: images_big test_ext2_full test_ext3_full test_ext4_full fsck_images + + +test_ext2_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext2 & + sleep 1 + make test_set_small + make server_kill + + +test_ext3_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext3 & + sleep 1 + make test_set_small + make server_kill + +test_ext4_small: + make server_kill + $(LWEXT4_SERVER) -i ext_images/ext4 & + sleep 1 + make test_set_small + make server_kill + +test: images_small test_ext2_small test_ext3_small test_ext4_small + + + + + + + diff --git a/lib/lwext4_rust/c/lwext4/fs_test/CMakeLists.txt b/lib/lwext4_rust/c/lwext4/fs_test/CMakeLists.txt new file mode 100644 index 0000000..16befb3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/CMakeLists.txt @@ -0,0 +1,32 @@ +#fs_test executables +add_executable(lwext4-server lwext4_server.c) +target_link_libraries(lwext4-server lwext4) +target_link_libraries(lwext4-server blockdev) +if(WIN32) +target_link_libraries(lwext4-server ws2_32) +endif(WIN32) +add_executable(lwext4-client lwext4_client.c) +target_link_libraries(lwext4-client lwext4) +if(WIN32) +target_link_libraries(lwext4-client ws2_32) +endif(WIN32) + +aux_source_directory(common COMMON_SRC) +add_executable(lwext4-generic lwext4_generic.c ${COMMON_SRC}) +target_link_libraries(lwext4-generic blockdev) +target_link_libraries(lwext4-generic lwext4) + +add_executable(lwext4-mkfs lwext4_mkfs.c) +target_link_libraries(lwext4-mkfs blockdev) +target_link_libraries(lwext4-mkfs lwext4) + +add_executable(lwext4-mbr lwext4_mbr.c) +target_link_libraries(lwext4-mbr blockdev) +target_link_libraries(lwext4-mbr lwext4) + +install (TARGETS lwext4-server DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-client DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-generic DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-mkfs DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install (TARGETS lwext4-mbr DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + diff --git a/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.c b/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.c new file mode 100644 index 0000000..78ec626 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "../common/test_lwext4.h" + +#include + +#include +#include +#include + + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Block cache handle.*/ +static struct ext4_bcache *bc; + +static char *entry_to_str(uint8_t type) +{ + switch (type) { + case EXT4_DE_UNKNOWN: + return "[unk] "; + case EXT4_DE_REG_FILE: + return "[fil] "; + case EXT4_DE_DIR: + return "[dir] "; + case EXT4_DE_CHRDEV: + return "[cha] "; + case EXT4_DE_BLKDEV: + return "[blk] "; + case EXT4_DE_FIFO: + return "[fif] "; + case EXT4_DE_SOCK: + return "[soc] "; + case EXT4_DE_SYMLINK: + return "[sym] "; + default: + break; + } + return "[???]"; +} + +static long int get_ms(void) { return tim_get_ms(); } + +static void printf_io_timings(long int diff) +{ + const struct ext4_io_stats *stats = io_timings_get(diff); + if (!stats) + return; + + printf("io_timings:\n"); + printf(" io_read: %.3f%%\n", (double)stats->io_read); + printf(" io_write: %.3f%%\n", (double)stats->io_write); + printf(" io_cpu: %.3f%%\n", (double)stats->cpu); +} + +void test_lwext4_dir_ls(const char *path) +{ + char sss[255]; + ext4_dir d; + const ext4_direntry *de; + + printf("ls %s\n", path); + + ext4_dir_open(&d, path); + de = ext4_dir_entry_next(&d); + + while (de) { + memcpy(sss, de->name, de->name_length); + sss[de->name_length] = 0; + printf(" %s%s\n", entry_to_str(de->inode_type), sss); + de = ext4_dir_entry_next(&d); + } + ext4_dir_close(&d); +} + +void test_lwext4_mp_stats(void) +{ + struct ext4_mount_stats stats; + ext4_mount_point_stats("/mp/", &stats); + + printf("********************\n"); + printf("ext4_mount_point_stats\n"); + printf("inodes_count = %" PRIu32 "\n", stats.inodes_count); + printf("free_inodes_count = %" PRIu32 "\n", stats.free_inodes_count); + printf("blocks_count = %" PRIu32 "\n", (uint32_t)stats.blocks_count); + printf("free_blocks_count = %" PRIu32 "\n", + (uint32_t)stats.free_blocks_count); + printf("block_size = %" PRIu32 "\n", stats.block_size); + printf("block_group_count = %" PRIu32 "\n", stats.block_group_count); + printf("blocks_per_group= %" PRIu32 "\n", stats.blocks_per_group); + printf("inodes_per_group = %" PRIu32 "\n", stats.inodes_per_group); + printf("volume_name = %s\n", stats.volume_name); + printf("********************\n"); +} + +void test_lwext4_block_stats(void) +{ + if (!bd) + return; + + printf("********************\n"); + printf("ext4 blockdev stats\n"); + printf("bdev->bread_ctr = %" PRIu32 "\n", bd->bdif->bread_ctr); + printf("bdev->bwrite_ctr = %" PRIu32 "\n", bd->bdif->bwrite_ctr); + + printf("bcache->ref_blocks = %" PRIu32 "\n", bd->bc->ref_blocks); + printf("bcache->max_ref_blocks = %" PRIu32 "\n", bd->bc->max_ref_blocks); + printf("bcache->lru_ctr = %" PRIu32 "\n", bd->bc->lru_ctr); + + printf("\n"); + + printf("********************\n"); +} + +bool test_lwext4_dir_test(int len) +{ + ext4_file f; + int r; + int i; + char path[64]; + long int diff; + long int stop; + long int start; + + printf("test_lwext4_dir_test: %d\n", len); + io_timings_clear(); + start = get_ms(); + + printf("directory create: /mp/dir1\n"); + r = ext4_dir_mk("/mp/dir1"); + if (r != EOK) { + printf("ext4_dir_mk: rc = %d\n", r); + return false; + } + + printf("add files to: /mp/dir1\n"); + for (i = 0; i < len; ++i) { + sprintf(path, "/mp/dir1/f%d", i); + r = ext4_fopen(&f, path, "wb"); + if (r != EOK) { + printf("ext4_fopen: rc = %d\n", r); + return false; + } + } + + stop = get_ms(); + diff = stop - start; + test_lwext4_dir_ls("/mp/dir1"); + printf("test_lwext4_dir_test: time: %d ms\n", (int)diff); + printf("test_lwext4_dir_test: av: %d ms/entry\n", (int)diff / (len + 1)); + printf_io_timings(diff); + return true; +} + +static int verify_buf(const unsigned char *b, size_t len, unsigned char c) +{ + size_t i; + for (i = 0; i < len; ++i) { + if (b[i] != c) + return c - b[i]; + } + + return 0; +} + +bool test_lwext4_file_test(uint8_t *rw_buff, uint32_t rw_size, uint32_t rw_count) +{ + int r; + size_t size; + uint32_t i; + long int start; + long int stop; + long int diff; + uint32_t kbps; + uint64_t size_bytes; + + ext4_file f; + + printf("file_test:\n"); + printf(" rw size: %" PRIu32 "\n", rw_size); + printf(" rw count: %" PRIu32 "\n", rw_count); + + /*Add hello world file.*/ + r = ext4_fopen(&f, "/mp/hello.txt", "wb"); + r = ext4_fwrite(&f, "Hello World !\n", strlen("Hello World !\n"), 0); + r = ext4_fclose(&f); + + io_timings_clear(); + start = get_ms(); + r = ext4_fopen(&f, "/mp/test1", "wb"); + if (r != EOK) { + printf("ext4_fopen ERROR = %d\n", r); + return false; + } + + printf("ext4_write: %" PRIu32 " * %" PRIu32 " ...\n", rw_size, + rw_count); + for (i = 0; i < rw_count; ++i) { + + memset(rw_buff, i % 10 + '0', rw_size); + + r = ext4_fwrite(&f, rw_buff, rw_size, &size); + + if ((r != EOK) || (size != rw_size)) + break; + } + + if (i != rw_count) { + printf(" file_test: rw_count = %" PRIu32 "\n", i); + return false; + } + + stop = get_ms(); + diff = stop - start; + size_bytes = rw_size * rw_count; + size_bytes = (size_bytes * 1000) / 1024; + kbps = (size_bytes) / (diff + 1); + printf(" write time: %d ms\n", (int)diff); + printf(" write speed: %" PRIu32 " KB/s\n", kbps); + printf_io_timings(diff); + r = ext4_fclose(&f); + + io_timings_clear(); + start = get_ms(); + r = ext4_fopen(&f, "/mp/test1", "r+"); + if (r != EOK) { + printf("ext4_fopen ERROR = %d\n", r); + return false; + } + + printf("ext4_read: %" PRIu32 " * %" PRIu32 " ...\n", rw_size, rw_count); + + for (i = 0; i < rw_count; ++i) { + r = ext4_fread(&f, rw_buff, rw_size, &size); + + if ((r != EOK) || (size != rw_size)) + break; + + if (verify_buf(rw_buff, rw_size, i % 10 + '0')) + break; + } + + if (i != rw_count) { + printf(" file_test: rw_count = %" PRIu32 "\n", i); + return false; + } + + stop = get_ms(); + diff = stop - start; + size_bytes = rw_size * rw_count; + size_bytes = (size_bytes * 1000) / 1024; + kbps = (size_bytes) / (diff + 1); + printf(" read time: %d ms\n", (int)diff); + printf(" read speed: %d KB/s\n", (int)kbps); + printf_io_timings(diff); + + r = ext4_fclose(&f); + return true; +} +void test_lwext4_cleanup(void) +{ + long int start; + long int stop; + long int diff; + int r; + + printf("\ncleanup:\n"); + r = ext4_fremove("/mp/hello.txt"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove error: rc = %d\n", r); + } + + printf("remove /mp/test1\n"); + r = ext4_fremove("/mp/test1"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove error: rc = %d\n", r); + } + + printf("remove /mp/dir1\n"); + io_timings_clear(); + start = get_ms(); + r = ext4_dir_rm("/mp/dir1"); + if (r != EOK && r != ENOENT) { + printf("ext4_fremove ext4_dir_rm: rc = %d\n", r); + } + stop = get_ms(); + diff = stop - start; + printf("cleanup: time: %d ms\n", (int)diff); + printf_io_timings(diff); +} + +bool test_lwext4_mount(struct ext4_blockdev *bdev, struct ext4_bcache *bcache) +{ + int r; + + bc = bcache; + bd = bdev; + + if (!bd) { + printf("test_lwext4_mount: no block device\n"); + return false; + } + + ext4_dmask_set(DEBUG_ALL); + + r = ext4_device_register(bd, "ext4_fs"); + if (r != EOK) { + printf("ext4_device_register: rc = %d\n", r); + return false; + } + + r = ext4_mount("ext4_fs", "/mp/", false); + if (r != EOK) { + printf("ext4_mount: rc = %d\n", r); + return false; + } + + r = ext4_recover("/mp/"); + if (r != EOK && r != ENOTSUP) { + printf("ext4_recover: rc = %d\n", r); + return false; + } + + r = ext4_journal_start("/mp/"); + if (r != EOK) { + printf("ext4_journal_start: rc = %d\n", r); + return false; + } + + ext4_cache_write_back("/mp/", 1); + return true; +} + +bool test_lwext4_umount(void) +{ + int r; + + ext4_cache_write_back("/mp/", 0); + + r = ext4_journal_stop("/mp/"); + if (r != EOK) { + printf("ext4_journal_stop: fail %d", r); + return false; + } + + r = ext4_umount("/mp/"); + if (r != EOK) { + printf("ext4_umount: fail %d", r); + return false; + } + return true; +} diff --git a/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.h b/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.h new file mode 100644 index 0000000..bc0c446 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/common/test_lwext4.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_LWEXT4_H_ +#define TEST_LWEXT4_H_ + +#include +#include +#include + +void test_lwext4_dir_ls(const char *path); +void test_lwext4_mp_stats(void); +void test_lwext4_block_stats(void); +bool test_lwext4_dir_test(int len); +bool test_lwext4_file_test(uint8_t *rw_buff, uint32_t rw_size, uint32_t rw_count); +void test_lwext4_cleanup(void); + +bool test_lwext4_mount(struct ext4_blockdev *bdev, struct ext4_bcache *bcache); +bool test_lwext4_umount(void); + +void tim_wait_ms(uint32_t v); + +uint32_t tim_get_ms(void); +uint64_t tim_get_us(void); + +struct ext4_io_stats { + float io_read; + float io_write; + float cpu; +}; + +void io_timings_clear(void); +const struct ext4_io_stats *io_timings_get(uint32_t time_sum_ms); + +#endif /* TEST_LWEXT4_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/fs_test/lwext4_client.c b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_client.c new file mode 100644 index 0000000..f47ca93 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_client.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include +static int inet_pton(int af, const char *src, void *dst); + +#else +#include +#include +#include +#include +#endif + +static int winsock_init(void); +static void winsock_fini(void); + +/**@brief Default server addres.*/ +static char *server_addr = "127.0.0.1"; + +/**@brief Default connection port.*/ +static int connection_port = 1234; + +/**@brief Call op*/ +static char *op_code; + +static const char *usage = " \n\ +Welcome in lwext4_client. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ + --call (-c) - call opt \n\ + --port (-p) - server port \n\ + --addr (-a) - server ip address \n\ +\n"; + +static int client_connect(void) +{ + int fd = 0; + struct sockaddr_in serv_addr; + + if (winsock_init() < 0) { + printf("winsock_init error\n"); + exit(-1); + } + + memset(&serv_addr, '0', sizeof(serv_addr)); + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + printf("socket() error: %s\n", strerror(errno)); + exit(-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(connection_port); + + if (!inet_pton(AF_INET, server_addr, &serv_addr.sin_addr)) { + printf("inet_pton() error\n"); + exit(-1); + } + + if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + printf("connect() error: %s\n", strerror(errno)); + exit(-1); + } + + return fd; +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"call", required_argument, 0, 'c'}, + {"port", required_argument, 0, 'p'}, + {"addr", required_argument, 0, 'a'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "c:p:a:x", long_options, + &option_index))) { + + switch (c) { + case 'a': + server_addr = optarg; + break; + case 'p': + connection_port = atoi(optarg); + break; + case 'c': + op_code = optarg; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char *argv[]) +{ + int sockfd; + int n; + int rc; + char recvBuff[1024]; + + if (!parse_opt(argc, argv)) + return -1; + + sockfd = client_connect(); + + n = send(sockfd, op_code, strlen(op_code), 0); + if (n < 0) { + printf("\tWrite error: %s fd = %d\n", strerror(errno), sockfd); + return -1; + } + + n = recv(sockfd, (void *)&rc, sizeof(rc), 0); + if (n < 0) { + printf("\tWrite error: %s fd = %d\n", strerror(errno), sockfd); + return -1; + } + + printf("rc: %d %s\n", rc, strerror(rc)); + if (rc) + printf("\t%s\n", op_code); + + winsock_fini(); + return rc; +} + +static int winsock_init(void) +{ +#if WIN32 + int rc; + static WSADATA wsaData; + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rc != 0) { + return -1; + } +#endif + return 0; +} + +static void winsock_fini(void) +{ +#if WIN32 + WSACleanup(); +#endif +} + +#if WIN32 +static int inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage ss; + int size = sizeof(ss); + char src_copy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory(&ss, sizeof(ss)); + /* stupid non-const API */ + strncpy(src_copy, src, INET6_ADDRSTRLEN + 1); + src_copy[INET6_ADDRSTRLEN] = 0; + + if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, + &size) == 0) { + switch (af) { + case AF_INET: + *(struct in_addr *)dst = + ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = + ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} +#endif diff --git a/lib/lwext4_rust/c/lwext4/fs_test/lwext4_generic.c b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_generic.c new file mode 100644 index 0000000..a9bfb1b --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_generic.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" +#include "common/test_lwext4.h" + +#ifdef WIN32 +#include +#endif + +/**@brief Input stream name.*/ +char input_name[128] = "ext_images/ext2"; + +/**@brief Read-write size*/ +static int rw_szie = 1024 * 1024; + +/**@brief Read-write size*/ +static int rw_count = 10; + +/**@brief Directory test count*/ +static int dir_cnt = 0; + +/**@brief Cleanup after test.*/ +static bool cleanup_flag = false; + +/**@brief Block device stats.*/ +static bool bstat = false; + +/**@brief Superblock stats.*/ +static bool sbstat = false; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +/**@brief Verbose mode*/ +static bool verbose = 0; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Block cache handle.*/ +static struct ext4_bcache *bc; + +static const char *usage = " \n\ +Welcome in ext4 generic demo. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file (default = ext2) \n\ +[-w] --rw_size - single R/W size (default = 1024 * 1024) \n\ +[-c] --rw_count - R/W count (default = 10) \n\ +[-d] --dirs - directory test count (default = 0) \n\ +[-l] --clean - clean up after test \n\ +[-b] --bstat - block device stats \n\ +[-t] --sbstat - superblock stats \n\ +[-w] --wpart - windows partition mode \n\ +\n"; + +void io_timings_clear(void) +{ +} + +const struct ext4_io_stats *io_timings_get(uint32_t time_sum_ms) +{ + return NULL; +} + +uint32_t tim_get_ms(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +uint64_t tim_get_us(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000000) + (t.tv_usec); +} + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"rw_size", required_argument, 0, 's'}, + {"rw_count", required_argument, 0, 'c'}, + {"dirs", required_argument, 0, 'd'}, + {"clean", no_argument, 0, 'l'}, + {"bstat", no_argument, 0, 'b'}, + {"sbstat", no_argument, 0, 't'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:s:c:q:d:lbtwvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + strcpy(input_name, optarg); + break; + case 's': + rw_szie = atoi(optarg); + break; + case 'c': + rw_count = atoi(optarg); + break; + case 'd': + dir_cnt = atoi(optarg); + break; + case 'l': + cleanup_flag = true; + break; + case 'b': + bstat = true; + break; + case 't': + sbstat = true; + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char **argv) +{ + if (!parse_opt(argc, argv)) + return EXIT_FAILURE; + + printf("ext4_generic\n"); + printf("test conditions:\n"); + printf("\timput name: %s\n", input_name); + printf("\trw size: %d\n", rw_szie); + printf("\trw count: %d\n", rw_count); + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + if (!test_lwext4_mount(bd, bc)) + return EXIT_FAILURE; + + test_lwext4_cleanup(); + + if (sbstat) + test_lwext4_mp_stats(); + + test_lwext4_dir_ls("/mp/"); + fflush(stdout); + if (!test_lwext4_dir_test(dir_cnt)) + return EXIT_FAILURE; + + fflush(stdout); + uint8_t *rw_buff = malloc(rw_szie); + if (!rw_buff) { + free(rw_buff); + return EXIT_FAILURE; + } + if (!test_lwext4_file_test(rw_buff, rw_szie, rw_count)) { + free(rw_buff); + return EXIT_FAILURE; + } + + free(rw_buff); + + fflush(stdout); + test_lwext4_dir_ls("/mp/"); + + if (sbstat) + test_lwext4_mp_stats(); + + if (cleanup_flag) + test_lwext4_cleanup(); + + if (bstat) + test_lwext4_block_stats(); + + if (!test_lwext4_umount()) + return EXIT_FAILURE; + + printf("\ntest finished\n"); + return EXIT_SUCCESS; +} diff --git a/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mbr.c b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mbr.c new file mode 100644 index 0000000..9bb19b8 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mbr.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + +/**@brief Input stream name.*/ +const char *input_name = NULL; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +static bool verbose = false; + +static const char *usage = " \n\ +Welcome in lwext4_mbr tool. \n\ +Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file name (or blockdevice) \n\ +[-w] --wpart - windows partition mode \n\ +[-v] --verbose - verbose mode \n\ +\n"; + + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:wvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + input_name = optarg; + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + + return true; +} + +int main(int argc, char **argv) +{ + int r; + if (!parse_opt(argc, argv)){ + printf("parse_opt error\n"); + return EXIT_FAILURE; + } + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + printf("ext4_mbr\n"); + struct ext4_mbr_bdevs bdevs; + r = ext4_mbr_scan(bd, &bdevs); + if (r != EOK) { + printf("ext4_mbr_scan error\n"); + return EXIT_FAILURE; + } + + int i; + printf("ext4_mbr_scan:\n"); + for (i = 0; i < 4; i++) { + printf("mbr_entry %d:\n", i); + if (!bdevs.partitions[i].bdif) { + printf("\tempty/unknown\n"); + continue; + } + + printf("\toffeset: 0x%"PRIx64", %"PRIu64"MB\n", + bdevs.partitions[i].part_offset, + bdevs.partitions[i].part_offset / (1024 * 1024)); + printf("\tsize: 0x%"PRIx64", %"PRIu64"MB\n", + bdevs.partitions[i].part_size, + bdevs.partitions[i].part_size / (1024 * 1024)); + } + + + return EXIT_SUCCESS; +} diff --git a/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mkfs.c b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mkfs.c new file mode 100644 index 0000000..781e8dc --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_mkfs.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + +/**@brief Input stream name.*/ +const char *input_name = NULL; + +/**@brief Block device handle.*/ +static struct ext4_blockdev *bd; + +/**@brief Indicates that input is windows partition.*/ +static bool winpart = false; + +static int fs_type = F_SET_EXT4; + +static struct ext4_fs fs; +static struct ext4_mkfs_info info = { + .block_size = 1024, + .journal = true, +}; + +static bool verbose = false; + +static const char *usage = " \n\ +Welcome in lwext4_mkfs tool . \n\ +Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ +[-i] --input - input file name (or blockdevice) \n\ +[-w] --wpart - windows partition mode \n\ +[-v] --verbose - verbose mode \n\ +[-b] --block - block size: 1024, 2048, 4096 (default 1024) \n\ +[-e] --ext - fs type (ext2: 2, ext3: 3 ext4: 4)) \n\ +\n"; + + +static bool open_linux(void) +{ + file_dev_name_set(input_name); + bd = file_dev_get(); + if (!bd) { + printf("open_filedev: fail\n"); + return false; + } + return true; +} + +static bool open_windows(void) +{ +#ifdef WIN32 + file_windows_name_set(input_name); + bd = file_windows_dev_get(); + if (!bd) { + printf("open_winpartition: fail\n"); + return false; + } + return true; +#else + printf("open_winpartition: this mode should be used only under windows " + "!\n"); + return false; +#endif +} + +static bool open_filedev(void) +{ + return winpart ? open_windows() : open_linux(); +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"block", required_argument, 0, 'b'}, + {"ext", required_argument, 0, 'e'}, + {"wpart", no_argument, 0, 'w'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:b:e:wvx", + long_options, &option_index))) { + + switch (c) { + case 'i': + input_name = optarg; + break; + case 'b': + info.block_size = atoi(optarg); + break; + case 'e': + fs_type = atoi(optarg); + break; + case 'w': + winpart = true; + break; + case 'v': + verbose = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + + switch (info.block_size) { + case 1024: + case 2048: + case 4096: + break; + default: + printf("parse_opt: block_size = %"PRIu32" unsupported\n", + info.block_size); + return false; + } + + switch (fs_type) { + case F_SET_EXT2: + case F_SET_EXT3: + case F_SET_EXT4: + break; + default: + printf("parse_opt: fs_type = %"PRIu32" unsupported\n", fs_type); + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + int r; + if (!parse_opt(argc, argv)){ + printf("parse_opt error\n"); + return EXIT_FAILURE; + } + + if (!open_filedev()) { + printf("open_filedev error\n"); + return EXIT_FAILURE; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + printf("ext4_mkfs: ext%d\n", fs_type); + r = ext4_mkfs(&fs, bd, &info, fs_type); + if (r != EOK) { + printf("ext4_mkfs error: %d\n", r); + return EXIT_FAILURE; + } + + memset(&info, 0, sizeof(struct ext4_mkfs_info)); + r = ext4_mkfs_read_info(bd, &info); + if (r != EOK) { + printf("ext4_mkfs_read_info error: %d\n", r); + return EXIT_FAILURE; + } + + printf("Created filesystem with parameters:\n"); + printf("Size: %"PRIu64"\n", info.len); + printf("Block size: %"PRIu32"\n", info.block_size); + printf("Blocks per group: %"PRIu32"\n", info.blocks_per_group); + printf("Inodes per group: %"PRIu32"\n", info.inodes_per_group); + printf("Inode size: %"PRIu32"\n", info.inode_size); + printf("Inodes: %"PRIu32"\n", info.inodes); + printf("Journal blocks: %"PRIu32"\n", info.journal_blocks); + printf("Features ro_compat: 0x%x\n", info.feat_ro_compat); + printf("Features compat: 0x%x\n", info.feat_compat); + printf("Features incompat: 0x%x\n", info.feat_incompat); + printf("BG desc reserve: %"PRIu32"\n", info.bg_desc_reserve_blocks); + printf("Descriptor size: %"PRIu32"\n",info.dsc_size); + printf("Label: %s\n", info.label); + + printf("\nDone ...\n"); + return EXIT_SUCCESS; +} diff --git a/lib/lwext4_rust/c/lwext4/fs_test/lwext4_server.c b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_server.c new file mode 100644 index 0000000..b65ef3d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/fs_test/lwext4_server.c @@ -0,0 +1,1180 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include +#include "../blockdev/linux/file_dev.h" +#include "../blockdev/windows/file_windows.h" + + +static int winsock_init(void); +static void winsock_fini(void); +static char *entry_to_str(uint8_t type); + +#define MAX_FILES 64 +#define MAX_DIRS 64 + +#define MAX_RW_BUFFER (1024 * 1024) +#define RW_BUFFER_PATERN ('x') + +/**@brief Default connection port*/ +static int connection_port = 1234; + +/**@brief Default filesystem filename.*/ +static char *ext4_fname = "ext2"; + +/**@brief Verbose mode*/ +static bool verbose = false; + +/**@brief Winpart mode*/ +static bool winpart = false; + +/**@brief Blockdev handle*/ +static struct ext4_blockdev *bd; + +static bool cache_wb = false; + +static char read_buffer[MAX_RW_BUFFER]; +static char write_buffer[MAX_RW_BUFFER]; + +static const char *usage = " \n\ +Welcome in lwext4_server. \n\ +Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) \n\ +Usage: \n\ + --image (-i) - ext2/3/4 image file \n\ + --port (-p) - server port \n\ + --verbose (-v) - verbose mode \n\ + --winpart (-w) - windows_partition mode \n\ + --cache_wb (-c) - cache writeback_mode \n\ +\n"; + +/**@brief Open file instance descriptor.*/ +struct lwext4_files { + char name[255]; + ext4_file fd; +}; + +/**@brief Open directory instance descriptor.*/ +struct lwext4_dirs { + char name[255]; + ext4_dir fd; +}; + +/**@brief Library call opcode.*/ +struct lwext4_op_codes { + char *func; +}; + +/**@brief Library call wraper.*/ +struct lwext4_call { + int (*lwext4_call)(const char *p); +}; + +/**@brief */ +static struct lwext4_files file_tab[MAX_FILES]; + +/**@brief */ +static struct lwext4_dirs dir_tab[MAX_DIRS]; + +/**@brief */ +static struct lwext4_op_codes op_codes[] = { + "device_register", + "mount", + "umount", + "mount_point_stats", + "cache_write_back", + "fremove", + "fopen", + "fclose", + "fread", + "fwrite", + "fseek", + "ftell", + "fsize", + "dir_rm", + "dir_mk", + "dir_open", + "dir_close", + "dir_entry_get", + + "multi_fcreate", + "multi_fwrite", + "multi_fread", + "multi_fremove", + "multi_dcreate", + "multi_dremove", + "stats_save", + "stats_check", +}; + +static int device_register(const char *p); +static int mount(const char *p); +static int umount(const char *p); +static int mount_point_stats(const char *p); +static int cache_write_back(const char *p); +static int fremove(const char *p); +static int file_open(const char *p); +static int file_close(const char *p); +static int file_read(const char *p); +static int file_write(const char *p); +static int file_seek(const char *p); +static int file_tell(const char *p); +static int file_size(const char *p); +static int dir_rm(const char *p); +static int dir_mk(const char *p); +static int dir_open(const char *p); +static int dir_close(const char *p); +static int dir_close(const char *p); +static int dir_entry_get(const char *p); + +static int multi_fcreate(const char *p); +static int multi_fwrite(const char *p); +static int multi_fread(const char *p); +static int multi_fremove(const char *p); +static int multi_dcreate(const char *p); +static int multi_dremove(const char *p); +static int stats_save(const char *p); +static int stats_check(const char *p); + +/**@brief */ +static struct lwext4_call op_call[] = { + device_register, /*PARAMS(3): 0 cache_mode dev_name */ + mount, /*PARAMS(2): dev_name mount_point */ + umount, /*PARAMS(1): mount_point */ + mount_point_stats, /*PARAMS(2): mount_point, 0 */ + cache_write_back, /*PARAMS(2): mount_point, en */ + fremove, /*PARAMS(1): path */ + file_open, /*PARAMS(2): fid path flags */ + file_close, /*PARAMS(1): fid */ + file_read, /*PARAMS(4): fid 0 len 0 */ + file_write, /*PARAMS(4): fid 0 len 0 */ + file_seek, /*PARAMS(2): fid off origin */ + file_tell, /*PARAMS(2): fid exp */ + file_size, /*PARAMS(2): fid exp */ + dir_rm, /*PARAMS(1): path */ + dir_mk, /*PARAMS(1): path */ + dir_open, /*PARAMS(2): did, path */ + dir_close, /*PARAMS(1): did */ + dir_entry_get, /*PARAMS(2): did, exp */ + + multi_fcreate, /*PARAMS(3): path prefix cnt */ + multi_fwrite, /*PARAMS(4): path prefix cnt size */ + multi_fread, /*PARAMS(4): path prefix cnt size */ + multi_fremove, /*PARAMS(2): path prefix cnt */ + multi_dcreate, /*PARAMS(3): path prefix cnt */ + multi_dremove, /*PARAMS(2): path prefix */ + stats_save, /*PARAMS(1): path */ + stats_check, /*PARAMS(1): path */ +}; + +static clock_t get_ms(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +/**@brief */ +static int exec_op_code(const char *opcode) +{ + int i; + int r = -1; + + for (i = 0; i < sizeof(op_codes) / sizeof(op_codes[0]); ++i) { + + if (strncmp(op_codes[i].func, opcode, strlen(op_codes[i].func))) + continue; + + if (opcode[strlen(op_codes[i].func)] != ' ') + continue; + + printf("%s\n", opcode); + opcode += strlen(op_codes[i].func); + /*Call*/ + + clock_t t = get_ms(); + r = op_call[i].lwext4_call(opcode); + + printf("rc: %d, time: %ums\n", r, (unsigned int)(get_ms() - t)); + + break; + } + + return r; +} + +static int server_open(void) +{ + int fd = 0; + struct sockaddr_in serv_addr; + + memset(&serv_addr, 0, sizeof(serv_addr)); + + if (winsock_init() < 0) { + printf("winsock_init() error\n"); + exit(-1); + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + printf("socket() error: %s\n", strerror(errno)); + exit(-1); + } + + int yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, + sizeof(int))) { + printf("setsockopt() error: %s\n", strerror(errno)); + exit(-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(connection_port); + + if (bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { + printf("bind() error: %s\n", strerror(errno)); + exit(-1); + } + + if (listen(fd, 1)) { + printf("listen() error: %s\n", strerror(errno)); + exit(-1); + } + + return fd; +} + +static bool parse_opt(int argc, char **argv) +{ + int option_index = 0; + int c; + + static struct option long_options[] = { + {"image", required_argument, 0, 'i'}, + {"port", required_argument, 0, 'p'}, + {"verbose", no_argument, 0, 'v'}, + {"winpart", no_argument, 0, 'w'}, + {"cache_wb", no_argument, 0, 'c'}, + {"version", no_argument, 0, 'x'}, + {0, 0, 0, 0}}; + + while (-1 != (c = getopt_long(argc, argv, "i:p:vcwx", long_options, + &option_index))) { + + switch (c) { + case 'i': + ext4_fname = optarg; + break; + case 'p': + connection_port = atoi(optarg); + break; + case 'v': + verbose = true; + break; + case 'c': + cache_wb = true; + break; + case 'w': + winpart = true; + break; + case 'x': + puts(VERSION); + exit(0); + break; + default: + printf("%s", usage); + return false; + } + } + return true; +} + +int main(int argc, char *argv[]) +{ + int n; + int listenfd; + int connfd; + char op_code[128]; + + if (!parse_opt(argc, argv)) + return -1; + + listenfd = server_open(); + + printf("lwext4_server: listening on port: %d\n", connection_port); + + memset(write_buffer, RW_BUFFER_PATERN, MAX_RW_BUFFER); + while (1) { + connfd = accept(listenfd, (struct sockaddr *)NULL, NULL); + + n = recv(connfd, op_code, sizeof(op_code), 0); + + if (n < 0) { + printf("recv() error: %s fd = %d\n", strerror(errno), + connfd); + break; + } + + op_code[n] = 0; + + int r = exec_op_code(op_code); + + n = send(connfd, (void *)&r, sizeof(r), 0); + if (n < 0) { + printf("send() error: %s fd = %d\n", strerror(errno), + connfd); + break; + } + + close(connfd); + } + + winsock_fini(); + return 0; +} + +static int device_register(const char *p) +{ + int dev; + int cache_mode; + char dev_name[32]; + + if (sscanf(p, "%d %d %s", &dev, &cache_mode, dev_name) != 3) { + printf("Param list error\n"); + return -1; + } + +#ifdef WIN32 + if (winpart) { + file_windows_name_set(ext4_fname); + bd = file_windows_dev_get(); + + } else +#endif + { + file_dev_name_set(ext4_fname); + bd = file_dev_get(); + } + + ext4_device_unregister_all(); + + return ext4_device_register(bd, dev_name); +} + +static int mount(const char *p) +{ + char dev_name[32]; + char mount_point[32]; + int rc; + + if (sscanf(p, "%s %s", dev_name, mount_point) != 2) { + printf("Param list error\n"); + return -1; + } + + if (verbose) + ext4_dmask_set(DEBUG_ALL); + + rc = ext4_mount(dev_name, mount_point, false); + if (rc != EOK) + return rc; + + rc = ext4_recover(mount_point); + if (rc != EOK && rc != ENOTSUP) + return rc; + + rc = ext4_journal_start(mount_point); + if (rc != EOK) + return rc; + + if (cache_wb) + ext4_cache_write_back(mount_point, 1); + return rc; +} + +static int umount(const char *p) +{ + char mount_point[32]; + int rc; + + if (sscanf(p, "%s", mount_point) != 1) { + printf("Param list error\n"); + return -1; + } + + if (cache_wb) + ext4_cache_write_back(mount_point, 0); + + rc = ext4_journal_stop(mount_point); + if (rc != EOK) + return rc; + + rc = ext4_umount(mount_point); + if (rc != EOK) + return rc; + + return rc; +} + +static int mount_point_stats(const char *p) +{ + char mount_point[32]; + int d; + int rc; + struct ext4_mount_stats stats; + + if (sscanf(p, "%s %d", mount_point, &d) != 2) { + printf("Param list error\n"); + return -1; + } + + rc = ext4_mount_point_stats(mount_point, &stats); + + if (rc != EOK) + return rc; + + if (verbose) { + printf("\tinodes_count = %" PRIu32"\n", stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + stats.free_blocks_count); + printf("\tblock_size = %" PRIu32"\n", stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + stats.inodes_per_group); + printf("\tvolume_name = %s\n", stats.volume_name); + } + + return rc; +} + +static int cache_write_back(const char *p) +{ + char mount_point[32]; + int en; + + if (sscanf(p, "%s %d", mount_point, &en) != 2) { + printf("Param list error\n"); + return -1; + } + + return ext4_cache_write_back(mount_point, en); +} + +static int fremove(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_fremove(path); +} + +static int file_open(const char *p) +{ + int fid = MAX_FILES; + char path[256]; + char flags[8]; + int rc; + + if (sscanf(p, "%d %s %s", &fid, path, flags) != 3) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + rc = ext4_fopen(&file_tab[fid].fd, path, flags); + + if (rc == EOK) + strcpy(file_tab[fid].name, path); + + return rc; +} + +static int file_close(const char *p) +{ + int fid = MAX_FILES; + int rc; + + if (sscanf(p, "%d", &fid) != 1) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + rc = ext4_fclose(&file_tab[fid].fd); + + if (rc == EOK) + file_tab[fid].name[0] = 0; + + return rc; +} + +static int file_read(const char *p) +{ + int fid = MAX_FILES; + int len; + int d; + int rc; + size_t rb; + + if (sscanf(p, "%d %d %d %d", &fid, &d, &len, &d) != 4) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + + memset(read_buffer, 0, MAX_RW_BUFFER); + rc = ext4_fread(&file_tab[fid].fd, read_buffer, d, &rb); + + if (rc != EOK) + break; + + if (rb != d) { + printf("Read count error\n"); + return -1; + } + + if (memcmp(read_buffer, write_buffer, d)) { + printf("Read compare error\n"); + return -1; + } + + len -= d; + } + + return rc; +} + +static int file_write(const char *p) +{ + int fid = MAX_FILES; + int d; + int rc; + + int len; + size_t wb; + + if (sscanf(p, "%d %d %d %d", &fid, &d, &len, &d) != 4) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + rc = ext4_fwrite(&file_tab[fid].fd, write_buffer, d, &wb); + + if (rc != EOK) + break; + + if (wb != d) { + printf("Write count error\n"); + return -1; + } + + len -= d; + } + + return rc; +} + +static int file_seek(const char *p) +{ + int fid = MAX_FILES; + int off; + int origin; + + if (sscanf(p, "%d %d %d", &fid, &off, &origin) != 3) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + return ext4_fseek(&file_tab[fid].fd, off, origin); +} + +static int file_tell(const char *p) +{ + int fid = MAX_FILES; + uint32_t exp_pos; + + if (sscanf(p, "%d %u", &fid, &exp_pos) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + if (exp_pos != ext4_ftell(&file_tab[fid].fd)) { + printf("Expected filepos error\n"); + return -1; + } + + return EOK; +} + +static int file_size(const char *p) +{ + int fid = MAX_FILES; + uint32_t exp_size; + + if (sscanf(p, "%d %u", &fid, &exp_size) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(fid < MAX_FILES)) { + printf("File id too big\n"); + return -1; + } + + if (file_tab[fid].name[0] == 0) { + printf("File id empty\n"); + return -1; + } + + if (exp_size != ext4_fsize(&file_tab[fid].fd)) { + printf("Expected filesize error\n"); + return -1; + } + + return EOK; +} + +static int dir_rm(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_dir_rm(path); +} + +static int dir_mk(const char *p) +{ + char path[255]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_dir_mk(path); +} + +static int dir_open(const char *p) +{ + int did = MAX_DIRS; + char path[255]; + int rc; + + if (sscanf(p, "%d %s", &did, path) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + rc = ext4_dir_open(&dir_tab[did].fd, path); + + if (rc == EOK) + strcpy(dir_tab[did].name, path); + + return rc; +} + +static int dir_close(const char *p) +{ + int did = MAX_DIRS; + int rc; + + if (sscanf(p, "%d", &did) != 1) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + if (dir_tab[did].name[0] == 0) { + printf("Dir id empty\n"); + return -1; + } + + rc = ext4_dir_close(&dir_tab[did].fd); + + if (rc == EOK) + dir_tab[did].name[0] = 0; + + return rc; +} + +static int dir_entry_get(const char *p) +{ + int did = MAX_DIRS; + int exp; + char name[256]; + + if (sscanf(p, "%d %d", &did, &exp) != 2) { + printf("Param list error\n"); + return -1; + } + + if (!(did < MAX_DIRS)) { + printf("Dir id too big\n"); + return -1; + } + + if (dir_tab[did].name[0] == 0) { + printf("Dir id empty\n"); + return -1; + } + + int idx = 0; + const ext4_direntry *d; + + while ((d = ext4_dir_entry_next(&dir_tab[did].fd)) != NULL) { + + idx++; + memcpy(name, d->name, d->name_length); + name[d->name_length] = 0; + if (verbose) { + printf("\t%s %s\n", entry_to_str(d->inode_type), name); + } + } + + if (idx < 2) { + printf("Minumum dir entry error\n"); + return -1; + } + + if ((idx - 2) != exp) { + printf("Expected dir entry error\n"); + return -1; + } + + return EOK; +} + +static int multi_fcreate(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt; + int rc; + int i; + ext4_file fd; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "wb+"); + + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_fwrite(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i; + int len, ll; + int rc; + size_t d, wb; + ext4_file fd; + + if (sscanf(p, "%s %s %d %d", path, prefix, &cnt, &ll) != 4) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "rb+"); + + if (rc != EOK) + break; + + len = ll; + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + rc = ext4_fwrite(&fd, write_buffer, d, &wb); + + if (rc != EOK) + break; + + if (wb != d) { + printf("Write count error\n"); + return -1; + } + + len -= d; + } + } + + return rc; +} + +static int multi_fread(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt; + int len, ll; + int rc ,i, d; + size_t rb; + ext4_file fd; + + if (sscanf(p, "%s %s %d %d", path, prefix, &cnt, &ll) != 4) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fopen(&fd, path1, "rb+"); + + if (rc != EOK) + break; + + len = ll; + while (len) { + d = len > MAX_RW_BUFFER ? MAX_RW_BUFFER : len; + + memset(read_buffer, 0, MAX_RW_BUFFER); + rc = ext4_fread(&fd, read_buffer, d, &rb); + + if (rc != EOK) + break; + + if (rb != d) { + printf("Read count error\n"); + return -1; + } + + if (memcmp(read_buffer, write_buffer, d)) { + printf("Read compare error\n"); + return -1; + } + + len -= d; + } + } + + return rc; +} + +static int multi_fremove(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_fremove(path1); + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_dcreate(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_dir_mk(path1); + if (rc != EOK) + break; + } + + return rc; +} + +static int multi_dremove(const char *p) +{ + char path[256]; + char path1[256]; + char prefix[32]; + int cnt, i, rc; + + if (sscanf(p, "%s %s %d", path, prefix, &cnt) != 3) { + printf("Param list error\n"); + return -1; + } + + for (i = 0; i < cnt; ++i) { + sprintf(path1, "%s%s%d", path, prefix, i); + rc = ext4_dir_rm(path1); + if (rc != EOK) + break; + } + + return rc; +} + +struct ext4_mount_stats saved_stats; + +static int stats_save(const char *p) +{ + char path[256]; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + return ext4_mount_point_stats(path, &saved_stats); +} + +static int stats_check(const char *p) +{ + char path[256]; + int rc; + + struct ext4_mount_stats actual_stats; + + if (sscanf(p, "%s", path) != 1) { + printf("Param list error\n"); + return -1; + } + + rc = ext4_mount_point_stats(path, &actual_stats); + + if (rc != EOK) + return rc; + + if (memcmp(&saved_stats, &actual_stats, + sizeof(struct ext4_mount_stats))) { + if (verbose) { + printf("\tMount point stats error:\n"); + printf("\tsaved_stats:\n"); + printf("\tinodes_count = %" PRIu32"\n", + saved_stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + saved_stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", + saved_stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + saved_stats.free_blocks_count); + printf("\tblock_size = %" PRIu32"\n", + saved_stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + saved_stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + saved_stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + saved_stats.inodes_per_group); + printf("\tvolume_name = %s\n", saved_stats.volume_name); + printf("\tactual_stats:\n"); + printf("\tinodes_count = %" PRIu32"\n", + actual_stats.inodes_count); + printf("\tfree_inodes_count = %" PRIu32"\n", + actual_stats.free_inodes_count); + printf("\tblocks_count = %" PRIu64"\n", + actual_stats.blocks_count); + printf("\tfree_blocks_count = %" PRIu64"\n", + actual_stats.free_blocks_count); + printf("\tblock_size = %d\n", actual_stats.block_size); + printf("\tblock_group_count = %" PRIu32"\n", + actual_stats.block_group_count); + printf("\tblocks_per_group = %" PRIu32"\n", + actual_stats.blocks_per_group); + printf("\tinodes_per_group = %" PRIu32"\n", + actual_stats.inodes_per_group); + printf("\tvolume_name = %s\n", + actual_stats.volume_name); + } + return -1; + } + + return rc; +} + +static char *entry_to_str(uint8_t type) +{ + switch (type) { + case EXT4_DE_UNKNOWN: + return "[unk] "; + case EXT4_DE_REG_FILE: + return "[fil] "; + case EXT4_DE_DIR: + return "[dir] "; + case EXT4_DE_CHRDEV: + return "[cha] "; + case EXT4_DE_BLKDEV: + return "[blk] "; + case EXT4_DE_FIFO: + return "[fif] "; + case EXT4_DE_SOCK: + return "[soc] "; + case EXT4_DE_SYMLINK: + return "[sym] "; + default: + break; + } + return "[???]"; +} + +static int winsock_init(void) +{ +#if WIN32 + int rc; + static WSADATA wsaData; + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rc != 0) { + return -1; + } +#endif + return 0; +} + +static void winsock_fini(void) +{ +#if WIN32 + WSACleanup(); +#endif +} diff --git a/lib/lwext4_rust/c/lwext4/include/ext4.h b/lib/lwext4_rust/c/lwext4/include/ext4.h new file mode 100644 index 0000000..18e756e --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4.h @@ -0,0 +1,628 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4.h + * @brief Ext4 high level operations (files, directories, mount points...). + * Client has to include only this file. + */ + +#ifndef EXT4_H_ +#define EXT4_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include + +/********************************OS LOCK INFERFACE***************************/ + +/**@brief OS dependent lock interface.*/ +struct ext4_lock { + + /**@brief Lock access to mount point.*/ + void (*lock)(void); + + /**@brief Unlock access to mount point.*/ + void (*unlock)(void); +}; + +/********************************FILE DESCRIPTOR*****************************/ + +/**@brief File descriptor. */ +typedef struct ext4_file { + + /**@brief Mount point handle.*/ + struct ext4_mountpoint *mp; + + /**@brief File inode id.*/ + uint32_t inode; + + /**@brief Open flags.*/ + uint32_t flags; + + /**@brief File size.*/ + uint64_t fsize; + + /**@brief Actual file position.*/ + uint64_t fpos; +} ext4_file; + +/*****************************DIRECTORY DESCRIPTOR***************************/ + +/**@brief Directory entry descriptor. */ +typedef struct ext4_direntry { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; + uint8_t name[255]; +} ext4_direntry; + +/**@brief Directory descriptor. */ +typedef struct ext4_dir { + /**@brief File descriptor.*/ + ext4_file f; + /**@brief Current directory entry.*/ + ext4_direntry de; + /**@brief Next entry offset.*/ + uint64_t next_off; +} ext4_dir; + +/********************************MOUNT OPERATIONS****************************/ + +/**@brief Register block device. + * + * @param bd Block device. + * @param dev_name Block device name. + * + * @return Standard error code.*/ +int ext4_device_register(struct ext4_blockdev *bd, + const char *dev_name); + +/**@brief Un-register block device. + * + * @param dev_name Block device name. + * + * @return Standard error code.*/ +int ext4_device_unregister(const char *dev_name); + +/**@brief Un-register all block devices. + * + * @return Standard error code.*/ +int ext4_device_unregister_all(void); + +/**@brief Mount a block device with EXT4 partition to the mount point. + * + * @param dev_name Block device name (@ref ext4_device_register). + * @param mount_point Mount point, for example: + * - / + * - /my_partition/ + * - /my_second_partition/ + * @param read_only mount as read-only mode. + * + * @return Standard error code */ +int ext4_mount(const char *dev_name, + const char *mount_point, + bool read_only); + +/**@brief Umount operation. + * + * @param mount_point Mount point. + * + * @return Standard error code */ +int ext4_umount(const char *mount_point); + +/**@brief Starts journaling. Journaling start/stop functions are transparent + * and might be used on filesystems without journaling support. + * @warning Usage: + * ext4_mount("sda1", "/"); + * ext4_journal_start("/"); + * + * //File operations here... + * + * ext4_journal_stop("/"); + * ext4_umount("/"); + * @param mount_point Mount point. + * + * @return Standard error code. */ +int ext4_journal_start(const char *mount_point); + +/**@brief Stops journaling. Journaling start/stop functions are transparent + * and might be used on filesystems without journaling support. + * + * @param mount_point Mount point name. + * + * @return Standard error code. */ +int ext4_journal_stop(const char *mount_point); + +/**@brief Journal recovery. + * @warning Must be called after @ref ext4_mount. + * + * @param mount_point Mount point. + * + * @return Standard error code. */ +int ext4_recover(const char *mount_point); + +/**@brief Some of the filesystem stats. */ +struct ext4_mount_stats { + uint32_t inodes_count; + uint32_t free_inodes_count; + uint64_t blocks_count; + uint64_t free_blocks_count; + + uint32_t block_size; + uint32_t block_group_count; + uint32_t blocks_per_group; + uint32_t inodes_per_group; + + char volume_name[16]; +}; + +/**@brief Get file mount point stats. + * + * @param mount_point Mount point. + * @param stats Filesystem stats. + * + * @return Standard error code. */ +int ext4_mount_point_stats(const char *mount_point, + struct ext4_mount_stats *stats); + +/**@brief Setup OS lock routines. + * + * @param mount_point Mount point. + * @param locks Lock and unlock functions + * + * @return Standard error code. */ +int ext4_mount_setup_locks(const char *mount_point, + const struct ext4_lock *locks); + +/**@brief Acquire the filesystem superblock pointer of a mp. + * + * @param mount_point Mount point. + * @param sb Superblock handle + * + * @return Standard error code. */ +int ext4_get_sblock(const char *mount_point, struct ext4_sblock **sb); + +/**@brief Enable/disable write back cache mode. + * @warning Default model of cache is write trough. It means that when You do: + * + * ext4_fopen(...); + * ext4_fwrite(...); + * < --- data is flushed to physical drive + * + * When you do: + * ext4_cache_write_back(..., 1); + * ext4_fopen(...); + * ext4_fwrite(...); + * < --- data is NOT flushed to physical drive + * ext4_cache_write_back(..., 0); + * < --- when write back mode is disabled all + * cache data will be flushed + * To enable write back mode permanently just call this function + * once after ext4_mount (and disable before ext4_umount). + * + * Some of the function use write back cache mode internally. + * If you enable write back mode twice you have to disable it twice + * to flush all data: + * + * ext4_cache_write_back(..., 1); + * ext4_cache_write_back(..., 1); + * + * ext4_cache_write_back(..., 0); + * ext4_cache_write_back(..., 0); + * + * Write back mode is useful when you want to create a lot of empty + * files/directories. + * + * @param path Mount point. + * @param on Enable/disable cache writeback mode. + * + * @return Standard error code. */ +int ext4_cache_write_back(const char *path, bool on); + + +/**@brief Force cache flush. + * + * @param path Mount point. + * + * @return Standard error code. */ +int ext4_cache_flush(const char *path); + +/********************************FILE OPERATIONS*****************************/ + +/**@brief Remove file by path. + * + * @param path Path to file. + * + * @return Standard error code. */ +int ext4_fremove(const char *path); + +/**@brief Create a hardlink for a file. + * + * @param path Path to file. + * @param hardlink_path Path of hardlink. + * + * @return Standard error code. */ +int ext4_flink(const char *path, const char *hardlink_path); + +/**@brief Rename file. + * @param path Source. + * @param new_path Destination. + * @return Standard error code. */ +int ext4_frename(const char *path, const char *new_path); + +/**@brief File open function. + * + * @param file File handle. + * @param path File path, has to start from mount point:/my_partition/file. + * @param flags File open flags. + * |---------------------------------------------------------------| + * | r or rb O_RDONLY | + * |---------------------------------------------------------------| + * | w or wb O_WRONLY|O_CREAT|O_TRUNC | + * |---------------------------------------------------------------| + * | a or ab O_WRONLY|O_CREAT|O_APPEND | + * |---------------------------------------------------------------| + * | r+ or rb+ or r+b O_RDWR | + * |---------------------------------------------------------------| + * | w+ or wb+ or w+b O_RDWR|O_CREAT|O_TRUNC | + * |---------------------------------------------------------------| + * | a+ or ab+ or a+b O_RDWR|O_CREAT|O_APPEND | + * |---------------------------------------------------------------| + * + * @return Standard error code.*/ +int ext4_fopen(ext4_file *file, const char *path, const char *flags); + +/**@brief Alternate file open function. + * + * @param file File handle. + * @param path File path, has to start from mount point:/my_partition/file. + * @param flags File open flags. + * + * @return Standard error code.*/ +int ext4_fopen2(ext4_file *file, const char *path, int flags); + +/**@brief File close function. + * + * @param file File handle. + * + * @return Standard error code.*/ +int ext4_fclose(ext4_file *file); + + +/**@brief File truncate function. + * + * @param file File handle. + * @param size New file size. + * + * @return Standard error code.*/ +int ext4_ftruncate(ext4_file *file, uint64_t size); + +/**@brief Read data from file. + * + * @param file File handle. + * @param buf Output buffer. + * @param size Bytes to read. + * @param rcnt Bytes read (NULL allowed). + * + * @return Standard error code.*/ +int ext4_fread(ext4_file *file, void *buf, size_t size, size_t *rcnt); + +/**@brief Write data to file. + * + * @param file File handle. + * @param buf Data to write + * @param size Write length.. + * @param wcnt Bytes written (NULL allowed). + * + * @return Standard error code.*/ +int ext4_fwrite(ext4_file *file, const void *buf, size_t size, size_t *wcnt); + +/**@brief File seek operation. + * + * @param file File handle. + * @param offset Offset to seek. + * @param origin Seek type: + * @ref SEEK_SET + * @ref SEEK_CUR + * @ref SEEK_END + * + * @return Standard error code.*/ +int ext4_fseek(ext4_file *file, int64_t offset, uint32_t origin); + +/**@brief Get file position. + * + * @param file File handle. + * + * @return Actual file position */ +uint64_t ext4_ftell(ext4_file *file); + +/**@brief Get file size. + * + * @param file File handle. + * + * @return File size. */ +uint64_t ext4_fsize(ext4_file *file); + + +/**@brief Get inode of file/directory/link. + * + * @param path Parh to file/dir/link. + * @param ret_ino Inode number. + * @param inode Inode internals. + * + * @return Standard error code.*/ +int ext4_raw_inode_fill(const char *path, uint32_t *ret_ino, + struct ext4_inode *inode); + +/**@brief Check if inode exists. + * + * @param path Parh to file/dir/link. + * @param type Inode type. + * @ref EXT4_DIRENTRY_UNKNOWN + * @ref EXT4_DE_REG_FILE + * @ref EXT4_DE_DIR + * @ref EXT4_DE_CHRDEV + * @ref EXT4_DE_BLKDEV + * @ref EXT4_DE_FIFO + * @ref EXT4_DE_SOCK + * @ref EXT4_DE_SYMLINK + * + * @return Standard error code.*/ +int ext4_inode_exist(const char *path, int type); + +/**@brief Change file/directory/link mode bits. + * + * @param path Path to file/dir/link. + * @param mode New mode bits (for example 0777). + * + * @return Standard error code.*/ +int ext4_mode_set(const char *path, uint32_t mode); + + +/**@brief Get file/directory/link mode bits. + * + * @param path Path to file/dir/link. + * @param mode New mode bits (for example 0777). + * + * @return Standard error code.*/ +int ext4_mode_get(const char *path, uint32_t *mode); + +/**@brief Change file owner and group. + * + * @param path Path to file/dir/link. + * @param uid User id. + * @param gid Group id. + * + * @return Standard error code.*/ +int ext4_owner_set(const char *path, uint32_t uid, uint32_t gid); + +/**@brief Get file/directory/link owner and group. + * + * @param path Path to file/dir/link. + * @param uid User id. + * @param gid Group id. + * + * @return Standard error code.*/ +int ext4_owner_get(const char *path, uint32_t *uid, uint32_t *gid); + +/**@brief Set file/directory/link access time. + * + * @param path Path to file/dir/link. + * @param atime Access timestamp. + * + * @return Standard error code.*/ +int ext4_atime_set(const char *path, uint32_t atime); + +/**@brief Set file/directory/link modify time. + * + * @param path Path to file/dir/link. + * @param mtime Modify timestamp. + * + * @return Standard error code.*/ +int ext4_mtime_set(const char *path, uint32_t mtime); + +/**@brief Set file/directory/link change time. + * + * @param path Path to file/dir/link. + * @param ctime Change timestamp. + * + * @return Standard error code.*/ +int ext4_ctime_set(const char *path, uint32_t ctime); + +/**@brief Get file/directory/link access time. + * + * @param path Path to file/dir/link. + * @param atime Access timestamp. + * + * @return Standard error code.*/ +int ext4_atime_get(const char *path, uint32_t *atime); + +/**@brief Get file/directory/link modify time. + * + * @param path Path to file/dir/link. + * @param mtime Modify timestamp. + * + * @return Standard error code.*/ +int ext4_mtime_get(const char *path, uint32_t *mtime); + +/**@brief Get file/directory/link change time. + * + * @param path Pathto file/dir/link. + * @param ctime Change timestamp. + * + * @return standard error code*/ +int ext4_ctime_get(const char *path, uint32_t *ctime); + +/**@brief Create symbolic link. + * + * @param target Destination entry path. + * @param path Source entry path. + * + * @return Standard error code.*/ +int ext4_fsymlink(const char *target, const char *path); + +/**@brief Create special file. + * @param path Path to new special file. + * @param filetype Filetype of the new special file. + * (that must not be regular file, directory, or unknown type) + * @param dev If filetype is char device or block device, + * the device number will become the payload in the inode. + * @return Standard error code.*/ +int ext4_mknod(const char *path, int filetype, uint32_t dev); + +/**@brief Read symbolic link payload. + * + * @param path Path to symlink. + * @param buf Output buffer. + * @param bufsize Output buffer max size. + * @param rcnt Bytes read. + * + * @return Standard error code.*/ +int ext4_readlink(const char *path, char *buf, size_t bufsize, size_t *rcnt); + +/**@brief Set extended attribute. + * + * @param path Path to file/directory + * @param name Name of the entry to add. + * @param name_len Length of @name in bytes. + * @param data Data of the entry to add. + * @param data_size Size of data to add. + * + * @return Standard error code.*/ +int ext4_setxattr(const char *path, const char *name, size_t name_len, + const void *data, size_t data_size); + +/**@brief Get extended attribute. + * + * @param path Path to file/directory. + * @param name Name of the entry to get. + * @param name_len Length of @name in bytes. + * @param buf Data of the entry to get. + * @param buf_size Size of data to get. + * + * @return Standard error code.*/ +int ext4_getxattr(const char *path, const char *name, size_t name_len, + void *buf, size_t buf_size, size_t *data_size); + +/**@brief List extended attributes. + * + * @param path Path to file/directory. + * @param list List to hold the name of entries. + * @param size Size of @list in bytes. + * @param ret_size Used bytes of @list. + * + * @return Standard error code.*/ +int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size); + +/**@brief Remove extended attribute. + * + * @param path Path to file/directory. + * @param name Name of the entry to remove. + * @param name_len Length of @name in bytes. + * + * @return Standard error code.*/ +int ext4_removexattr(const char *path, const char *name, size_t name_len); + + +/*********************************DIRECTORY OPERATION***********************/ + +/**@brief Recursive directory remove. + * + * @param path Directory path to remove + * + * @return Standard error code.*/ +int ext4_dir_rm(const char *path); + +/**@brief Rename/move directory. + * + * @param path Source path. + * @param new_path Destination path. + * + * @return Standard error code. */ +int ext4_dir_mv(const char *path, const char *new_path); + +/**@brief Create new directory. + * + * @param path Directory name. + * + * @return Standard error code.*/ +int ext4_dir_mk(const char *path); + +/**@brief Directory open. + * + * @param dir Directory handle. + * @param path Directory path. + * + * @return Standard error code.*/ +int ext4_dir_open(ext4_dir *dir, const char *path); + +/**@brief Directory close. + * + * @param dir directory handle. + * + * @return Standard error code.*/ +int ext4_dir_close(ext4_dir *dir); + +/**@brief Return next directory entry. + * + * @param dir Directory handle. + * + * @return Directory entry id (NULL if no entry)*/ +const ext4_direntry *ext4_dir_entry_next(ext4_dir *dir); + +/**@brief Rewine directory entry offset. + * + * @param dir Directory handle.*/ +void ext4_dir_entry_rewind(ext4_dir *dir); + + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_balloc.h b/lib/lwext4_rust/c/lwext4/include/ext4_balloc.h new file mode 100644 index 0000000..5f163d5 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_balloc.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_balloc.h + * @brief Physical block allocator. + */ + +#ifndef EXT4_BALLOC_H_ +#define EXT4_BALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +#include +#include + +/**@brief Compute number of block group from block address. + * @param s superblock pointer. + * @param baddr Absolute address of block. + * @return Block group index + */ +uint32_t ext4_balloc_get_bgid_of_block(struct ext4_sblock *s, + ext4_fsblk_t baddr); + +/**@brief Compute the starting block address of a block group + * @param s superblock pointer. + * @param bgid block group index + * @return Block address + */ +ext4_fsblk_t ext4_balloc_get_block_of_bgid(struct ext4_sblock *s, + uint32_t bgid); + +/**@brief Calculate and set checksum of block bitmap. + * @param sb superblock pointer. + * @param bg block group + * @param bitmap bitmap buffer + */ +void ext4_balloc_set_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap); + +/**@brief Free block from inode. + * @param inode_ref inode reference + * @param baddr block address + * @return standard error code*/ +int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr); + +/**@brief Free blocks from inode. + * @param inode_ref inode reference + * @param first block address + * @param count block count + * @return standard error code*/ +int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t first, uint32_t count); + +/**@brief Allocate block procedure. + * @param inode_ref inode reference + * @param baddr allocated block address + * @return standard error code*/ +int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *baddr); + +/**@brief Try allocate selected block. + * @param inode_ref inode reference + * @param baddr block address to allocate + * @param free if baddr is not allocated + * @return standard error code*/ +int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr, bool *free); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BALLOC_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_bcache.h b/lib/lwext4_rust/c/lwext4/include/ext4_bcache.h new file mode 100644 index 0000000..de12bd5 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_bcache.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bcache.h + * @brief Block cache allocator. + */ + +#ifndef EXT4_BCACHE_H_ +#define EXT4_BCACHE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include +#include + +#define EXT4_BLOCK_ZERO() \ + {.lb_id = 0, .data = 0} + +/**@brief Single block descriptor*/ +struct ext4_block { + /**@brief Logical block ID*/ + uint64_t lb_id; + + /**@brief Buffer */ + struct ext4_buf *buf; + + /**@brief Data buffer.*/ + uint8_t *data; +}; + +struct ext4_bcache; + +/**@brief Single block descriptor*/ +struct ext4_buf { + /**@brief Flags*/ + int flags; + + /**@brief Logical block address*/ + uint64_t lba; + + /**@brief Data buffer.*/ + uint8_t *data; + + /**@brief LRU priority. (unused) */ + uint32_t lru_prio; + + /**@brief LRU id.*/ + uint32_t lru_id; + + /**@brief Reference count table*/ + uint32_t refctr; + + /**@brief The block cache this buffer belongs to. */ + struct ext4_bcache *bc; + + /**@brief Whether or not buffer is on dirty list.*/ + bool on_dirty_list; + + /**@brief LBA tree node*/ + RB_ENTRY(ext4_buf) lba_node; + + /**@brief LRU tree node*/ + RB_ENTRY(ext4_buf) lru_node; + + /**@brief Dirty list node*/ + SLIST_ENTRY(ext4_buf) dirty_node; + + /**@brief Callback routine after a disk-write operation. + * @param bc block cache descriptor + * @param buf buffer descriptor + * @param standard error code returned by bdev->bwrite() + * @param arg argument passed to this routine*/ + void (*end_write)(struct ext4_bcache *bc, + struct ext4_buf *buf, + int res, + void *arg); + + /**@brief argument passed to end_write() callback.*/ + void *end_write_arg; +}; + +/**@brief Block cache descriptor*/ +struct ext4_bcache { + + /**@brief Item count in block cache*/ + uint32_t cnt; + + /**@brief Item size in block cache*/ + uint32_t itemsize; + + /**@brief Last recently used counter*/ + uint32_t lru_ctr; + + /**@brief Currently referenced datablocks*/ + uint32_t ref_blocks; + + /**@brief Maximum referenced datablocks*/ + uint32_t max_ref_blocks; + + /**@brief The blockdev binded to this block cache*/ + struct ext4_blockdev *bdev; + + /**@brief The cache should not be shaked */ + bool dont_shake; + + /**@brief A tree holding all bufs*/ + RB_HEAD(ext4_buf_lba, ext4_buf) lba_root; + + /**@brief A tree holding unreferenced bufs*/ + RB_HEAD(ext4_buf_lru, ext4_buf) lru_root; + + /**@brief A singly-linked list holding dirty buffers*/ + SLIST_HEAD(ext4_buf_dirty, ext4_buf) dirty_list; +}; + +/**@brief buffer state bits + * + * - BC♡UPTODATE: Buffer contains valid data. + * - BC_DIRTY: Buffer is dirty. + * - BC_FLUSH: Buffer will be immediately flushed, + * when no one references it. + * - BC_TMP: Buffer will be dropped once its refctr + * reaches zero. + */ +enum bcache_state_bits { + BC_UPTODATE, + BC_DIRTY, + BC_FLUSH, + BC_TMP +}; + +#define ext4_bcache_set_flag(buf, b) \ + (buf)->flags |= 1 << (b) + +#define ext4_bcache_clear_flag(buf, b) \ + (buf)->flags &= ~(1 << (b)) + +#define ext4_bcache_test_flag(buf, b) \ + (((buf)->flags & (1 << (b))) >> (b)) + +static inline void ext4_bcache_set_dirty(struct ext4_buf *buf) { + ext4_bcache_set_flag(buf, BC_UPTODATE); + ext4_bcache_set_flag(buf, BC_DIRTY); +} + +static inline void ext4_bcache_clear_dirty(struct ext4_buf *buf) { + ext4_bcache_clear_flag(buf, BC_UPTODATE); + ext4_bcache_clear_flag(buf, BC_DIRTY); +} + +/**@brief Increment reference counter of buf by 1.*/ +#define ext4_bcache_inc_ref(buf) ((buf)->refctr++) + +/**@brief Decrement reference counter of buf by 1.*/ +#define ext4_bcache_dec_ref(buf) ((buf)->refctr--) + +/**@brief Insert buffer to dirty cache list + * @param bc block cache descriptor + * @param buf buffer descriptor */ +static inline void +ext4_bcache_insert_dirty_node(struct ext4_bcache *bc, struct ext4_buf *buf) { + if (!buf->on_dirty_list) { + SLIST_INSERT_HEAD(&bc->dirty_list, buf, dirty_node); + buf->on_dirty_list = true; + } +} + +/**@brief Remove buffer to dirty cache list + * @param bc block cache descriptor + * @param buf buffer descriptor */ +static inline void +ext4_bcache_remove_dirty_node(struct ext4_bcache *bc, struct ext4_buf *buf) { + if (buf->on_dirty_list) { + SLIST_REMOVE(&bc->dirty_list, buf, ext4_buf, dirty_node); + buf->on_dirty_list = false; + } +} + + +/**@brief Dynamic initialization of block cache. + * @param bc block cache descriptor + * @param cnt items count in block cache + * @param itemsize single item size (in bytes) + * @return standard error code*/ +int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt, + uint32_t itemsize); + +/**@brief Do cleanup works on block cache. + * @param bc block cache descriptor.*/ +void ext4_bcache_cleanup(struct ext4_bcache *bc); + +/**@brief Dynamic de-initialization of block cache. + * @param bc block cache descriptor + * @return standard error code*/ +int ext4_bcache_fini_dynamic(struct ext4_bcache *bc); + +/**@brief Get a buffer with the lowest LRU counter in bcache. + * @param bc block cache descriptor + * @return buffer with the lowest LRU counter*/ +struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc); + +/**@brief Drop unreferenced buffer from bcache. + * @param bc block cache descriptor + * @param buf buffer*/ +void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf); + +/**@brief Invalidate a buffer. + * @param bc block cache descriptor + * @param buf buffer*/ +void ext4_bcache_invalidate_buf(struct ext4_bcache *bc, + struct ext4_buf *buf); + +/**@brief Invalidate a range of buffers. + * @param bc block cache descriptor + * @param from starting lba + * @param cnt block counts*/ +void ext4_bcache_invalidate_lba(struct ext4_bcache *bc, + uint64_t from, + uint32_t cnt); + +/**@brief Find existing buffer from block cache memory. + * Unreferenced block allocation is based on LRU + * (Last Recently Used) algorithm. + * @param bc block cache descriptor + * @param b block to alloc + * @param lba logical block address + * @return block cache buffer */ +struct ext4_buf * +ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b, + uint64_t lba); + +/**@brief Allocate block from block cache memory. + * Unreferenced block allocation is based on LRU + * (Last Recently Used) algorithm. + * @param bc block cache descriptor + * @param b block to alloc + * @param is_new block is new (needs to be read) + * @return standard error code*/ +int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b, + bool *is_new); + +/**@brief Free block from cache memory (decrement reference counter). + * @param bc block cache descriptor + * @param b block to free + * @return standard error code*/ +int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b); + +/**@brief Return a full status of block cache. + * @param bc block cache descriptor + * @return full status*/ +bool ext4_bcache_is_full(struct ext4_bcache *bc); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BCACHE_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_bitmap.h b/lib/lwext4_rust/c/lwext4/include/ext4_bitmap.h new file mode 100644 index 0000000..6bcb100 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_bitmap.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bitmap.h + * @brief Block/inode bitmap allocator. + */ + +#ifndef EXT4_BITMAP_H_ +#define EXT4_BITMAP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/**@brief Set bitmap bit. + * @param bmap bitmap + * @param bit bit to set*/ +static inline void ext4_bmap_bit_set(uint8_t *bmap, uint32_t bit) +{ + *(bmap + (bit >> 3)) |= (1 << (bit & 7)); +} + +/**@brief Clear bitmap bit. + * @param bmap bitmap buffer + * @param bit bit to clear*/ +static inline void ext4_bmap_bit_clr(uint8_t *bmap, uint32_t bit) +{ + *(bmap + (bit >> 3)) &= ~(1 << (bit & 7)); +} + +/**@brief Check if the bitmap bit is set. + * @param bmap bitmap buffer + * @param bit bit to check*/ +static inline bool ext4_bmap_is_bit_set(uint8_t *bmap, uint32_t bit) +{ + return (*(bmap + (bit >> 3)) & (1 << (bit & 7))); +} + +/**@brief Check if the bitmap bit is clear. + * @param bmap bitmap buffer + * @param bit bit to check*/ +static inline bool ext4_bmap_is_bit_clr(uint8_t *bmap, uint32_t bit) +{ + return !ext4_bmap_is_bit_set(bmap, bit); +} + +/**@brief Free range of bits in bitmap. + * @param bmap bitmap buffer + * @param sbit start bit + * @param bcnt bit count*/ +void ext4_bmap_bits_free(uint8_t *bmap, uint32_t sbit, uint32_t bcnt); + +/**@brief Find first clear bit in bitmap. + * @param sbit start bit of search + * @param ebit end bit of search + * @param bit_id output parameter (first free bit) + * @return standard error code*/ +int ext4_bmap_bit_find_clr(uint8_t *bmap, uint32_t sbit, uint32_t ebit, + uint32_t *bit_id); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BITMAP_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_block_group.h b/lib/lwext4_rust/c/lwext4/include/ext4_block_group.h new file mode 100644 index 0000000..a31d3e7 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_block_group.h @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_block_group.h + * @brief Block group function set. + */ + +#ifndef EXT4_BLOCK_GROUP_H_ +#define EXT4_BLOCK_GROUP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +/**@brief Get address of block with data block bitmap. + * @param bg pointer to block group + * @param s pointer to superblock + * @return Address of block with block bitmap + */ +static inline uint64_t ext4_bg_get_block_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint64_t v = to_le32(bg->block_bitmap_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->block_bitmap_hi) << 32; + + return v; +} + +/**@brief Set address of block with data block bitmap. + * @param bg pointer to block group + * @param s pointer to superblock + * @param blk block to set + */ +static inline void ext4_bg_set_block_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + + bg->block_bitmap_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->block_bitmap_hi = to_le32(blk >> 32); + +} + +/**@brief Get address of block with i-node bitmap. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Address of block with i-node bitmap + */ +static inline uint64_t ext4_bg_get_inode_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + + uint64_t v = to_le32(bg->inode_bitmap_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->inode_bitmap_hi) << 32; + + return v; +} + +/**@brief Set address of block with i-node bitmap. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param blk block to set + */ +static inline void ext4_bg_set_inode_bitmap(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + bg->inode_bitmap_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_bitmap_hi = to_le32(blk >> 32); + +} + + +/**@brief Get address of the first block of the i-node table. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Address of first block of i-node table + */ +static inline uint64_t +ext4_bg_get_inode_table_first_block(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint64_t v = to_le32(bg->inode_table_first_block_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint64_t)to_le32(bg->inode_table_first_block_hi) << 32; + + return v; +} + +/**@brief Set address of the first block of the i-node table. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param blk block to set + */ +static inline void +ext4_bg_set_inode_table_first_block(struct ext4_bgroup *bg, + struct ext4_sblock *s, uint64_t blk) +{ + bg->inode_table_first_block_lo = to_le32((uint32_t)blk); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_table_first_block_hi = to_le32(blk >> 32); +} + +/**@brief Get number of free blocks in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of free blocks in block group + */ +static inline uint32_t ext4_bg_get_free_blocks_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->free_blocks_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->free_blocks_count_hi) << 16; + + return v; +} + +/**@brief Set number of free blocks in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of free blocks in block group + */ +static inline void ext4_bg_set_free_blocks_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->free_blocks_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->free_blocks_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of free i-nodes in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of free i-nodes in block group + */ +static inline uint32_t ext4_bg_get_free_inodes_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->free_inodes_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->free_inodes_count_hi) << 16; + + return v; +} + +/**@brief Set number of free i-nodes in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of free i-nodes in block group + */ +static inline void ext4_bg_set_free_inodes_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->free_inodes_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->free_inodes_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of used directories in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of used directories in block group + */ +static inline uint32_t ext4_bg_get_used_dirs_count(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + uint32_t v = to_le16(bg->used_dirs_count_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->used_dirs_count_hi) << 16; + + return v; +} + +/**@brief Set number of used directories in block group. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of used directories in block group + */ +static inline void ext4_bg_set_used_dirs_count(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->used_dirs_count_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->used_dirs_count_hi = to_le16(cnt >> 16); +} + +/**@brief Get number of unused i-nodes. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @return Number of unused i-nodes + */ +static inline uint32_t ext4_bg_get_itable_unused(struct ext4_bgroup *bg, + struct ext4_sblock *s) +{ + + uint32_t v = to_le16(bg->itable_unused_lo); + + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + v |= (uint32_t)to_le16(bg->itable_unused_hi) << 16; + + return v; +} + +/**@brief Set number of unused i-nodes. + * @param bg Pointer to block group + * @param s Pointer to superblock + * @param cnt Number of unused i-nodes + */ +static inline void ext4_bg_set_itable_unused(struct ext4_bgroup *bg, + struct ext4_sblock *s, + uint32_t cnt) +{ + bg->itable_unused_lo = to_le16((cnt << 16) >> 16); + if (ext4_sb_get_desc_size(s) > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->itable_unused_hi = to_le16(cnt >> 16); +} + +/**@brief Set checksum of block group. + * @param bg Pointer to block group + * @param crc Cheksum of block group + */ +static inline void ext4_bg_set_checksum(struct ext4_bgroup *bg, uint16_t crc) +{ + bg->checksum = to_le16(crc); +} + +/**@brief Check if block group has a flag. + * @param bg Pointer to block group + * @param f Flag to be checked + * @return True if flag is set to 1 + */ +static inline bool ext4_bg_has_flag(struct ext4_bgroup *bg, uint32_t f) +{ + return to_le16(bg->flags) & f; +} + +/**@brief Set flag of block group. + * @param bg Pointer to block group + * @param f Flag to be set + */ +static inline void ext4_bg_set_flag(struct ext4_bgroup *bg, uint32_t f) +{ + uint16_t flags = to_le16(bg->flags); + flags |= f; + bg->flags = to_le16(flags); +} + +/**@brief Clear flag of block group. + * @param bg Pointer to block group + * @param f Flag to be cleared + */ +static inline void ext4_bg_clear_flag(struct ext4_bgroup *bg, uint32_t f) +{ + uint16_t flags = to_le16(bg->flags); + flags &= ~f; + bg->flags = to_le16(flags); +} + +/**@brief Calculate CRC16 of the block group. + * @param crc Init value + * @param buffer Input buffer + * @param len Sizeof input buffer + * @return Computed CRC16*/ +uint16_t ext4_bg_crc16(uint16_t crc, const uint8_t *buffer, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BLOCK_GROUP_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_blockdev.h b/lib/lwext4_rust/c/lwext4/include/ext4_blockdev.h new file mode 100644 index 0000000..668fb05 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_blockdev.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_blockdev.h + * @brief Block device module. + */ + +#ifndef EXT4_BLOCKDEV_H_ +#define EXT4_BLOCKDEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct ext4_blockdev_iface { + /**@brief Open device function + * @param bdev block device.*/ + int (*open)(struct ext4_blockdev *bdev); + + /**@brief Block read function. + * @param bdev block device + * @param buf output buffer + * @param blk_id block id + * @param blk_cnt block count*/ + int (*bread)(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt); + + /**@brief Block write function. + * @param buf input buffer + * @param blk_id block id + * @param blk_cnt block count*/ + int (*bwrite)(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt); + + /**@brief Close device function. + * @param bdev block device.*/ + int (*close)(struct ext4_blockdev *bdev); + + /**@brief Lock block device. Required in multi partition mode + * operations. Not mandatory field. + * @param bdev block device.*/ + int (*lock)(struct ext4_blockdev *bdev); + + /**@brief Unlock block device. Required in multi partition mode + * operations. Not mandatory field. + * @param bdev block device.*/ + int (*unlock)(struct ext4_blockdev *bdev); + + /**@brief Block size (bytes): physical*/ + uint32_t ph_bsize; + + /**@brief Block count: physical*/ + uint64_t ph_bcnt; + + /**@brief Block size buffer: physical*/ + uint8_t *ph_bbuf; + + /**@brief Reference counter to block device interface*/ + uint32_t ph_refctr; + + /**@brief Physical read counter*/ + uint32_t bread_ctr; + + /**@brief Physical write counter*/ + uint32_t bwrite_ctr; + + /**@brief User data pointer*/ + void* p_user; +}; + +/**@brief Definition of the simple block device.*/ +struct ext4_blockdev { + /**@brief Block device interface*/ + struct ext4_blockdev_iface *bdif; + + /**@brief Offset in bdif. For multi partition mode.*/ + uint64_t part_offset; + + /**@brief Part size in bdif. For multi partition mode.*/ + uint64_t part_size; + + /**@brief Block cache.*/ + struct ext4_bcache *bc; + + /**@brief Block size (bytes) logical*/ + uint32_t lg_bsize; + + /**@brief Block count: logical*/ + uint64_t lg_bcnt; + + /**@brief Cache write back mode reference counter*/ + uint32_t cache_write_back; + + /**@brief The filesystem this block device belongs to. */ + struct ext4_fs *fs; + + void *journal; +}; + +/**@brief Static initialization of the block device.*/ +#define EXT4_BLOCKDEV_STATIC_INSTANCE(__name, __bsize, __bcnt, __open, __bread,\ + __bwrite, __close, __lock, __unlock) \ + static uint8_t __name##_ph_bbuf[(__bsize)]; \ + static struct ext4_blockdev_iface __name##_iface = { \ + .open = __open, \ + .bread = __bread, \ + .bwrite = __bwrite, \ + .close = __close, \ + .lock = __lock, \ + .unlock = __unlock, \ + .ph_bsize = __bsize, \ + .ph_bcnt = __bcnt, \ + .ph_bbuf = __name##_ph_bbuf, \ + }; \ + static struct ext4_blockdev __name = { \ + .bdif = &__name##_iface, \ + .part_offset = 0, \ + .part_size = (__bcnt) * (__bsize), \ + } + +/**@brief Block device initialization. + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_init(struct ext4_blockdev *bdev); + +/**@brief Binds a bcache to block device. + * @param bdev block device descriptor + * @param bc block cache descriptor + * @return standard error code*/ +int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc); + +/**@brief Close block device + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_fini(struct ext4_blockdev *bdev); + +/**@brief Flush data in given buffer to disk. + * @param bdev block device descriptor + * @param buf buffer + * @return standard error code*/ +int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf); + +/**@brief Flush data in buffer of given lba to disk, + * if that buffer exists in block cache. + * @param bdev block device descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba); + +/**@brief Set logical block size in block device. + * @param bdev block device descriptor + * @param lb_bsize logical block size (in bytes)*/ +void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint32_t lb_bsize); + +/**@brief Block get function (through cache, don't read). + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba); + +/**@brief Block get function (through cache). + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba); + +/**@brief Block set procedure (through cache). + * @param bdev block device descriptor + * @param b block descriptor + * @return standard error code*/ +int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b); + +/**@brief Block read procedure (without cache) + * @param bdev block device descriptor + * @param buf output buffer + * @param lba logical block address + * @return standard error code*/ +int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba, + uint32_t cnt); + +/**@brief Block write procedure (without cache) + * @param bdev block device descriptor + * @param buf output buffer + * @param lba logical block address + * @return standard error code*/ +int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf, + uint64_t lba, uint32_t cnt); + +/**@brief Write to block device (by direct address). + * @param bdev block device descriptor + * @param off byte offset in block device + * @param buf input buffer + * @param len length of the write buffer + * @return standard error code*/ +int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off, + const void *buf, uint32_t len); + +/**@brief Read freom block device (by direct address). + * @param bdev block device descriptor + * @param off byte offset in block device + * @param buf input buffer + * @param len length of the write buffer + * @return standard error code*/ +int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf, + uint32_t len); + +/**@brief Flush all dirty buffers to disk + * @param bdev block device descriptor + * @return standard error code*/ +int ext4_block_cache_flush(struct ext4_blockdev *bdev); + +/**@brief Enable/disable write back cache mode + * @param bdev block device descriptor + * @param on_off + * !0 - ENABLE + * 0 - DISABLE (all delayed cache buffers will be flushed) + * @return standard error code*/ +int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_BLOCKDEV_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_config.h b/lib/lwext4_rust/c/lwext4/include/ext4_config.h new file mode 100644 index 0000000..72dcdc3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_config.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_config.h + * @brief Configuration file. + */ + +#ifndef EXT4_CONFIG_H_ +#define EXT4_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if !CONFIG_USE_DEFAULT_CFG +#include "generated/ext4_config.h" +#endif + +/*****************************************************************************/ + +#define F_SET_EXT2 2 +#define F_SET_EXT3 3 +#define F_SET_EXT4 4 + +#ifndef CONFIG_EXT_FEATURE_SET_LVL +#define CONFIG_EXT_FEATURE_SET_LVL F_SET_EXT4 +#endif + +/*****************************************************************************/ + +#if CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT2 +/*Superblock features flag EXT2*/ +#define CONFIG_SUPPORTED_FCOM EXT2_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT2_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT2_SUPPORTED_FRO_COM + +#elif CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT3 +/*Superblock features flag EXT3*/ +#define CONFIG_SUPPORTED_FCOM EXT3_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT3_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT3_SUPPORTED_FRO_COM +#elif CONFIG_EXT_FEATURE_SET_LVL == F_SET_EXT4 +/*Superblock features flag EXT4*/ +#define CONFIG_SUPPORTED_FCOM EXT4_SUPPORTED_FCOM +#define CONFIG_SUPPORTED_FINCOM (EXT4_SUPPORTED_FINCOM | EXT_FINCOM_IGNORED) +#define CONFIG_SUPPORTED_FRO_COM EXT4_SUPPORTED_FRO_COM +#else +#define "Unsupported CONFIG_EXT_FEATURE_SET_LVL" +#endif + +#define CONFIG_DIR_INDEX_ENABLE (CONFIG_SUPPORTED_FCOM & EXT4_FCOM_DIR_INDEX) +#define CONFIG_EXTENT_ENABLE (CONFIG_SUPPORTED_FINCOM & EXT4_FINCOM_EXTENTS) +#define CONFIG_META_CSUM_ENABLE (CONFIG_SUPPORTED_FRO_COM & EXT4_FRO_COM_METADATA_CSUM) + +/*****************************************************************************/ + +/**@brief Enable/disable journaling*/ +#ifndef CONFIG_JOURNALING_ENABLE +#define CONFIG_JOURNALING_ENABLE 1 +#endif + +/**@brief Enable/disable xattr*/ +#ifndef CONFIG_XATTR_ENABLE +#define CONFIG_XATTR_ENABLE 1 +#endif + +/**@brief Enable/disable extents*/ +#ifndef CONFIG_EXTENTS_ENABLE +#define CONFIG_EXTENTS_ENABLE 1 +#endif + +/**@brief Include error codes from ext4_errno or standard library.*/ +#ifndef CONFIG_HAVE_OWN_ERRNO +#define CONFIG_HAVE_OWN_ERRNO 0 +#endif + +/**@brief Debug printf enable (stdout)*/ +#ifndef CONFIG_DEBUG_PRINTF +#define CONFIG_DEBUG_PRINTF 1 +#endif + +/**@brief Assert printf enable (stdout)*/ +#ifndef CONFIG_DEBUG_ASSERT +#define CONFIG_DEBUG_ASSERT 1 +#endif + +/**@brief Include assert codes from ext4_debug or standard library.*/ +#ifndef CONFIG_HAVE_OWN_ASSERT +#define CONFIG_HAVE_OWN_ASSERT 1 +#endif + +/**@brief Statistics of block device*/ +#ifndef CONFIG_BLOCK_DEV_ENABLE_STATS +#define CONFIG_BLOCK_DEV_ENABLE_STATS 1 +#endif + +/**@brief Cache size of block device.*/ +#ifndef CONFIG_BLOCK_DEV_CACHE_SIZE +#define CONFIG_BLOCK_DEV_CACHE_SIZE 8 +#endif + + +/**@brief Maximum block device name*/ +#ifndef CONFIG_EXT4_MAX_BLOCKDEV_NAME +#define CONFIG_EXT4_MAX_BLOCKDEV_NAME 32 +#endif + + +/**@brief Maximum block device count*/ +#ifndef CONFIG_EXT4_BLOCKDEVS_COUNT +#define CONFIG_EXT4_BLOCKDEVS_COUNT 2 +#endif + +/**@brief Maximum mountpoint name*/ +#ifndef CONFIG_EXT4_MAX_MP_NAME +#define CONFIG_EXT4_MAX_MP_NAME 32 +#endif + +/**@brief Maximum mountpoint count*/ +#ifndef CONFIG_EXT4_MOUNTPOINTS_COUNT +#define CONFIG_EXT4_MOUNTPOINTS_COUNT 2 +#endif + +/**@brief Include open flags from ext4_errno or standard library.*/ +#ifndef CONFIG_HAVE_OWN_OFLAGS +#define CONFIG_HAVE_OWN_OFLAGS 1 +#endif + +/**@brief Maximum single truncate size. Transactions must be limited to reduce + * number of allocetions for single transaction*/ +#ifndef CONFIG_MAX_TRUNCATE_SIZE +#define CONFIG_MAX_TRUNCATE_SIZE (16ul * 1024ul * 1024ul) +#endif + + +/**@brief Unaligned access switch on/off*/ +#ifndef CONFIG_UNALIGNED_ACCESS +#define CONFIG_UNALIGNED_ACCESS 0 +#endif + +/**@brief Switches use of malloc/free functions family + * from standard library to user provided*/ +#ifndef CONFIG_USE_USER_MALLOC +#define CONFIG_USE_USER_MALLOC 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_CONFIG_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_crc32.h b/lib/lwext4_rust/c/lwext4/include/ext4_crc32.h new file mode 100644 index 0000000..5f701fb --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_crc32.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Based on FreeBSD. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_crc32.h + * @brief Crc32c routine. Taken from FreeBSD kernel. + */ + +#ifndef LWEXT4_EXT4_CRC32C_H_ +#define LWEXT4_EXT4_CRC32C_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +/**@brief CRC32 algorithm. + * @param crc input feed + * @param buf input buffer + * @param size input buffer length (bytes) + * @return updated crc32 value*/ +uint32_t ext4_crc32(uint32_t crc, const void *buf, uint32_t size); + +/**@brief CRC32C algorithm. + * @param crc input feed + * @param buf input buffer + * @param size input buffer length (bytes) + * @return updated crc32c value*/ +uint32_t ext4_crc32c(uint32_t crc, const void *buf, uint32_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* LWEXT4_EXT4_CRC32C_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_debug.h b/lib/lwext4_rust/c/lwext4/include/ext4_debug.h new file mode 100644 index 0000000..c558e20 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_debug.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_debug.c + * @brief Debug printf and assert macros. + */ + +#ifndef EXT4_DEBUG_H_ +#define EXT4_DEBUG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if !CONFIG_HAVE_OWN_ASSERT +#include +#endif + +#include +#include + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif + +#ifndef PRId64 +#define PRId64 "lld" +#endif + + +#define DEBUG_BALLOC (1ul << 0) +#define DEBUG_BCACHE (1ul << 1) +#define DEBUG_BITMAP (1ul << 2) +#define DEBUG_BLOCK_GROUP (1ul << 3) +#define DEBUG_BLOCKDEV (1ul << 4) +#define DEBUG_DIR_IDX (1ul << 5) +#define DEBUG_DIR (1ul << 6) +#define DEBUG_EXTENT (1ul << 7) +#define DEBUG_FS (1ul << 8) +#define DEBUG_HASH (1ul << 9) +#define DEBUG_IALLOC (1ul << 10) +#define DEBUG_INODE (1ul << 11) +#define DEBUG_SUPER (1ul << 12) +#define DEBUG_XATTR (1ul << 13) +#define DEBUG_MKFS (1ul << 14) +#define DEBUG_EXT4 (1ul << 15) +#define DEBUG_JBD (1ul << 16) +#define DEBUG_MBR (1ul << 17) + +#define DEBUG_NOPREFIX (1ul << 31) +#define DEBUG_ALL (0xFFFFFFFF) + +static inline const char *ext4_dmask_id2str(uint32_t m) +{ + switch(m) { + case DEBUG_BALLOC: + return "ext4_balloc: "; + case DEBUG_BCACHE: + return "ext4_bcache: "; + case DEBUG_BITMAP: + return "ext4_bitmap: "; + case DEBUG_BLOCK_GROUP: + return "ext4_block_group: "; + case DEBUG_BLOCKDEV: + return "ext4_blockdev: "; + case DEBUG_DIR_IDX: + return "ext4_dir_idx: "; + case DEBUG_DIR: + return "ext4_dir: "; + case DEBUG_EXTENT: + return "ext4_extent: "; + case DEBUG_FS: + return "ext4_fs: "; + case DEBUG_HASH: + return "ext4_hash: "; + case DEBUG_IALLOC: + return "ext4_ialloc: "; + case DEBUG_INODE: + return "ext4_inode: "; + case DEBUG_SUPER: + return "ext4_super: "; + case DEBUG_XATTR: + return "ext4_xattr: "; + case DEBUG_MKFS: + return "ext4_mkfs: "; + case DEBUG_JBD: + return "ext4_jbd: "; + case DEBUG_MBR: + return "ext4_mbr: "; + case DEBUG_EXT4: + return "ext4: "; + } + return ""; +} +#define DBG_NONE "" +#define DBG_INFO "[info] " +#define DBG_WARN "[warn] " +#define DBG_ERROR "[error] " + +/**@brief Global mask debug set. + * @param m new debug mask.*/ +void ext4_dmask_set(uint32_t m); + +/**@brief Global mask debug clear. + * @param m new debug mask.*/ +void ext4_dmask_clr(uint32_t m); + +/**@brief Global debug mask get. + * @return debug mask*/ +uint32_t ext4_dmask_get(void); + +#if CONFIG_DEBUG_PRINTF +#include + +/**@brief Debug printf.*/ +#define ext4_dbg(m, ...) \ + do { \ + if ((m) & ext4_dmask_get()) { \ + if (!((m) & DEBUG_NOPREFIX)) { \ + printf("%s", ext4_dmask_id2str(m)); \ + printf("l: %d ", __LINE__); \ + } \ + printf(__VA_ARGS__); \ + fflush(stdout); \ + } \ + } while (0) +#else +#define ext4_dbg(m, ...) do { } while (0) +#endif + +#if CONFIG_DEBUG_ASSERT +/**@brief Debug assertion.*/ +#if CONFIG_HAVE_OWN_ASSERT +#include + +#define ext4_assert(_v) \ + do { \ + if (!(_v)) { \ + printf("assertion failed:\nfile: %s\nline: %d\n", \ + __FILE__, __LINE__); \ + while (1) \ + ; \ + } \ + } while (0) +#else +#define ext4_assert(_v) assert(_v) +#endif +#else +#define ext4_assert(_v) ((void)(_v)) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DEBUG_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_dir.h b/lib/lwext4_rust/c/lwext4/include/ext4_dir.h new file mode 100644 index 0000000..d0d13a2 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_dir.h @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir.h + * @brief Directory handle procedures. + */ + +#ifndef EXT4_DIR_H_ +#define EXT4_DIR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + +#include + +struct ext4_dir_iter { + struct ext4_inode_ref *inode_ref; + struct ext4_block curr_blk; + uint64_t curr_off; + struct ext4_dir_en *curr; +}; + +struct ext4_dir_search_result { + struct ext4_block block; + struct ext4_dir_en *dentry; +}; + + +/**@brief Get i-node number from directory entry. + * @param de Directory entry + * @return I-node number + */ +static inline uint32_t +ext4_dir_en_get_inode(struct ext4_dir_en *de) +{ + return to_le32(de->inode); +} + +/**@brief Set i-node number to directory entry. + * @param de Directory entry + * @param inode I-node number + */ +static inline void +ext4_dir_en_set_inode(struct ext4_dir_en *de, uint32_t inode) +{ + de->inode = to_le32(inode); +} + +/**@brief Set i-node number to directory entry. (For HTree root) + * @param de Directory entry + * @param inode I-node number + */ +static inline void +ext4_dx_dot_en_set_inode(struct ext4_dir_idx_dot_en *de, uint32_t inode) +{ + de->inode = to_le32(inode); +} + +/**@brief Get directory entry length. + * @param de Directory entry + * @return Entry length + */ +static inline uint16_t ext4_dir_en_get_entry_len(struct ext4_dir_en *de) +{ + return to_le16(de->entry_len); +} + +/**@brief Set directory entry length. + * @param de Directory entry + * @param l Entry length + */ +static inline void ext4_dir_en_set_entry_len(struct ext4_dir_en *de, uint16_t l) +{ + de->entry_len = to_le16(l); +} + +/**@brief Get directory entry name length. + * @param sb Superblock + * @param de Directory entry + * @return Entry name length + */ +static inline uint16_t ext4_dir_en_get_name_len(struct ext4_sblock *sb, + struct ext4_dir_en *de) +{ + uint16_t v = de->name_len; + + if ((ext4_get32(sb, rev_level) == 0) && + (ext4_get32(sb, minor_rev_level) < 5)) + v |= ((uint16_t)de->in.name_length_high) << 8; + + return v; +} + +/**@brief Set directory entry name length. + * @param sb Superblock + * @param de Directory entry + * @param len Entry name length + */ +static inline void ext4_dir_en_set_name_len(struct ext4_sblock *sb, + struct ext4_dir_en *de, + uint16_t len) +{ + de->name_len = (len << 8) >> 8; + + if ((ext4_get32(sb, rev_level) == 0) && + (ext4_get32(sb, minor_rev_level) < 5)) + de->in.name_length_high = len >> 8; +} + +/**@brief Get i-node type of directory entry. + * @param sb Superblock + * @param de Directory entry + * @return I-node type (file, dir, etc.) + */ +static inline uint8_t ext4_dir_en_get_inode_type(struct ext4_sblock *sb, + struct ext4_dir_en *de) +{ + if ((ext4_get32(sb, rev_level) > 0) || + (ext4_get32(sb, minor_rev_level) >= 5)) + return de->in.inode_type; + + return EXT4_DE_UNKNOWN; +} +/**@brief Set i-node type of directory entry. + * @param sb Superblock + * @param de Directory entry + * @param t I-node type (file, dir, etc.) + */ + +static inline void ext4_dir_en_set_inode_type(struct ext4_sblock *sb, + struct ext4_dir_en *de, uint8_t t) +{ + if ((ext4_get32(sb, rev_level) > 0) || + (ext4_get32(sb, minor_rev_level) >= 5)) + de->in.inode_type = t; +} + +/**@brief Verify checksum of a linear directory leaf block + * @param inode_ref Directory i-node + * @param dirent Linear directory leaf block + * @return true means the block passed checksum verification + */ +bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent); + +/**@brief Initialize directory iterator. + * Set position to the first valid entry from the required position. + * @param it Pointer to iterator to be initialized + * @param inode_ref Directory i-node + * @param pos Position to start reading entries from + * @return Error code + */ +int ext4_dir_iterator_init(struct ext4_dir_iter *it, + struct ext4_inode_ref *inode_ref, uint64_t pos); + +/**@brief Jump to the next valid entry + * @param it Initialized iterator + * @return Error code + */ +int ext4_dir_iterator_next(struct ext4_dir_iter *it); + +/**@brief Uninitialize directory iterator. + * Release all allocated structures. + * @param it Iterator to be finished + * @return Error code + */ +int ext4_dir_iterator_fini(struct ext4_dir_iter *it); + +/**@brief Write directory entry to concrete data block. + * @param sb Superblock + * @param en Pointer to entry to be written + * @param entry_len Length of new entry + * @param child Child i-node to be written to new entry + * @param name Name of the new entry + * @param name_len Length of entry name + */ +void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, + uint16_t entry_len, struct ext4_inode_ref *child, + const char *name, size_t name_len); + +/**@brief Add new entry to the directory. + * @param parent Directory i-node + * @param name Name of new entry + * @param child I-node to be referenced from new entry + * @return Error code + */ +int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len, struct ext4_inode_ref *child); + +/**@brief Find directory entry with passed name. + * @param result Result structure to be returned if entry found + * @param parent Directory i-node + * @param name Name of entry to be found + * @param name_len Name length + * @return Error code + */ +int ext4_dir_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *parent, const char *name, + uint32_t name_len); + +/**@brief Remove directory entry. + * @param parent Directory i-node + * @param name Name of the entry to be removed + * @param name_len Name length + * @return Error code + */ +int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len); + +/**@brief Try to insert entry to concrete data block. + * @param sb Superblock + * @param inode_ref Directory i-node + * @param dst_blk Block to try to insert entry to + * @param child Child i-node to be inserted by new entry + * @param name Name of the new entry + * @param name_len Length of the new entry name + * @return Error code + */ +int ext4_dir_try_insert_entry(struct ext4_sblock *sb, + struct ext4_inode_ref *inode_ref, + struct ext4_block *dst_blk, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len); + +/**@brief Try to find entry in block by name. + * @param block Block containing entries + * @param sb Superblock + * @param name_len Length of entry name + * @param name Name of entry to be found + * @param res_entry Output pointer to found entry, NULL if not found + * @return Error code + */ +int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, + size_t name_len, const char *name, + struct ext4_dir_en **res_entry); + +/**@brief Simple function to release allocated data from result. + * @param parent Parent inode + * @param result Search result to destroy + * @return Error code + * + */ +int ext4_dir_destroy_result(struct ext4_inode_ref *parent, + struct ext4_dir_search_result *result); + +void ext4_dir_set_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent); + + +void ext4_dir_init_entry_tail(struct ext4_dir_entry_tail *t); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DIR_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_dir_idx.h b/lib/lwext4_rust/c/lwext4/include/ext4_dir_idx.h new file mode 100644 index 0000000..e4a7abe --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_dir_idx.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir_idx.h + * @brief Directory indexing procedures. + */ + +#ifndef EXT4_DIR_IDX_H_ +#define EXT4_DIR_IDX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +#include +#include + +struct ext4_dir_idx_block { + struct ext4_block b; + struct ext4_dir_idx_entry *entries; + struct ext4_dir_idx_entry *position; +}; + +#define EXT4_DIR_DX_INIT_BCNT 2 + + +/**@brief Initialize index structure of new directory. + * @param dir Pointer to directory i-node + * @param parent Pointer to parent directory i-node + * @return Error code + */ +int ext4_dir_dx_init(struct ext4_inode_ref *dir, + struct ext4_inode_ref *parent); + +/**@brief Try to find directory entry using directory index. + * @param result Output value - if entry will be found, + * than will be passed through this parameter + * @param inode_ref Directory i-node + * @param name_len Length of name to be found + * @param name Name to be found + * @return Error code + */ +int ext4_dir_dx_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *inode_ref, size_t name_len, + const char *name); + +/**@brief Add new entry to indexed directory + * @param parent Directory i-node + * @param child I-node to be referenced from directory entry + * @param name Name of new directory entry + * @return Error code + */ +int ext4_dir_dx_add_entry(struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, uint32_t name_len); + +/**@brief Add new entry to indexed directory + * @param dir Directory i-node + * @param parent_inode parent inode index + * @return Error code + */ +int ext4_dir_dx_reset_parent_inode(struct ext4_inode_ref *dir, + uint32_t parent_inode); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_DIR_IDX_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_errno.h b/lib/lwext4_rust/c/lwext4/include/ext4_errno.h new file mode 100644 index 0000000..2d92280 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_errno.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_errno.h + * @brief Error codes. + */ +#ifndef EXT4_ERRNO_H_ +#define EXT4_ERRNO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if !CONFIG_HAVE_OWN_ERRNO +#include +#else +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define EEXIST 17 /* File exists */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define ERANGE 34 /* Math result not representable */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ENODATA 61 /* No data available */ +#define ENOTSUP 95 /* Not supported */ +#endif + +#ifndef ENODATA + #ifdef ENOATTR + #define ENODATA ENOATTR + #else + #define ENODATA 61 + #endif +#endif + +#ifndef ENOTSUP +#define ENOTSUP 95 +#endif + +#ifndef EOK +#define EOK 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_ERRNO_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_extent.h b/lib/lwext4_rust/c/lwext4/include/ext4_extent.h new file mode 100644 index 0000000..fee0926 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_extent.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_extent.h + * @brief More complex filesystem functions. + */ +#ifndef EXT4_EXTENT_H_ +#define EXT4_EXTENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +void ext4_extent_tree_init(struct ext4_inode_ref *inode_ref); + + +int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_lblk_t iblock, + uint32_t max_blocks, ext4_fsblk_t *result, bool create, + uint32_t *blocks_count); + + +/**@brief Release all data blocks starting from specified logical block. + * @param inode_ref I-node to release blocks from + * @param iblock_from First logical block to release + * @return Error code */ +int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from, + ext4_lblk_t to); + + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_EXTENT_H_ */ +/** +* @} +*/ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_fs.h b/lib/lwext4_rust/c/lwext4/include/ext4_fs.h new file mode 100644 index 0000000..b76be6c --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_fs.h @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_fs.c + * @brief More complex filesystem functions. + */ + +#ifndef EXT4_FS_H_ +#define EXT4_FS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +struct ext4_fs { + bool read_only; + + struct ext4_blockdev *bdev; + struct ext4_sblock sb; + + uint64_t inode_block_limits[4]; + uint64_t inode_blocks_per_level[4]; + + uint32_t last_inode_bg_id; + + struct jbd_fs *jbd_fs; + struct jbd_journal *jbd_journal; + struct jbd_trans *curr_trans; +}; + +struct ext4_block_group_ref { + struct ext4_block block; + struct ext4_bgroup *block_group; + struct ext4_fs *fs; + uint32_t index; + bool dirty; +}; + +struct ext4_inode_ref { + struct ext4_block block; + struct ext4_inode *inode; + struct ext4_fs *fs; + uint32_t index; + bool dirty; +}; + + +/**@brief Convert block address to relative index in block group. + * @param s Superblock pointer + * @param baddr Block number to convert + * @return Relative number of block + */ +static inline uint32_t ext4_fs_addr_to_idx_bg(struct ext4_sblock *s, + ext4_fsblk_t baddr) +{ + if (ext4_get32(s, first_data_block) && baddr) + baddr--; + + return baddr % ext4_get32(s, blocks_per_group); +} + +/**@brief Convert relative block address in group to absolute address. + * @param s Superblock pointer + * @param index Relative block address + * @param bgid Block group + * @return Absolute block address + */ +static inline ext4_fsblk_t ext4_fs_bg_idx_to_addr(struct ext4_sblock *s, + uint32_t index, + uint32_t bgid) +{ + if (ext4_get32(s, first_data_block)) + index++; + + return ext4_get32(s, blocks_per_group) * bgid + index; +} + +/**@brief TODO: */ +static inline ext4_fsblk_t ext4_fs_first_bg_block_no(struct ext4_sblock *s, + uint32_t bgid) +{ + return (uint64_t)bgid * ext4_get32(s, blocks_per_group) + + ext4_get32(s, first_data_block); +} + +/**@brief Initialize filesystem and read all needed data. + * @param fs Filesystem instance to be initialized + * @param bdev Identifier if device with the filesystem + * @param read_only Mark the filesystem as read-only. + * @return Error code + */ +int ext4_fs_init(struct ext4_fs *fs, struct ext4_blockdev *bdev, + bool read_only); + +/**@brief Destroy filesystem instance (used by unmount operation). + * @param fs Filesystem to be destroyed + * @return Error code + */ +int ext4_fs_fini(struct ext4_fs *fs); + +/**@brief Check filesystem's features, if supported by this driver + * Function can return EOK and set read_only flag. It mean's that + * there are some not-supported features, that can cause problems + * during some write operations. + * @param fs Filesystem to be checked + * @param read_only Flag if filesystem should be mounted only for reading + * @return Error code + */ +int ext4_fs_check_features(struct ext4_fs *fs, bool *read_only); + +/**@brief Get reference to block group specified by index. + * @param fs Filesystem to find block group on + * @param bgid Index of block group to load + * @param ref Output pointer for reference + * @return Error code + */ +int ext4_fs_get_block_group_ref(struct ext4_fs *fs, uint32_t bgid, + struct ext4_block_group_ref *ref); + +/**@brief Put reference to block group. + * @param ref Pointer for reference to be put back + * @return Error code + */ +int ext4_fs_put_block_group_ref(struct ext4_block_group_ref *ref); + +/**@brief Get reference to i-node specified by index. + * @param fs Filesystem to find i-node on + * @param index Index of i-node to load + * @param ref Output pointer for reference + * @return Error code + */ +int ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref); + +/**@brief Reset blocks field of i-node. + * @param fs Filesystem to reset blocks field of i-inode on + * @param inode_ref ref Pointer for inode to be operated on + */ +void ext4_fs_inode_blocks_init(struct ext4_fs *fs, + struct ext4_inode_ref *inode_ref); + +/**@brief Put reference to i-node. + * @param ref Pointer for reference to be put back + * @return Error code + */ +int ext4_fs_put_inode_ref(struct ext4_inode_ref *ref); + +/**@brief Convert filetype to inode mode. + * @param filetype File type + * @return inode mode + */ +uint32_t ext4_fs_correspond_inode_mode(int filetype); + +/**@brief Allocate new i-node in the filesystem. + * @param fs Filesystem to allocated i-node on + * @param inode_ref Output pointer to return reference to allocated i-node + * @param filetype File type of newly created i-node + * @return Error code + */ +int ext4_fs_alloc_inode(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + int filetype); + +/**@brief Release i-node and mark it as free. + * @param inode_ref I-node to be released + * @return Error code + */ +int ext4_fs_free_inode(struct ext4_inode_ref *inode_ref); + +/**@brief Truncate i-node data blocks. + * @param inode_ref I-node to be truncated + * @param new_size New size of inode (must be < current size) + * @return Error code + */ +int ext4_fs_truncate_inode(struct ext4_inode_ref *inode_ref, uint64_t new_size); + +/**@brief Compute 'goal' for inode index + * @param inode_ref Reference to inode, to allocate block for + * @return goal + */ +ext4_fsblk_t ext4_fs_inode_to_goal_block(struct ext4_inode_ref *inode_ref); + +/**@brief Compute 'goal' for allocation algorithm (For blockmap). + * @param inode_ref Reference to inode, to allocate block for + * @return error code + */ +int ext4_fs_indirect_find_goal(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *goal); + +/**@brief Get physical block address by logical index of the block. + * @param inode_ref I-node to read block address from + * @param iblock Logical index of block + * @param fblock Output pointer for return physical + * block address + * @param support_unwritten Indicate whether unwritten block range + * is supported under the current context + * @return Error code + */ +int ext4_fs_get_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool support_unwritten); + +/**@brief Initialize a part of unwritten range of the inode. + * @param inode_ref I-node to proceed on. + * @param iblock Logical index of block + * @param fblock Output pointer for return physical block address + * @return Error code + */ +int ext4_fs_init_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock); + +/**@brief Append following logical block to the i-node. + * @param inode_ref I-node to append block to + * @param fblock Output physical block address of newly allocated block + * @param iblock Output logical number of newly allocated block + * @return Error code + */ +int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *fblock, ext4_lblk_t *iblock); + +/**@brief Increment inode link count. + * @param inode_ref none handle + */ +void ext4_fs_inode_links_count_inc(struct ext4_inode_ref *inode_ref); + +/**@brief Decrement inode link count. + * @param inode_ref none handle + */ +void ext4_fs_inode_links_count_dec(struct ext4_inode_ref *inode_ref); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_FS_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_hash.h b/lib/lwext4_rust/c/lwext4/include/ext4_hash.h new file mode 100644 index 0000000..15c2b94 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_hash.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_hash.h + * @brief Directory indexing hash functions. + */ + +#ifndef EXT4_HASH_H_ +#define EXT4_HASH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +struct ext4_hash_info { + uint32_t hash; + uint32_t minor_hash; + uint32_t hash_version; + const uint32_t *seed; +}; + + +/**@brief Directory entry name hash function. + * @param name entry name + * @param len entry name length + * @param hash_seed (from superblock) + * @param hash_version version (from superblock) + * @param hash_minor output value + * @param hash_major output value + * @return standard error code*/ +int ext2_htree_hash(const char *name, int len, const uint32_t *hash_seed, + int hash_version, uint32_t *hash_major, + uint32_t *hash_minor); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_HASH_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_ialloc.h b/lib/lwext4_rust/c/lwext4/include/ext4_ialloc.h new file mode 100644 index 0000000..e845c79 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_ialloc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_ialloc.c + * @brief Inode allocation procedures. + */ + +#ifndef EXT4_IALLOC_H_ +#define EXT4_IALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/**@brief Calculate and set checksum of inode bitmap. + * @param sb superblock pointer. + * @param bg block group + * @param bitmap bitmap buffer + */ +void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap); + +/**@brief Free i-node number and modify filesystem data structers. + * @param fs Filesystem, where the i-node is located + * @param index Index of i-node to be release + * @param is_dir Flag us for information whether i-node is directory or not + */ +int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir); + +/**@brief I-node allocation algorithm. + * This is more simple algorithm, than Orlov allocator used + * in the Linux kernel. + * @param fs Filesystem to allocate i-node on + * @param index Output value - allocated i-node number + * @param is_dir Flag if allocated i-node will be file or directory + * @return Error code + */ +int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *index, bool is_dir); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_IALLOC_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_inode.h b/lib/lwext4_rust/c/lwext4/include/ext4_inode.h new file mode 100644 index 0000000..11fd1d9 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_inode.h @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_inode.h + * @brief Inode handle functions + */ + +#ifndef EXT4_INODE_H_ +#define EXT4_INODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +/**@brief Get mode of the i-node. + * @param sb Superblock + * @param inode I-node to load mode from + * @return Mode of the i-node + */ +uint32_t ext4_inode_get_mode(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Set mode of the i-node. + * @param sb Superblock + * @param inode I-node to set mode to + * @param mode Mode to set to i-node + */ +void ext4_inode_set_mode(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t mode); + +/**@brief Get ID of the i-node owner (user id). + * @param inode I-node to load uid from + * @return User ID of the i-node owner + */ +uint32_t ext4_inode_get_uid(struct ext4_inode *inode); + +/**@brief Set ID of the i-node owner. + * @param inode I-node to set uid to + * @param uid ID of the i-node owner + */ +void ext4_inode_set_uid(struct ext4_inode *inode, uint32_t uid); + +/**@brief Get real i-node size. + * @param sb Superblock + * @param inode I-node to load size from + * @return Real size of i-node + */ +uint64_t ext4_inode_get_size(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Set real i-node size. + * @param inode I-node to set size to + * @param size Size of the i-node + */ +void ext4_inode_set_size(struct ext4_inode *inode, uint64_t size); + +/**@brief Get time, when i-node was last accessed. + * @param inode I-node + * @return Time of the last access (POSIX) + */ +uint32_t ext4_inode_get_access_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was last accessed. + * @param inode I-node + * @param time Time of the last access (POSIX) + */ +void ext4_inode_set_access_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node was last changed. + * @param inode I-node + * @return Time of the last change (POSIX) + */ +uint32_t ext4_inode_get_change_inode_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was last changed. + * @param inode I-node + * @param time Time of the last change (POSIX) + */ +void ext4_inode_set_change_inode_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node content was last modified. + * @param inode I-node + * @return Time of the last content modification (POSIX) + */ +uint32_t ext4_inode_get_modif_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node content was last modified. + * @param inode I-node + * @param time Time of the last content modification (POSIX) + */ +void ext4_inode_set_modif_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get time, when i-node was deleted. + * @param inode I-node + * @return Time of the delete action (POSIX) + */ +uint32_t ext4_inode_get_del_time(struct ext4_inode *inode); + +/**@brief Set time, when i-node was deleted. + * @param inode I-node + * @param time Time of the delete action (POSIX) + */ +void ext4_inode_set_del_time(struct ext4_inode *inode, uint32_t time); + +/**@brief Get ID of the i-node owner's group. + * @param inode I-node to load gid from + * @return Group ID of the i-node owner + */ +uint32_t ext4_inode_get_gid(struct ext4_inode *inode); + +/**@brief Set ID to the i-node owner's group. + * @param inode I-node to set gid to + * @param gid Group ID of the i-node owner + */ +void ext4_inode_set_gid(struct ext4_inode *inode, uint32_t gid); + +/**@brief Get number of links to i-node. + * @param inode I-node to load number of links from + * @return Number of links to i-node + */ +uint16_t ext4_inode_get_links_cnt(struct ext4_inode *inode); + +/**@brief Set number of links to i-node. + * @param inode I-node to set number of links to + * @param cnt Number of links to i-node + */ +void ext4_inode_set_links_cnt(struct ext4_inode *inode, uint16_t cnt); + +/**@brief Get number of 512-bytes blocks used for i-node. + * @param sb Superblock + * @param inode I-node + * @return Number of 512-bytes blocks + */ +uint64_t ext4_inode_get_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode); + +/**@brief Set number of 512-bytes blocks used for i-node. + * @param sb Superblock + * @param inode I-node + * @param cnt Number of 512-bytes blocks + * @return Error code + */ +int ext4_inode_set_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode, uint64_t cnt); + +/**@brief Get flags (features) of i-node. + * @param inode I-node to get flags from + * @return Flags (bitmap) + */ +uint32_t ext4_inode_get_flags(struct ext4_inode *inode); + +/**@brief Set flags (features) of i-node. + * @param inode I-node to set flags to + * @param flags Flags to set to i-node + */ +void ext4_inode_set_flags(struct ext4_inode *inode, uint32_t flags); + +/**@brief Get file generation (used by NFS). + * @param inode I-node + * @return File generation + */ +uint32_t ext4_inode_get_generation(struct ext4_inode *inode); + +/**@brief Set file generation (used by NFS). + * @param inode I-node + * @param gen File generation + */ +void ext4_inode_set_generation(struct ext4_inode *inode, uint32_t gen); + +/**@brief Get extra I-node size field. + * @param sb Superblock + * @param inode I-node + * @return extra I-node size + */ +uint16_t ext4_inode_get_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode); + +/**@brief Set extra I-node size field. + * @param sb Superblock + * @param inode I-node + * @param size extra I-node size + */ +void ext4_inode_set_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode, + uint16_t size); + +/**@brief Get address of block, where are extended attributes located. + * @param inode I-node + * @param sb Superblock + * @return Block address + */ +uint64_t ext4_inode_get_file_acl(struct ext4_inode *inode, + struct ext4_sblock *sb); + +/**@brief Set address of block, where are extended attributes located. + * @param inode I-node + * @param sb Superblock + * @param acl Block address + */ +void ext4_inode_set_file_acl(struct ext4_inode *inode, struct ext4_sblock *sb, + uint64_t acl); + +/**@brief Get block address of specified direct block. + * @param inode I-node to load block from + * @param idx Index of logical block + * @return Physical block address + */ +uint32_t ext4_inode_get_direct_block(struct ext4_inode *inode, uint32_t idx); + +/**@brief Set block address of specified direct block. + * @param inode I-node to set block address to + * @param idx Index of logical block + * @param block Physical block address + */ +void ext4_inode_set_direct_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block); + +/**@brief Get block address of specified indirect block. + * @param inode I-node to get block address from + * @param idx Index of indirect block + * @return Physical block address + */ +uint32_t ext4_inode_get_indirect_block(struct ext4_inode *inode, uint32_t idx); + +/**@brief Set block address of specified indirect block. + * @param inode I-node to set block address to + * @param idx Index of indirect block + * @param block Physical block address + */ +void ext4_inode_set_indirect_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block); + +/**@brief Get device number + * @param inode I-node to get device number from + * @return Device number + */ +uint32_t ext4_inode_get_dev(struct ext4_inode *inode); + +/**@brief Set device number + * @param inode I-node to set device number to + * @param dev Device number + */ +void ext4_inode_set_dev(struct ext4_inode *inode, uint32_t dev); + +/**@brief return the type of i-node + * @param sb Superblock + * @param inode I-node to return the type of + * @return Result of check operation + */ +uint32_t ext4_inode_type(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Check if i-node has specified type. + * @param sb Superblock + * @param inode I-node to check type of + * @param type Type to check + * @return Result of check operation + */ +bool ext4_inode_is_type(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t type); + +/**@brief Check if i-node has specified flag. + * @param inode I-node to check flags of + * @param f Flag to check + * @return Result of check operation + */ +bool ext4_inode_has_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Remove specified flag from i-node. + * @param inode I-node to clear flag on + * @param f Flag to be cleared + */ +void ext4_inode_clear_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Set specified flag to i-node. + * @param inode I-node to set flag on + * @param f Flag to be set + */ +void ext4_inode_set_flag(struct ext4_inode *inode, uint32_t f); + +/**@brief Get inode checksum(crc32) + * @param sb Superblock + * @param inode I-node to get checksum value from + */ +uint32_t +ext4_inode_get_csum(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Get inode checksum(crc32) + * @param sb Superblock + * @param inode I-node to get checksum value from + */ +void +ext4_inode_set_csum(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t checksum); + +/**@brief Check if i-node can be truncated. + * @param sb Superblock + * @param inode I-node to check + * @return Result of the check operation + */ +bool ext4_inode_can_truncate(struct ext4_sblock *sb, struct ext4_inode *inode); + +/**@brief Get extent header from the root of the extent tree. + * @param inode I-node to get extent header from + * @return Pointer to extent header of the root node + */ +struct ext4_extent_header * +ext4_inode_get_extent_header(struct ext4_inode *inode); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_INODE_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_journal.h b/lib/lwext4_rust/c/lwext4/include/ext4_journal.h new file mode 100644 index 0000000..415618b --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_journal.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_journal.h + * @brief Journal handle functions + */ + +#ifndef EXT4_JOURNAL_H_ +#define EXT4_JOURNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +struct jbd_fs { + struct ext4_blockdev *bdev; + struct ext4_inode_ref inode_ref; + struct jbd_sb sb; + + bool dirty; +}; + +struct jbd_buf { + uint32_t jbd_lba; + struct ext4_block block; + struct jbd_trans *trans; + struct jbd_block_rec *block_rec; + TAILQ_ENTRY(jbd_buf) buf_node; + TAILQ_ENTRY(jbd_buf) dirty_buf_node; +}; + +struct jbd_revoke_rec { + ext4_fsblk_t lba; + RB_ENTRY(jbd_revoke_rec) revoke_node; +}; + +struct jbd_block_rec { + ext4_fsblk_t lba; + struct jbd_trans *trans; + RB_ENTRY(jbd_block_rec) block_rec_node; + LIST_ENTRY(jbd_block_rec) tbrec_node; + TAILQ_HEAD(jbd_buf_dirty, jbd_buf) dirty_buf_queue; +}; + +struct jbd_trans { + uint32_t trans_id; + + uint32_t start_iblock; + int alloc_blocks; + int data_cnt; + uint32_t data_csum; + int written_cnt; + int error; + + struct jbd_journal *journal; + + TAILQ_HEAD(jbd_trans_buf, jbd_buf) buf_queue; + RB_HEAD(jbd_revoke_tree, jbd_revoke_rec) revoke_root; + LIST_HEAD(jbd_trans_block_rec, jbd_block_rec) tbrec_list; + TAILQ_ENTRY(jbd_trans) trans_node; +}; + +struct jbd_journal { + uint32_t first; + uint32_t start; + uint32_t last; + uint32_t trans_id; + uint32_t alloc_trans_id; + + uint32_t block_size; + + TAILQ_HEAD(jbd_cp_queue, jbd_trans) cp_queue; + RB_HEAD(jbd_block, jbd_block_rec) block_rec_root; + + struct jbd_fs *jbd_fs; +}; + +int jbd_get_fs(struct ext4_fs *fs, + struct jbd_fs *jbd_fs); +int jbd_put_fs(struct jbd_fs *jbd_fs); +int jbd_inode_bmap(struct jbd_fs *jbd_fs, + ext4_lblk_t iblock, + ext4_fsblk_t *fblock); +int jbd_recover(struct jbd_fs *jbd_fs); +int jbd_journal_start(struct jbd_fs *jbd_fs, + struct jbd_journal *journal); +int jbd_journal_stop(struct jbd_journal *journal); +struct jbd_trans * +jbd_journal_new_trans(struct jbd_journal *journal); +int jbd_trans_set_block_dirty(struct jbd_trans *trans, + struct ext4_block *block); +int jbd_trans_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba); +int jbd_trans_try_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba); +void jbd_journal_free_trans(struct jbd_journal *journal, + struct jbd_trans *trans, + bool abort); +int jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans); +void +jbd_journal_purge_cp_trans(struct jbd_journal *journal, + bool flush, + bool once); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_JOURNAL_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_mbr.h b/lib/lwext4_rust/c/lwext4/include/ext4_mbr.h new file mode 100644 index 0000000..97a4459 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_mbr.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mbr.h + * @brief Master boot record parser + */ + +#ifndef EXT4_MBR_H_ +#define EXT4_MBR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/**@brief Master boot record block devices descriptor*/ +struct ext4_mbr_bdevs { + struct ext4_blockdev partitions[4]; +}; + +int ext4_mbr_scan(struct ext4_blockdev *parent, struct ext4_mbr_bdevs *bdevs); + +/**@brief Master boot record partitions*/ +struct ext4_mbr_parts { + + /**@brief Percentage division tab: + * - {50, 20, 10, 20} + * Sum of all 4 elements must be <= 100*/ + uint8_t division[4]; +}; + +int ext4_mbr_write(struct ext4_blockdev *parent, struct ext4_mbr_parts *parts, uint32_t disk_id); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MBR_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_misc.h b/lib/lwext4_rust/c/lwext4/include/ext4_misc.h new file mode 100644 index 0000000..3067d4d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_misc.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_misc.h + * @brief Miscellaneous helpers. + */ + +#ifndef EXT4_MISC_H_ +#define EXT4_MISC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/**************************************************************/ + +#define EXT4_DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y)) +#define EXT4_ALIGN(x, y) ((y) * EXT4_DIV_ROUND_UP((x), (y))) + +/****************************Endian conversion*****************/ + +static inline uint64_t reorder64(uint64_t n) +{ + return ((n & 0xff) << 56) | + ((n & 0xff00) << 40) | + ((n & 0xff0000) << 24) | + ((n & 0xff000000LL) << 8) | + ((n & 0xff00000000LL) >> 8) | + ((n & 0xff0000000000LL) >> 24) | + ((n & 0xff000000000000LL) >> 40) | + ((n & 0xff00000000000000LL) >> 56); +} + +static inline uint32_t reorder32(uint32_t n) +{ + return ((n & 0xff) << 24) | + ((n & 0xff00) << 8) | + ((n & 0xff0000) >> 8) | + ((n & 0xff000000) >> 24); +} + +static inline uint16_t reorder16(uint16_t n) +{ + return ((n & 0xff) << 8) | + ((n & 0xff00) >> 8); +} + +#ifdef CONFIG_BIG_ENDIAN +#define to_le64(_n) reorder64(_n) +#define to_le32(_n) reorder32(_n) +#define to_le16(_n) reorder16(_n) + +#define to_be64(_n) _n +#define to_be32(_n) _n +#define to_be16(_n) _n + +#else +#define to_le64(_n) _n +#define to_le32(_n) _n +#define to_le16(_n) _n + +#define to_be64(_n) reorder64(_n) +#define to_be32(_n) reorder32(_n) +#define to_be16(_n) reorder16(_n) +#endif + +/****************************Access macros to ext4 structures*****************/ + +#define ext4_get32(s, f) to_le32((s)->f) +#define ext4_get16(s, f) to_le16((s)->f) +#define ext4_get8(s, f) (s)->f + +#define ext4_set32(s, f, v) \ + do { \ + (s)->f = to_le32(v); \ + } while (0) +#define ext4_set16(s, f, v) \ + do { \ + (s)->f = to_le16(v); \ + } while (0) +#define ext4_set8 \ + (s, f, v) do { (s)->f = (v); } \ + while (0) + +/****************************Access macros to jbd2 structures*****************/ + +#define jbd_get32(s, f) to_be32((s)->f) +#define jbd_get16(s, f) to_be16((s)->f) +#define jbd_get8(s, f) (s)->f + +#define jbd_set32(s, f, v) \ + do { \ + (s)->f = to_be32(v); \ + } while (0) +#define jbd_set16(s, f, v) \ + do { \ + (s)->f = to_be16(v); \ + } while (0) +#define jbd_set8 \ + (s, f, v) do { (s)->f = (v); } \ + while (0) + +#ifdef __GNUC__ + #ifndef __unused + #define __unused __attribute__ ((__unused__)) + #endif +#else + #define __unused +#endif + +#ifndef offsetof +#define offsetof(type, field) \ + ((size_t)(&(((type *)0)->field))) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MISC_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_mkfs.h b/lib/lwext4_rust/c/lwext4/include/ext4_mkfs.h new file mode 100644 index 0000000..aadedb0 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_mkfs.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mkfs.h + * @brief + */ + +#ifndef EXT4_MKFS_H_ +#define EXT4_MKFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +#include +#include + +struct ext4_mkfs_info { + uint64_t len; + uint32_t block_size; + uint32_t blocks_per_group; + uint32_t inodes_per_group; + uint32_t inode_size; + uint32_t inodes; + uint32_t journal_blocks; + uint32_t feat_ro_compat; + uint32_t feat_compat; + uint32_t feat_incompat; + uint32_t bg_desc_reserve_blocks; + uint16_t dsc_size; + uint8_t uuid[UUID_SIZE]; + bool journal; + const char *label; +}; + + +int ext4_mkfs_read_info(struct ext4_blockdev *bd, struct ext4_mkfs_info *info); + +int ext4_mkfs(struct ext4_fs *fs, struct ext4_blockdev *bd, + struct ext4_mkfs_info *info, int fs_type); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_MKFS_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_oflags.h b/lib/lwext4_rust/c/lwext4/include/ext4_oflags.h new file mode 100644 index 0000000..7f7be7e --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_oflags.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_oflags.h + * @brief File opening & seeking flags. + */ +#ifndef EXT4_OFLAGS_H_ +#define EXT4_OFLAGS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/********************************FILE OPEN FLAGS*****************************/ + +#if CONFIG_HAVE_OWN_OFLAGS + + #ifndef O_RDONLY + #define O_RDONLY 00 + #endif + + #ifndef O_WRONLY + #define O_WRONLY 01 + #endif + + #ifndef O_RDWR + #define O_RDWR 02 + #endif + + #ifndef O_CREAT + #define O_CREAT 0100 + #endif + + #ifndef O_EXCL + #define O_EXCL 0200 + #endif + + #ifndef O_TRUNC + #define O_TRUNC 01000 + #endif + + #ifndef O_APPEND + #define O_APPEND 02000 + #endif + +/********************************FILE SEEK FLAGS*****************************/ + + #ifndef SEEK_SET + #define SEEK_SET 0 + #endif + + #ifndef SEEK_CUR + #define SEEK_CUR 1 + #endif + + #ifndef SEEK_END + #define SEEK_END 2 + #endif + +#else + #include + #include +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_OFLAGS_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_super.h b/lib/lwext4_rust/c/lwext4/include/ext4_super.h new file mode 100644 index 0000000..1b563da --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_super.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_super.c + * @brief Superblock operations. + */ + +#ifndef EXT4_SUPER_H_ +#define EXT4_SUPER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/**@brief Blocks count get stored in superblock. + * @param s superblock descriptor + * @return count of blocks*/ +static inline uint64_t ext4_sb_get_blocks_cnt(struct ext4_sblock *s) +{ + return ((uint64_t)to_le32(s->blocks_count_hi) << 32) | + to_le32(s->blocks_count_lo); +} + +/**@brief Blocks count set in superblock. + * @param s superblock descriptor + * @param cnt count of blocks*/ +static inline void ext4_sb_set_blocks_cnt(struct ext4_sblock *s, uint64_t cnt) +{ + s->blocks_count_lo = to_le32((cnt << 32) >> 32); + s->blocks_count_hi = to_le32(cnt >> 32); +} + +/**@brief Free blocks count get stored in superblock. + * @param s superblock descriptor + * @return free blocks*/ +static inline uint64_t ext4_sb_get_free_blocks_cnt(struct ext4_sblock *s) +{ + return ((uint64_t)to_le32(s->free_blocks_count_hi) << 32) | + to_le32(s->free_blocks_count_lo); +} + +/**@brief Free blocks count set. + * @param s superblock descriptor + * @param cnt new value of free blocks*/ +static inline void ext4_sb_set_free_blocks_cnt(struct ext4_sblock *s, + uint64_t cnt) +{ + s->free_blocks_count_lo = to_le32((cnt << 32) >> 32); + s->free_blocks_count_hi = to_le32(cnt >> 32); +} + +/**@brief Block size get from superblock. + * @param s superblock descriptor + * @return block size in bytes*/ +static inline uint32_t ext4_sb_get_block_size(struct ext4_sblock *s) +{ + return 1024 << to_le32(s->log_block_size); +} + +/**@brief Block group descriptor size. + * @param s superblock descriptor + * @return block group descriptor size in bytes*/ +static inline uint16_t ext4_sb_get_desc_size(struct ext4_sblock *s) +{ + uint16_t size = to_le16(s->desc_size); + + return size < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + ? EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + : size; +} + +/*************************Flags and features*********************************/ + +/**@brief Support check of flag. + * @param s superblock descriptor + * @param v flag to check + * @return true if flag is supported*/ +static inline bool ext4_sb_check_flag(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->flags) & v; +} + +/**@brief Support check of feature compatible. + * @param s superblock descriptor + * @param v feature to check + * @return true if feature is supported*/ +static inline bool ext4_sb_feature_com(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_compatible) & v; +} + +/**@brief Support check of feature incompatible. + * @param s superblock descriptor + * @param v feature to check + * @return true if feature is supported*/ +static inline bool ext4_sb_feature_incom(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_incompatible) & v; +} + +/**@brief Support check of read only flag. + * @param s superblock descriptor + * @param v flag to check + * @return true if flag is supported*/ +static inline bool ext4_sb_feature_ro_com(struct ext4_sblock *s, uint32_t v) +{ + return to_le32(s->features_read_only) & v; +} + +/**@brief Block group to flex group. + * @param s superblock descriptor + * @param block_group block group + * @return flex group id*/ +static inline uint32_t ext4_sb_bg_to_flex(struct ext4_sblock *s, + uint32_t block_group) +{ + return block_group >> to_le32(s->log_groups_per_flex); +} + +/**@brief Flex block group size. + * @param s superblock descriptor + * @return flex bg size*/ +static inline uint32_t ext4_sb_flex_bg_size(struct ext4_sblock *s) +{ + return 1 << to_le32(s->log_groups_per_flex); +} + +/**@brief Return first meta block group id. + * @param s superblock descriptor + * @return first meta_bg id */ +static inline uint32_t ext4_sb_first_meta_bg(struct ext4_sblock *s) +{ + return to_le32(s->first_meta_bg); +} + +/**************************More complex functions****************************/ + +/**@brief Returns a block group count. + * @param s superblock descriptor + * @return count of block groups*/ +uint32_t ext4_block_group_cnt(struct ext4_sblock *s); + +/**@brief Returns block count in block group + * (last block group may have less blocks) + * @param s superblock descriptor + * @param bgid block group id + * @return blocks count*/ +uint32_t ext4_blocks_in_group_cnt(struct ext4_sblock *s, uint32_t bgid); + +/**@brief Returns inodes count in block group + * (last block group may have less inodes) + * @param s superblock descriptor + * @param bgid block group id + * @return inodes count*/ +uint32_t ext4_inodes_in_group_cnt(struct ext4_sblock *s, uint32_t bgid); + +/***************************Read/write/check superblock**********************/ + +/**@brief Superblock write. + * @param bdev block device descriptor. + * @param s superblock descriptor + * @return Standard error code */ +int ext4_sb_write(struct ext4_blockdev *bdev, struct ext4_sblock *s); + +/**@brief Superblock read. + * @param bdev block device descriptor. + * @param s superblock descriptor + * @return Standard error code */ +int ext4_sb_read(struct ext4_blockdev *bdev, struct ext4_sblock *s); + +/**@brief Superblock simple validation. + * @param s superblock descriptor + * @return true if OK*/ +bool ext4_sb_check(struct ext4_sblock *s); + +/**@brief Superblock presence in block group. + * @param s superblock descriptor + * @param block_group block group id + * @return true if block group has superblock*/ +bool ext4_sb_is_super_in_bg(struct ext4_sblock *s, uint32_t block_group); + +/**@brief TODO:*/ +bool ext4_sb_sparse(uint32_t group); + +/**@brief TODO:*/ +uint32_t ext4_bg_num_gdb(struct ext4_sblock *s, uint32_t group); + +/**@brief TODO:*/ +uint32_t ext4_num_base_meta_clusters(struct ext4_sblock *s, + uint32_t block_group); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_SUPER_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_trans.h b/lib/lwext4_rust/c/lwext4/include/ext4_trans.h new file mode 100644 index 0000000..b17373c --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_trans.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_trans.h + * @brief Transaction handle functions + */ + +#ifndef EXT4_TRANS_H +#define EXT4_TRANS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + +/**@brief Mark a buffer dirty and add it to the current transaction. + * @param buf buffer + * @return standard error code*/ +int ext4_trans_set_block_dirty(struct ext4_buf *buf); + +/**@brief Block get function (through cache, don't read). + * jbd_trans_get_access would be called in order to + * get write access to the buffer. + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_block_get_noread(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba); + +/**@brief Block get function (through cache). + * jbd_trans_get_access would be called in order to + * get write access to the buffer. + * @param bdev block device descriptor + * @param b block descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_block_get(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba); + +/**@brief Try to add block to be revoked to the current transaction. + * @param bdev block device descriptor + * @param lba logical block address + * @return standard error code*/ +int ext4_trans_try_revoke_block(struct ext4_blockdev *bdev, + uint64_t lba); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT4_TRANS_H */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_types.h b/lib/lwext4_rust/c/lwext4/include/ext4_types.h new file mode 100644 index 0000000..b69936d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_types.h @@ -0,0 +1,849 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_types.h + * @brief Ext4 data structure definitions. + */ + +#ifndef EXT4_TYPES_H_ +#define EXT4_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +/* + * Types of blocks. + */ +typedef uint32_t ext4_lblk_t; +typedef uint64_t ext4_fsblk_t; + + +#define EXT4_CHECKSUM_CRC32C 1 + +#define UUID_SIZE 16 + +#pragma pack(push, 1) + +/* + * Structure of the super block + */ +struct ext4_sblock { + uint32_t inodes_count; /* I-nodes count */ + uint32_t blocks_count_lo; /* Blocks count */ + uint32_t reserved_blocks_count_lo; /* Reserved blocks count */ + uint32_t free_blocks_count_lo; /* Free blocks count */ + uint32_t free_inodes_count; /* Free inodes count */ + uint32_t first_data_block; /* First Data Block */ + uint32_t log_block_size; /* Block size */ + uint32_t log_cluster_size; /* Obsoleted fragment size */ + uint32_t blocks_per_group; /* Number of blocks per group */ + uint32_t frags_per_group; /* Obsoleted fragments per group */ + uint32_t inodes_per_group; /* Number of inodes per group */ + uint32_t mount_time; /* Mount time */ + uint32_t write_time; /* Write time */ + uint16_t mount_count; /* Mount count */ + uint16_t max_mount_count; /* Maximal mount count */ + uint16_t magic; /* Magic signature */ + uint16_t state; /* File system state */ + uint16_t errors; /* Behavior when detecting errors */ + uint16_t minor_rev_level; /* Minor revision level */ + uint32_t last_check_time; /* Time of last check */ + uint32_t check_interval; /* Maximum time between checks */ + uint32_t creator_os; /* Creator OS */ + uint32_t rev_level; /* Revision level */ + uint16_t def_resuid; /* Default uid for reserved blocks */ + uint16_t def_resgid; /* Default gid for reserved blocks */ + + /* Fields for EXT4_DYNAMIC_REV superblocks only. */ + uint32_t first_inode; /* First non-reserved inode */ + uint16_t inode_size; /* Size of inode structure */ + uint16_t block_group_index; /* Block group index of this superblock */ + uint32_t features_compatible; /* Compatible feature set */ + uint32_t features_incompatible; /* Incompatible feature set */ + uint32_t features_read_only; /* Readonly-compatible feature set */ + uint8_t uuid[UUID_SIZE]; /* 128-bit uuid for volume */ + char volume_name[16]; /* Volume name */ + char last_mounted[64]; /* Directory where last mounted */ + uint32_t algorithm_usage_bitmap; /* For compression */ + + /* + * Performance hints. Directory preallocation should only + * happen if the EXT4_FEATURE_COMPAT_DIR_PREALLOC flag is on. + */ + uint8_t s_prealloc_blocks; /* Number of blocks to try to preallocate */ + uint8_t s_prealloc_dir_blocks; /* Number to preallocate for dirs */ + uint16_t s_reserved_gdt_blocks; /* Per group desc for online growth */ + + /* + * Journaling support valid if EXT4_FEATURE_COMPAT_HAS_JOURNAL set. + */ + uint8_t journal_uuid[UUID_SIZE]; /* UUID of journal superblock */ + uint32_t journal_inode_number; /* Inode number of journal file */ + uint32_t journal_dev; /* Device number of journal file */ + uint32_t last_orphan; /* Head of list of inodes to delete */ + uint32_t hash_seed[4]; /* HTREE hash seed */ + uint8_t default_hash_version; /* Default hash version to use */ + uint8_t journal_backup_type; + uint16_t desc_size; /* Size of group descriptor */ + uint32_t default_mount_opts; /* Default mount options */ + uint32_t first_meta_bg; /* First metablock block group */ + uint32_t mkfs_time; /* When the filesystem was created */ + uint32_t journal_blocks[17]; /* Backup of the journal inode */ + + /* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */ + uint32_t blocks_count_hi; /* Blocks count */ + uint32_t reserved_blocks_count_hi; /* Reserved blocks count */ + uint32_t free_blocks_count_hi; /* Free blocks count */ + uint16_t min_extra_isize; /* All inodes have at least # bytes */ + uint16_t want_extra_isize; /* New inodes should reserve # bytes */ + uint32_t flags; /* Miscellaneous flags */ + uint16_t raid_stride; /* RAID stride */ + uint16_t mmp_interval; /* # seconds to wait in MMP checking */ + uint64_t mmp_block; /* Block for multi-mount protection */ + uint32_t raid_stripe_width; /* Blocks on all data disks (N * stride) */ + uint8_t log_groups_per_flex; /* FLEX_BG group size */ + uint8_t checksum_type; + uint16_t reserved_pad; + uint64_t kbytes_written; /* Number of lifetime kilobytes written */ + uint32_t snapshot_inum; /* I-node number of active snapshot */ + uint32_t snapshot_id; /* Sequential ID of active snapshot */ + uint64_t + snapshot_r_blocks_count; /* Reserved blocks for active snapshot's + future use */ + uint32_t + snapshot_list; /* I-node number of the head of the on-disk snapshot + list */ + uint32_t error_count; /* Number of file system errors */ + uint32_t first_error_time; /* First time an error happened */ + uint32_t first_error_ino; /* I-node involved in first error */ + uint64_t first_error_block; /* Block involved of first error */ + uint8_t first_error_func[32]; /* Function where the error happened */ + uint32_t first_error_line; /* Line number where error happened */ + uint32_t last_error_time; /* Most recent time of an error */ + uint32_t last_error_ino; /* I-node involved in last error */ + uint32_t last_error_line; /* Line number where error happened */ + uint64_t last_error_block; /* Block involved of last error */ + uint8_t last_error_func[32]; /* Function where the error happened */ + uint8_t mount_opts[64]; + uint32_t usr_quota_inum; /* inode for tracking user quota */ + uint32_t grp_quota_inum; /* inode for tracking group quota */ + uint32_t overhead_clusters; /* overhead blocks/clusters in fs */ + uint32_t backup_bgs[2]; /* groups with sparse_super2 SBs */ + uint8_t encrypt_algos[4]; /* Encryption algorithms in use */ + uint8_t encrypt_pw_salt[16]; /* Salt used for string2key algorithm */ + uint32_t lpf_ino; /* Location of the lost+found inode */ + uint32_t padding[100]; /* Padding to the end of the block */ + uint32_t checksum; /* crc32c(superblock) */ +}; + +#pragma pack(pop) + +#define EXT4_SUPERBLOCK_MAGIC 0xEF53 +#define EXT4_SUPERBLOCK_SIZE 1024 +#define EXT4_SUPERBLOCK_OFFSET 1024 + +#define EXT4_SUPERBLOCK_OS_LINUX 0 +#define EXT4_SUPERBLOCK_OS_HURD 1 + +/* + * Misc. filesystem flags + */ +#define EXT4_SUPERBLOCK_FLAGS_SIGNED_HASH 0x0001 +#define EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH 0x0002 +#define EXT4_SUPERBLOCK_FLAGS_TEST_FILESYS 0x0004 +/* + * Filesystem states + */ +#define EXT4_SUPERBLOCK_STATE_VALID_FS 0x0001 /* Unmounted cleanly */ +#define EXT4_SUPERBLOCK_STATE_ERROR_FS 0x0002 /* Errors detected */ +#define EXT4_SUPERBLOCK_STATE_ORPHAN_FS 0x0004 /* Orphans being recovered */ + +/* + * Behaviour when errors detected + */ +#define EXT4_SUPERBLOCK_ERRORS_CONTINUE 1 /* Continue execution */ +#define EXT4_SUPERBLOCK_ERRORS_RO 2 /* Remount fs read-only */ +#define EXT4_SUPERBLOCK_ERRORS_PANIC 3 /* Panic */ +#define EXT4_SUPERBLOCK_ERRORS_DEFAULT EXT4_ERRORS_CONTINUE + +/* + * Compatible features + */ +#define EXT4_FCOM_DIR_PREALLOC 0x0001 +#define EXT4_FCOM_IMAGIC_INODES 0x0002 +#define EXT4_FCOM_HAS_JOURNAL 0x0004 +#define EXT4_FCOM_EXT_ATTR 0x0008 +#define EXT4_FCOM_RESIZE_INODE 0x0010 +#define EXT4_FCOM_DIR_INDEX 0x0020 + +/* + * Read-only compatible features + */ +#define EXT4_FRO_COM_SPARSE_SUPER 0x0001 +#define EXT4_FRO_COM_LARGE_FILE 0x0002 +#define EXT4_FRO_COM_BTREE_DIR 0x0004 +#define EXT4_FRO_COM_HUGE_FILE 0x0008 +#define EXT4_FRO_COM_GDT_CSUM 0x0010 +#define EXT4_FRO_COM_DIR_NLINK 0x0020 +#define EXT4_FRO_COM_EXTRA_ISIZE 0x0040 +#define EXT4_FRO_COM_QUOTA 0x0100 +#define EXT4_FRO_COM_BIGALLOC 0x0200 +#define EXT4_FRO_COM_METADATA_CSUM 0x0400 + +/* + * Incompatible features + */ +#define EXT4_FINCOM_COMPRESSION 0x0001 +#define EXT4_FINCOM_FILETYPE 0x0002 +#define EXT4_FINCOM_RECOVER 0x0004 /* Needs recovery */ +#define EXT4_FINCOM_JOURNAL_DEV 0x0008 /* Journal device */ +#define EXT4_FINCOM_META_BG 0x0010 +#define EXT4_FINCOM_EXTENTS 0x0040 /* extents support */ +#define EXT4_FINCOM_64BIT 0x0080 +#define EXT4_FINCOM_MMP 0x0100 +#define EXT4_FINCOM_FLEX_BG 0x0200 +#define EXT4_FINCOM_EA_INODE 0x0400 /* EA in inode */ +#define EXT4_FINCOM_DIRDATA 0x1000 /* data in dirent */ +#define EXT4_FINCOM_BG_USE_META_CSUM 0x2000 /* use crc32c for bg */ +#define EXT4_FINCOM_LARGEDIR 0x4000 /* >2GB or 3-lvl htree */ +#define EXT4_FINCOM_INLINE_DATA 0x8000 /* data in inode */ + +/* + * EXT2 supported feature set + */ +#define EXT2_SUPPORTED_FCOM 0x0000 + +#define EXT2_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG) + +#define EXT2_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | \ + EXT4_FRO_COM_LARGE_FILE) + +/* + * EXT3 supported feature set + */ +#define EXT3_SUPPORTED_FCOM (EXT4_FCOM_DIR_INDEX) + +#define EXT3_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG) + +#define EXT3_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | EXT4_FRO_COM_LARGE_FILE) + +/* + * EXT4 supported feature set + */ +#define EXT4_SUPPORTED_FCOM (EXT4_FCOM_DIR_INDEX) + +#define EXT4_SUPPORTED_FINCOM \ + (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG | \ + EXT4_FINCOM_EXTENTS | EXT4_FINCOM_FLEX_BG | \ + EXT4_FINCOM_64BIT) + +#define EXT4_SUPPORTED_FRO_COM \ + (EXT4_FRO_COM_SPARSE_SUPER | \ + EXT4_FRO_COM_METADATA_CSUM | \ + EXT4_FRO_COM_LARGE_FILE | EXT4_FRO_COM_GDT_CSUM | \ + EXT4_FRO_COM_DIR_NLINK | \ + EXT4_FRO_COM_EXTRA_ISIZE | EXT4_FRO_COM_HUGE_FILE) + +/*Ignored features: + * RECOVER - journaling in lwext4 is not supported + * (probably won't be ever...) + * MMP - multi-mout protection (impossible scenario) + * */ +#define EXT_FINCOM_IGNORED \ + EXT4_FINCOM_RECOVER | EXT4_FINCOM_MMP + +#if 0 +/*TODO: Features incompatible to implement*/ +#define EXT4_SUPPORTED_FINCOM + (EXT4_FINCOM_INLINE_DATA) + +/*TODO: Features read only to implement*/ +#define EXT4_SUPPORTED_FRO_COM + EXT4_FRO_COM_BIGALLOC |\ + EXT4_FRO_COM_QUOTA) +#endif + + +/* Inode table/bitmap not in use */ +#define EXT4_BLOCK_GROUP_INODE_UNINIT 0x0001 +/* Block bitmap not in use */ +#define EXT4_BLOCK_GROUP_BLOCK_UNINIT 0x0002 +/* On-disk itable initialized to zero */ +#define EXT4_BLOCK_GROUP_ITABLE_ZEROED 0x0004 + +/* + * Structure of a blocks group descriptor + */ +struct ext4_bgroup { + uint32_t block_bitmap_lo; /* Blocks bitmap block */ + uint32_t inode_bitmap_lo; /* Inodes bitmap block */ + uint32_t inode_table_first_block_lo; /* Inodes table block */ + uint16_t free_blocks_count_lo; /* Free blocks count */ + uint16_t free_inodes_count_lo; /* Free inodes count */ + uint16_t used_dirs_count_lo; /* Directories count */ + uint16_t flags; /* EXT4_BG_flags (INODE_UNINIT, etc) */ + uint32_t exclude_bitmap_lo; /* Exclude bitmap for snapshots */ + uint16_t block_bitmap_csum_lo; /* crc32c(s_uuid+grp_num+bbitmap) LE */ + uint16_t inode_bitmap_csum_lo; /* crc32c(s_uuid+grp_num+ibitmap) LE */ + uint16_t itable_unused_lo; /* Unused inodes count */ + uint16_t checksum; /* crc16(sb_uuid+group+desc) */ + + uint32_t block_bitmap_hi; /* Blocks bitmap block MSB */ + uint32_t inode_bitmap_hi; /* I-nodes bitmap block MSB */ + uint32_t inode_table_first_block_hi; /* I-nodes table block MSB */ + uint16_t free_blocks_count_hi; /* Free blocks count MSB */ + uint16_t free_inodes_count_hi; /* Free i-nodes count MSB */ + uint16_t used_dirs_count_hi; /* Directories count MSB */ + uint16_t itable_unused_hi; /* Unused inodes count MSB */ + uint32_t exclude_bitmap_hi; /* Exclude bitmap block MSB */ + uint16_t block_bitmap_csum_hi; /* crc32c(s_uuid+grp_num+bbitmap) BE */ + uint16_t inode_bitmap_csum_hi; /* crc32c(s_uuid+grp_num+ibitmap) BE */ + uint32_t reserved; /* Padding */ +}; + + +#define EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE 32 +#define EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE 64 + +#define EXT4_MIN_BLOCK_SIZE 1024 /* 1 KiB */ +#define EXT4_MAX_BLOCK_SIZE 65536 /* 64 KiB */ +#define EXT4_REV0_INODE_SIZE 128 + +#define EXT4_INODE_BLOCK_SIZE 512 + +#define EXT4_INODE_DIRECT_BLOCK_COUNT 12 +#define EXT4_INODE_INDIRECT_BLOCK EXT4_INODE_DIRECT_BLOCK_COUNT +#define EXT4_INODE_DOUBLE_INDIRECT_BLOCK (EXT4_INODE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_TRIPPLE_INDIRECT_BLOCK (EXT4_INODE_DOUBLE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_BLOCKS (EXT4_INODE_TRIPPLE_INDIRECT_BLOCK + 1) +#define EXT4_INODE_INDIRECT_BLOCK_COUNT \ + (EXT4_INODE_BLOCKS - EXT4_INODE_DIRECT_BLOCK_COUNT) + +#pragma pack(push, 1) + +/* + * Structure of an inode on the disk + */ +struct ext4_inode { + uint16_t mode; /* File mode */ + uint16_t uid; /* Low 16 bits of owner uid */ + uint32_t size_lo; /* Size in bytes */ + uint32_t access_time; /* Access time */ + uint32_t change_inode_time; /* I-node change time */ + uint32_t modification_time; /* Modification time */ + uint32_t deletion_time; /* Deletion time */ + uint16_t gid; /* Low 16 bits of group id */ + uint16_t links_count; /* Links count */ + uint32_t blocks_count_lo; /* Blocks count */ + uint32_t flags; /* File flags */ + uint32_t unused_osd1; /* OS dependent - not used in HelenOS */ + uint32_t blocks[EXT4_INODE_BLOCKS]; /* Pointers to blocks */ + uint32_t generation; /* File version (for NFS) */ + uint32_t file_acl_lo; /* File ACL */ + uint32_t size_hi; + uint32_t obso_faddr; /* Obsoleted fragment address */ + + union { + struct { + uint16_t blocks_high; + uint16_t file_acl_high; + uint16_t uid_high; + uint16_t gid_high; + uint16_t checksum_lo; /* crc32c(uuid+inum+inode) LE */ + uint16_t reserved2; + } linux2; + struct { + uint16_t reserved1; + uint16_t mode_high; + uint16_t uid_high; + uint16_t gid_high; + uint32_t author; + } hurd2; + } osd2; + + uint16_t extra_isize; + uint16_t checksum_hi; /* crc32c(uuid+inum+inode) BE */ + uint32_t ctime_extra; /* Extra change time (nsec << 2 | epoch) */ + uint32_t mtime_extra; /* Extra Modification time (nsec << 2 | epoch) */ + uint32_t atime_extra; /* Extra Access time (nsec << 2 | epoch) */ + uint32_t crtime; /* File creation time */ + uint32_t + crtime_extra; /* Extra file creation time (nsec << 2 | epoch) */ + uint32_t version_hi; /* High 32 bits for 64-bit version */ +}; + +#pragma pack(pop) + +#define EXT4_INODE_MODE_FIFO 0x1000 +#define EXT4_INODE_MODE_CHARDEV 0x2000 +#define EXT4_INODE_MODE_DIRECTORY 0x4000 +#define EXT4_INODE_MODE_BLOCKDEV 0x6000 +#define EXT4_INODE_MODE_FILE 0x8000 +#define EXT4_INODE_MODE_SOFTLINK 0xA000 +#define EXT4_INODE_MODE_SOCKET 0xC000 +#define EXT4_INODE_MODE_TYPE_MASK 0xF000 + +/* + * Inode flags + */ +#define EXT4_INODE_FLAG_SECRM 0x00000001 /* Secure deletion */ +#define EXT4_INODE_FLAG_UNRM 0x00000002 /* Undelete */ +#define EXT4_INODE_FLAG_COMPR 0x00000004 /* Compress file */ +#define EXT4_INODE_FLAG_SYNC 0x00000008 /* Synchronous updates */ +#define EXT4_INODE_FLAG_IMMUTABLE 0x00000010 /* Immutable file */ +#define EXT4_INODE_FLAG_APPEND 0x00000020 /* writes to file may only append */ +#define EXT4_INODE_FLAG_NODUMP 0x00000040 /* do not dump file */ +#define EXT4_INODE_FLAG_NOATIME 0x00000080 /* do not update atime */ + +/* Compression flags */ +#define EXT4_INODE_FLAG_DIRTY 0x00000100 +#define EXT4_INODE_FLAG_COMPRBLK \ + 0x00000200 /* One or more compressed clusters */ +#define EXT4_INODE_FLAG_NOCOMPR 0x00000400 /* Don't compress */ +#define EXT4_INODE_FLAG_ECOMPR 0x00000800 /* Compression error */ + +#define EXT4_INODE_FLAG_INDEX 0x00001000 /* hash-indexed directory */ +#define EXT4_INODE_FLAG_IMAGIC 0x00002000 /* AFS directory */ +#define EXT4_INODE_FLAG_JOURNAL_DATA \ + 0x00004000 /* File data should be journaled */ +#define EXT4_INODE_FLAG_NOTAIL 0x00008000 /* File tail should not be merged */ +#define EXT4_INODE_FLAG_DIRSYNC \ + 0x00010000 /* Dirsync behaviour (directories only) */ +#define EXT4_INODE_FLAG_TOPDIR 0x00020000 /* Top of directory hierarchies */ +#define EXT4_INODE_FLAG_HUGE_FILE 0x00040000 /* Set to each huge file */ +#define EXT4_INODE_FLAG_EXTENTS 0x00080000 /* Inode uses extents */ +#define EXT4_INODE_FLAG_EA_INODE 0x00200000 /* Inode used for large EA */ +#define EXT4_INODE_FLAG_EOFBLOCKS 0x00400000 /* Blocks allocated beyond EOF */ +#define EXT4_INODE_FLAG_RESERVED 0x80000000 /* reserved for ext4 lib */ + +#define EXT4_INODE_ROOT_INDEX 2 + + +#define EXT4_DIRECTORY_FILENAME_LEN 255 + +/**@brief Directory entry types. */ +enum { EXT4_DE_UNKNOWN = 0, + EXT4_DE_REG_FILE, + EXT4_DE_DIR, + EXT4_DE_CHRDEV, + EXT4_DE_BLKDEV, + EXT4_DE_FIFO, + EXT4_DE_SOCK, + EXT4_DE_SYMLINK }; + +#define EXT4_DIRENTRY_DIR_CSUM 0xDE + +#pragma pack(push, 1) + +union ext4_dir_en_internal { + uint8_t name_length_high; /* Higher 8 bits of name length */ + uint8_t inode_type; /* Type of referenced inode (in rev >= 0.5) */ +}; + +/** + * Linked list directory entry structure + */ +struct ext4_dir_en { + uint32_t inode; /* I-node for the entry */ + uint16_t entry_len; /* Distance to the next directory entry */ + uint8_t name_len; /* Lower 8 bits of name length */ + + union ext4_dir_en_internal in; + uint8_t name[]; /* Entry name */ +}; + +/* Structures for indexed directory */ + +struct ext4_dir_idx_climit { + uint16_t limit; + uint16_t count; +}; + +struct ext4_dir_idx_dot_en { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; + uint8_t name[4]; +}; + +struct ext4_dir_idx_rinfo { + uint32_t reserved_zero; + uint8_t hash_version; + uint8_t info_length; + uint8_t indirect_levels; + uint8_t unused_flags; +}; + +struct ext4_dir_idx_entry { + uint32_t hash; + uint32_t block; +}; + +struct ext4_dir_idx_root { + struct ext4_dir_idx_dot_en dots[2]; + struct ext4_dir_idx_rinfo info; + struct ext4_dir_idx_entry en[]; +}; + +struct ext4_fake_dir_entry { + uint32_t inode; + uint16_t entry_length; + uint8_t name_length; + uint8_t inode_type; +}; + +struct ext4_dir_idx_node { + struct ext4_fake_dir_entry fake; + struct ext4_dir_idx_entry entries[]; +}; + +/* + * This goes at the end of each htree block. + */ +struct ext4_dir_idx_tail { + uint32_t reserved; + uint32_t checksum; /* crc32c(uuid+inum+dirblock) */ +}; + +/* + * This is a bogus directory entry at the end of each leaf block that + * records checksums. + */ +struct ext4_dir_entry_tail { + uint32_t reserved_zero1; /* Pretend to be unused */ + uint16_t rec_len; /* 12 */ + uint8_t reserved_zero2; /* Zero name length */ + uint8_t reserved_ft; /* 0xDE, fake file type */ + uint32_t checksum; /* crc32c(uuid+inum+dirblock) */ +}; + +#pragma pack(pop) + +#define EXT4_DIRENT_TAIL(block, blocksize) \ + ((struct ext4_dir_entry_tail *)(((char *)(block)) + ((blocksize) - \ + sizeof(struct ext4_dir_entry_tail)))) + +#define EXT4_ERR_BAD_DX_DIR (-25000) + +#define EXT4_LINK_MAX 65000 + +#define EXT4_BAD_INO 1 +#define EXT4_ROOT_INO 2 +#define EXT4_BOOT_LOADER_INO 5 +#define EXT4_UNDEL_DIR_INO 6 +#define EXT4_RESIZE_INO 7 +#define EXT4_JOURNAL_INO 8 + +#define EXT4_GOOD_OLD_FIRST_INO 11 +#define EXT_MAX_BLOCKS (ext4_lblk_t) (-1) +#define IN_RANGE(b, first, len) ((b) >= (first) && (b) <= (first) + (len) - 1) + + +/******************************************************************************/ + +/* EXT3 HTree directory indexing */ +#define EXT2_HTREE_LEGACY 0 +#define EXT2_HTREE_HALF_MD4 1 +#define EXT2_HTREE_TEA 2 +#define EXT2_HTREE_LEGACY_UNSIGNED 3 +#define EXT2_HTREE_HALF_MD4_UNSIGNED 4 +#define EXT2_HTREE_TEA_UNSIGNED 5 + +#define EXT2_HTREE_EOF 0x7FFFFFFFUL + +#define EXT4_GOOD_OLD_INODE_SIZE 128 + +/*****************************************************************************/ + +/* + * JBD stores integers in big endian. + */ + +#define JBD_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * Descriptor block types: + */ + +#define JBD_DESCRIPTOR_BLOCK 1 +#define JBD_COMMIT_BLOCK 2 +#define JBD_SUPERBLOCK 3 +#define JBD_SUPERBLOCK_V2 4 +#define JBD_REVOKE_BLOCK 5 + +#pragma pack(push, 1) + +/* + * Standard header for all descriptor blocks: + */ +struct jbd_bhdr { + uint32_t magic; + uint32_t blocktype; + uint32_t sequence; +}; + +#pragma pack(pop) + +/* + * Checksum types. + */ +#define JBD_CRC32_CHKSUM 1 +#define JBD_MD5_CHKSUM 2 +#define JBD_SHA1_CHKSUM 3 +#define JBD_CRC32C_CHKSUM 4 + +#define JBD_CRC32_CHKSUM_SIZE 4 + +#define JBD_CHECKSUM_BYTES (32 / sizeof(uint32_t)) + +#pragma pack(push, 1) + +/* + * Commit block header for storing transactional checksums: + * + * NOTE: If FEATURE_COMPAT_CHECKSUM (checksum v1) is set, the h_chksum* + * fields are used to store a checksum of the descriptor and data blocks. + * + * If FEATURE_INCOMPAT_CSUM_V2 (checksum v2) is set, then the h_chksum + * field is used to store crc32c(uuid+commit_block). Each journal metadata + * block gets its own checksum, and data block checksums are stored in + * journal_block_tag (in the descriptor). The other h_chksum* fields are + * not used. + * + * If FEATURE_INCOMPAT_CSUM_V3 is set, the descriptor block uses + * journal_block_tag3_t to store a full 32-bit checksum. Everything else + * is the same as v2. + * + * Checksum v1, v2, and v3 are mutually exclusive features. + */ + +struct jbd_commit_header { + struct jbd_bhdr header; + uint8_t chksum_type; + uint8_t chksum_size; + uint8_t padding[2]; + uint32_t chksum[JBD_CHECKSUM_BYTES]; + uint64_t commit_sec; + uint32_t commit_nsec; +}; + +/* + * The block tag: used to describe a single buffer in the journal + */ +struct jbd_block_tag3 { + uint32_t blocknr; /* The on-disk block number */ + uint32_t flags; /* See below */ + uint32_t blocknr_high; /* most-significant high 32bits. */ + uint32_t checksum; /* crc32c(uuid+seq+block) */ +}; + +struct jbd_block_tag { + uint32_t blocknr; /* The on-disk block number */ + uint16_t checksum; /* truncated crc32c(uuid+seq+block) */ + uint16_t flags; /* See below */ + uint32_t blocknr_high; /* most-significant high 32bits. */ +}; + +#pragma pack(pop) + +/* Definitions for the journal tag flags word: */ +#define JBD_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JBD_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JBD_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JBD_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + +#pragma pack(push, 1) + +/* Tail of descriptor block, for checksumming */ +struct jbd_block_tail { + uint32_t checksum; +}; + +/* + * The revoke descriptor: used on disk to describe a series of blocks to + * be revoked from the log + */ +struct jbd_revoke_header { + struct jbd_bhdr header; + uint32_t count; /* Count of bytes used in the block */ +}; + +/* Tail of revoke block, for checksumming */ +struct jbd_revoke_tail { + uint32_t checksum; +}; + +#pragma pack(pop) + +#define JBD_USERS_MAX 48 +#define JBD_USERS_SIZE (UUID_SIZE * JBD_USERS_MAX) + +#pragma pack(push, 1) + +/* + * The journal superblock. All fields are in big-endian byte order. + */ +struct jbd_sb { +/* 0x0000 */ + struct jbd_bhdr header; + +/* 0x000C */ + /* Static information describing the journal */ + uint32_t blocksize; /* journal device blocksize */ + uint32_t maxlen; /* total blocks in journal file */ + uint32_t first; /* first block of log information */ + +/* 0x0018 */ + /* Dynamic information describing the current state of the log */ + uint32_t sequence; /* first commit ID expected in log */ + uint32_t start; /* blocknr of start of log */ + +/* 0x0020 */ + /* Error value, as set by journal_abort(). */ + int32_t error_val; + +/* 0x0024 */ + /* Remaining fields are only valid in a version-2 superblock */ + uint32_t feature_compat; /* compatible feature set */ + uint32_t feature_incompat; /* incompatible feature set */ + uint32_t feature_ro_compat; /* readonly-compatible feature set */ +/* 0x0030 */ + uint8_t uuid[UUID_SIZE]; /* 128-bit uuid for journal */ + +/* 0x0040 */ + uint32_t nr_users; /* Nr of filesystems sharing log */ + + uint32_t dynsuper; /* Blocknr of dynamic superblock copy*/ + +/* 0x0048 */ + uint32_t max_transaction; /* Limit of journal blocks per trans.*/ + uint32_t max_trandata; /* Limit of data blocks per trans. */ + +/* 0x0050 */ + uint8_t checksum_type; /* checksum type */ + uint8_t padding2[3]; + uint32_t padding[42]; + uint32_t checksum; /* crc32c(superblock) */ + +/* 0x0100 */ + uint8_t users[JBD_USERS_SIZE]; /* ids of all fs'es sharing the log */ + +/* 0x0400 */ +}; + +#pragma pack(pop) + +#define JBD_SUPERBLOCK_SIZE sizeof(struct jbd_sb) + +#define JBD_HAS_COMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_compat & to_be32((mask)))) +#define JBD_HAS_RO_COMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_ro_compat & to_be32((mask)))) +#define JBD_HAS_INCOMPAT_FEATURE(jsb,mask) \ + ((jsb)->header.blocktype >= to_be32(2) && \ + ((jsb)->feature_incompat & to_be32((mask)))) + +#define JBD_FEATURE_COMPAT_CHECKSUM 0x00000001 + +#define JBD_FEATURE_INCOMPAT_REVOKE 0x00000001 +#define JBD_FEATURE_INCOMPAT_64BIT 0x00000002 +#define JBD_FEATURE_INCOMPAT_ASYNC_COMMIT 0x00000004 +#define JBD_FEATURE_INCOMPAT_CSUM_V2 0x00000008 +#define JBD_FEATURE_INCOMPAT_CSUM_V3 0x00000010 + +/* Features known to this kernel version: */ +#define JBD_KNOWN_COMPAT_FEATURES 0 +#define JBD_KNOWN_ROCOMPAT_FEATURES 0 +#define JBD_KNOWN_INCOMPAT_FEATURES (JBD_FEATURE_INCOMPAT_REVOKE|\ + JBD_FEATURE_INCOMPAT_ASYNC_COMMIT|\ + JBD_FEATURE_INCOMPAT_64BIT|\ + JBD_FEATURE_INCOMPAT_CSUM_V2|\ + JBD_FEATURE_INCOMPAT_CSUM_V3) + +/*****************************************************************************/ + +#define EXT4_CRC32_INIT (0xFFFFFFFFUL) + +/*****************************************************************************/ + +#ifdef __cplusplus +} +#endif + + +#if CONFIG_USE_USER_MALLOC + +void *ext4_user_malloc(size_t size); +void *ext4_user_calloc(size_t nmemb, size_t size); +void *ext4_user_realloc(void *ptr, size_t size); +void ext4_user_free(void *ptr); + +#define ext4_malloc ext4_user_malloc +#define ext4_calloc ext4_user_calloc +#define ext4_realloc ext4_user_realloc +#define ext4_free ext4_user_free + +#else + +#define ext4_malloc malloc +#define ext4_calloc calloc +#define ext4_realloc realloc +#define ext4_free free + +#endif + + +#endif /* EXT4_TYPES_H_ */ + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/ext4_xattr.h b/lib/lwext4_rust/c/lwext4/include/ext4_xattr.h new file mode 100644 index 0000000..d79febc --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/ext4_xattr.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.h + * @brief Extended Attribute manipulation. + */ + +#ifndef EXT4_XATTR_H_ +#define EXT4_XATTR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +struct ext4_xattr_info { + uint8_t name_index; + const char *name; + size_t name_len; + const void *value; + size_t value_len; +}; + +struct ext4_xattr_list_entry { + uint8_t name_index; + char *name; + size_t name_len; + struct ext4_xattr_list_entry *next; +}; + +struct ext4_xattr_search { + /* The first entry in the buffer */ + struct ext4_xattr_entry *first; + + /* The address of the buffer */ + void *base; + + /* The first inaccessible address */ + void *end; + + /* The current entry pointer */ + struct ext4_xattr_entry *here; + + /* Entry not found */ + bool not_found; +}; + +const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len, + bool *found); + +const char *ext4_get_xattr_name_prefix(uint8_t name_index, + size_t *ret_prefix_len); + +int ext4_xattr_list(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_list_entry *list, size_t *list_len); + +int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, void *buf, size_t buf_len, + size_t *data_len); + +int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len); + +int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, const void *value, + size_t value_len); + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/include/misc/queue.h b/lib/lwext4_rust/c/lwext4/include/misc/queue.h new file mode 100644 index 0000000..6efd6f3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/misc/queue.h @@ -0,0 +1,702 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD$ + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../ext4_config.h" + +/* + * This file defines four types of data structures: singly-linked lists, + * singly-linked tail queues, lists and tail queues. + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A singly-linked tail queue is headed by a pair of pointers, one to the + * head of the list and the other to the tail of the list. The elements are + * singly linked for minimum space and pointer manipulation overhead at the + * expense of O(n) removal for arbitrary elements. New elements can be added + * to the list after an existing element, at the head of the list, or at the + * end of the list. Elements being removed from the head of the tail queue + * should use the explicit macro for this purpose for optimum efficiency. + * A singly-linked tail queue may only be traversed in the forward direction. + * Singly-linked tail queues are ideal for applications with large datasets + * and few or no removals or for implementing a FIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may be traversed in either direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + * + * + * SLIST LIST STAILQ TAILQ + * _HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - + - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_FROM + + + + + * _FOREACH_SAFE + + + + + * _FOREACH_FROM_SAFE + + + + + * _FOREACH_REVERSE - - - + + * _FOREACH_REVERSE_FROM - - - + + * _FOREACH_REVERSE_SAFE - - - + + * _FOREACH_REVERSE_FROM_SAFE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _CONCAT - - + + + * _REMOVE_AFTER + - + - + * _REMOVE_HEAD + - + - + * _REMOVE + + + + + * _SWAP + + + + + * + */ +#ifdef QUEUE_MACRO_DEBUG +/* Store the last 2 places the queue element or head was altered */ +struct qm_trace { + unsigned long lastline; + unsigned long prevline; + const char *lastfile; + const char *prevfile; +}; + +#define TRACEBUF struct qm_trace trace; +#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) + +#define QMD_TRACE_HEAD(head) do { \ + (head)->trace.prevline = (head)->trace.lastline; \ + (head)->trace.prevfile = (head)->trace.lastfile; \ + (head)->trace.lastline = __LINE__; \ + (head)->trace.lastfile = __FILE__; \ +} while (0) + +#define QMD_TRACE_ELEM(elem) do { \ + (elem)->trace.prevline = (elem)->trace.lastline; \ + (elem)->trace.prevfile = (elem)->trace.lastfile; \ + (elem)->trace.lastline = __LINE__; \ + (elem)->trace.lastfile = __FILE__; \ +} while (0) + +#else +#define QMD_TRACE_ELEM(elem) +#define QMD_TRACE_HEAD(head) +#define QMD_SAVELINK(name, link) +#define TRACEBUF +#define TRACEBUF_INITIALIZER +#define TRASHIT(x) +#endif /* QUEUE_MACRO_DEBUG */ + +/* + * Singly-linked List declarations. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) + +#define SLIST_FIRST(head) ((head)->slh_first) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST((head)); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != NULL; \ + (varp) = &SLIST_NEXT((var), field)) + +#define SLIST_INIT(head) do { \ + SLIST_FIRST((head)) = NULL; \ +} while (0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ + SLIST_NEXT((slistelm), field) = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ + SLIST_FIRST((head)) = (elm); \ +} while (0) + +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ + if (SLIST_FIRST((head)) == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = SLIST_FIRST((head)); \ + while (SLIST_NEXT(curelm, field) != (elm)) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_REMOVE_AFTER(curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + SLIST_NEXT(elm, field) = \ + SLIST_NEXT(SLIST_NEXT(elm, field), field); \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ +} while (0) + +#define SLIST_SWAP(head1, head2, type) do { \ + struct type *swap_first = SLIST_FIRST(head1); \ + SLIST_FIRST(head1) = SLIST_FIRST(head2); \ + SLIST_FIRST(head2) = swap_first; \ +} while (0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (0) + +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ +} while (0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ +} while (0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? NULL : \ + __containerof((head)->stqh_last, struct type, field.stqe_next)) + +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = STAILQ_FIRST((head)); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_SWAP(head1, head2, type) do { \ + struct type *swap_first = STAILQ_FIRST(head1); \ + struct type **swap_last = (head1)->stqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &STAILQ_FIRST(head2); \ +} while (0) + + +/* + * List declarations. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ + +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_LIST_CHECK_HEAD(head, field) do { \ + if (LIST_FIRST((head)) != NULL && \ + LIST_FIRST((head))->field.le_prev != \ + &LIST_FIRST((head))) \ + panic("Bad list head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_LIST_CHECK_NEXT(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL && \ + LIST_NEXT((elm), field)->field.le_prev != \ + &((elm)->field.le_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_LIST_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.le_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_LIST_CHECK_HEAD(head, field) +#define QMD_LIST_CHECK_NEXT(elm, field) +#define QMD_LIST_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_INIT(head) do { \ + LIST_FIRST((head)) = NULL; \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QMD_LIST_CHECK_NEXT(listelm, field); \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_LIST_CHECK_PREV(listelm, field); \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QMD_LIST_CHECK_HEAD((head), field); \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_PREV(elm, head, type, field) \ + ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ + __containerof((elm)->field.le_prev, struct type, field.le_next)) + +#define LIST_REMOVE(elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.le_next); \ + QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ + QMD_LIST_CHECK_NEXT(elm, field); \ + QMD_LIST_CHECK_PREV(elm, field); \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ +} while (0) + +#define LIST_SWAP(head1, head2, type, field) do { \ + struct type *swap_tmp = LIST_FIRST((head1)); \ + LIST_FIRST((head1)) = LIST_FIRST((head2)); \ + LIST_FIRST((head2)) = swap_tmp; \ + if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ + if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ +} while (0) + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +/* + * Tail queue functions. + */ +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_TAILQ_CHECK_HEAD(head, field) do { \ + if (!TAILQ_EMPTY(head) && \ + TAILQ_FIRST((head))->field.tqe_prev != \ + &TAILQ_FIRST((head))) \ + panic("Bad tailq head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_TAIL(head, field) do { \ + if (*(head)->tqh_last != NULL) \ + panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ + if (TAILQ_NEXT((elm), field) != NULL && \ + TAILQ_NEXT((elm), field)->field.tqe_prev != \ + &((elm)->field.tqe_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_TAILQ_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.tqe_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_TAILQ_CHECK_HEAD(head, field) +#define QMD_TAILQ_CHECK_TAIL(head, headname) +#define QMD_TAILQ_CHECK_NEXT(elm, field) +#define QMD_TAILQ_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + QMD_TRACE_HEAD(head1); \ + QMD_TRACE_HEAD(head2); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QMD_TAILQ_CHECK_NEXT(listelm, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_TAILQ_CHECK_PREV(listelm, field); \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QMD_TAILQ_CHECK_HEAD(head, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QMD_TAILQ_CHECK_TAIL(head, field); \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ + QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ + QMD_TAILQ_CHECK_NEXT(elm, field); \ + QMD_TAILQ_CHECK_PREV(elm, field); \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + QMD_TRACE_HEAD(head); \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type, field) do { \ + struct type *swap_first = (head1)->tqh_first; \ + struct type **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/include/misc/tree.h b/lib/lwext4_rust/c/lwext4/include/misc/tree.h new file mode 100644 index 0000000..8ed5d41 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/include/misc/tree.h @@ -0,0 +1,809 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../ext4_config.h" + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ + RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \ + RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \ + RB_PROTOTYPE_INSERT(name, type, attr); \ + RB_PROTOTYPE_REMOVE(name, type, attr); \ + RB_PROTOTYPE_FIND(name, type, attr); \ + RB_PROTOTYPE_NFIND(name, type, attr); \ + RB_PROTOTYPE_NEXT(name, type, attr); \ + RB_PROTOTYPE_PREV(name, type, attr); \ + RB_PROTOTYPE_MINMAX(name, type, attr); +#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ + attr void name##_RB_INSERT_COLOR(struct name *, struct type *) +#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ + attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *) +#define RB_PROTOTYPE_REMOVE(name, type, attr) \ + attr struct type *name##_RB_REMOVE(struct name *, struct type *) +#define RB_PROTOTYPE_INSERT(name, type, attr) \ + attr struct type *name##_RB_INSERT(struct name *, struct type *) +#define RB_PROTOTYPE_FIND(name, type, attr) \ + attr struct type *name##_RB_FIND(struct name *, struct type *) +#define RB_PROTOTYPE_NFIND(name, type, attr) \ + attr struct type *name##_RB_NFIND(struct name *, struct type *) +#define RB_PROTOTYPE_NEXT(name, type, attr) \ + attr struct type *name##_RB_NEXT(struct type *) +#define RB_PROTOTYPE_PREV(name, type, attr) \ + attr struct type *name##_RB_PREV(struct type *) +#define RB_PROTOTYPE_MINMAX(name, type, attr) \ + attr struct type *name##_RB_MINMAX(struct name *, int) + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ + RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ + RB_GENERATE_INSERT(name, type, field, cmp, attr) \ + RB_GENERATE_REMOVE(name, type, field, attr) \ + RB_GENERATE_FIND(name, type, field, cmp, attr) \ + RB_GENERATE_NFIND(name, type, field, cmp, attr) \ + RB_GENERATE_NEXT(name, type, field, attr) \ + RB_GENERATE_PREV(name, type, field, attr) \ + RB_GENERATE_MINMAX(name, type, field, attr) + +#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) != NULL && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)) \ + != NULL) \ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)) \ + != NULL) \ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE(name, type, field, attr) \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field)) != NULL) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field)) != NULL); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + +#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} + +#define RB_GENERATE_FIND(name, type, field, cmp, attr) \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} + +#define RB_GENERATE_NFIND(name, type, field, cmp, attr) \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} + +#define RB_GENERATE_NEXT(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_PREV(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_MINMAX(name, type, field, attr) \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TREE_H_ */ diff --git a/lib/lwext4_rust/c/lwext4/src/CMakeLists.txt b/lib/lwext4_rust/c/lwext4/src/CMakeLists.txt new file mode 100644 index 0000000..23c5299 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/CMakeLists.txt @@ -0,0 +1,29 @@ + +option(LWEXT4_BUILD_SHARED_LIB "Build shared library" OFF) +option(LWEXT4_ULIBC "Build ulibc" OFF) + +#LIBRARY +include_directories(.) +aux_source_directory(. LWEXT4_SRC) +set(M_SRC "ulibc.c") + +if(LWEXT4_ULIBC) + add_definitions(-DLWEXT4_ULIBC) +endif() + +if(LWEXT4_BUILD_SHARED_LIB) + add_library(lwext4 SHARED ${LWEXT4_SRC}) +else() + add_library(lwext4 STATIC ${LWEXT4_SRC} ${M_SRC}) +endif() + +if (DEFINED SIZE) + add_custom_target(lib_size ALL DEPENDS lwext4 COMMAND ${SIZE} liblwext4.a) +else() + +endif() + +if (DEFINED INSTALL_LIB) +INSTALL(TARGETS lwext4 DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) +INSTALL(DIRECTORY ${PROJECT_BINARY_DIR}/include/. DESTINATION ${CMAKE_INSTALL_PREFIX}/include/lwext4) +endif() diff --git a/lib/lwext4_rust/c/lwext4/src/ext4.c b/lib/lwext4_rust/c/lwext4/src/ext4.c new file mode 100644 index 0000000..90ce45e --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4.c @@ -0,0 +1,3241 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4.h + * @brief Ext4 high level operations (file, directory, mountpoints...) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +/**@brief Mount point OS dependent lock*/ +#define EXT4_MP_LOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->lock(); \ + } while (0) + +/**@brief Mount point OS dependent unlock*/ +#define EXT4_MP_UNLOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->unlock(); \ + } while (0) + +/**@brief Mount point descriptor.*/ +struct ext4_mountpoint { + + /**@brief Mount done flag.*/ + bool mounted; + + /**@brief Mount point name (@ref ext4_mount)*/ + char name[CONFIG_EXT4_MAX_MP_NAME + 1]; + + /**@brief OS dependent lock/unlock functions.*/ + const struct ext4_lock *os_locks; + + /**@brief Ext4 filesystem internals.*/ + struct ext4_fs fs; + + /**@brief JBD fs.*/ + struct jbd_fs jbd_fs; + + /**@brief Journal.*/ + struct jbd_journal jbd_journal; + + /**@brief Block cache.*/ + struct ext4_bcache bc; +}; + +/**@brief Block devices descriptor.*/ +struct ext4_block_devices { + + /**@brief Block device name.*/ + char name[CONFIG_EXT4_MAX_BLOCKDEV_NAME + 1]; + + /**@brief Block device handle.*/ + struct ext4_blockdev *bd; +}; + +/**@brief Block devices.*/ +static struct ext4_block_devices s_bdevices[CONFIG_EXT4_BLOCKDEVS_COUNT]; + +/**@brief Mountpoints.*/ +static struct ext4_mountpoint s_mp[CONFIG_EXT4_MOUNTPOINTS_COUNT]; + +int ext4_device_register(struct ext4_blockdev *bd, + const char *dev_name) +{ + ext4_assert(bd && dev_name); + + if (strlen(dev_name) > CONFIG_EXT4_MAX_BLOCKDEV_NAME) + return EINVAL; + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!strcmp(s_bdevices[i].name, dev_name)) + return EEXIST; + } + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!s_bdevices[i].bd) { + strcpy(s_bdevices[i].name, dev_name); + s_bdevices[i].bd = bd; + return EOK; + } + } + + return ENOSPC; +} + +int ext4_device_unregister(const char *dev_name) +{ + ext4_assert(dev_name); + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (strcmp(s_bdevices[i].name, dev_name)) + continue; + + memset(&s_bdevices[i], 0, sizeof(s_bdevices[i])); + return EOK; + } + + return ENOENT; +} + +int ext4_device_unregister_all(void) +{ + memset(s_bdevices, 0, sizeof(s_bdevices)); + + return EOK; +} + +/****************************************************************************/ + +static bool ext4_is_dots(const uint8_t *name, size_t name_size) +{ + if ((name_size == 1) && (name[0] == '.')) + return true; + + if ((name_size == 2) && (name[0] == '.') && (name[1] == '.')) + return true; + + return false; +} + +static int ext4_has_children(bool *has_children, struct ext4_inode_ref *enode) +{ + struct ext4_sblock *sb = &enode->fs->sb; + + /* Check if node is directory */ + if (!ext4_inode_is_type(sb, enode->inode, EXT4_INODE_MODE_DIRECTORY)) { + *has_children = false; + return EOK; + } + + struct ext4_dir_iter it; + int rc = ext4_dir_iterator_init(&it, enode, 0); + if (rc != EOK) + return rc; + + /* Find a non-empty directory entry */ + bool found = false; + while (it.curr != NULL) { + if (ext4_dir_en_get_inode(it.curr) != 0) { + uint16_t nsize; + nsize = ext4_dir_en_get_name_len(sb, it.curr); + if (!ext4_is_dots(it.curr->name, nsize)) { + found = true; + break; + } + } + + rc = ext4_dir_iterator_next(&it); + if (rc != EOK) { + ext4_dir_iterator_fini(&it); + return rc; + } + } + + rc = ext4_dir_iterator_fini(&it); + if (rc != EOK) + return rc; + + *has_children = found; + + return EOK; +} + +static int ext4_link(struct ext4_mountpoint *mp, struct ext4_inode_ref *parent, + struct ext4_inode_ref *ch, const char *n, + uint32_t len, bool rename) +{ + /* Check maximum name length */ + if (len > EXT4_DIRECTORY_FILENAME_LEN) + return EINVAL; + + /* Add entry to parent directory */ + int r = ext4_dir_add_entry(parent, n, len, ch); + if (r != EOK) + return r; + + /* Fill new dir -> add '.' and '..' entries. + * Also newly allocated inode should have 0 link count. + */ + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, ch->inode, + EXT4_INODE_MODE_DIRECTORY); + if (is_dir && !rename) { + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(ch, parent); + if (r != EOK) + return r; + + ext4_inode_set_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + ch->dirty = true; + } else +#endif + { + r = ext4_dir_add_entry(ch, ".", strlen("."), ch); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + return r; + } + + r = ext4_dir_add_entry(ch, "..", strlen(".."), parent); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + ext4_dir_remove_entry(ch, ".", strlen(".")); + return r; + } + } + + /*New empty directory. Two links (. and ..) */ + ext4_inode_set_links_cnt(ch->inode, 2); + ext4_fs_inode_links_count_inc(parent); + ch->dirty = true; + parent->dirty = true; + return r; + } + /* + * In case we want to rename a directory, + * we reset the original '..' pointer. + */ + if (is_dir) { + bool idx; + idx = ext4_inode_has_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + struct ext4_dir_search_result res; + if (!idx) { + r = ext4_dir_find_entry(&res, ch, "..", strlen("..")); + if (r != EOK) + return EIO; + + ext4_dir_en_set_inode(res.dentry, parent->index); + ext4_trans_set_block_dirty(res.block.buf); + r = ext4_dir_destroy_result(ch, &res); + if (r != EOK) + return r; + + } else { +#if CONFIG_DIR_INDEX_ENABLE + r = ext4_dir_dx_reset_parent_inode(ch, parent->index); + if (r != EOK) + return r; + +#endif + } + + ext4_fs_inode_links_count_inc(parent); + parent->dirty = true; + } + if (!rename) { + ext4_fs_inode_links_count_inc(ch); + ch->dirty = true; + } + + return r; +} + +static int ext4_unlink(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + bool has_children; + int rc = ext4_has_children(&has_children, child); + if (rc != EOK) + return rc; + + /* Cannot unlink non-empty node */ + if (has_children) + return ENOTEMPTY; + + /* Remove entry from parent directory */ + rc = ext4_dir_remove_entry(parent, name, name_len); + if (rc != EOK) + return rc; + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, child->inode, + EXT4_INODE_MODE_DIRECTORY); + + /* If directory - handle links from parent */ + if (is_dir) { + ext4_fs_inode_links_count_dec(parent); + parent->dirty = true; + } + + /* + * TODO: Update timestamps of the parent + * (when we have wall-clock time). + * + * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now); + * ext4_inode_set_modification_time(parent->inode, (uint32_t) now); + * parent->dirty = true; + */ + + /* + * TODO: Update timestamp for inode. + * + * ext4_inode_set_change_inode_time(child->inode, + * (uint32_t) now); + */ + if (ext4_inode_get_links_cnt(child->inode)) { + ext4_fs_inode_links_count_dec(child); + child->dirty = true; + } + + return EOK; +} + +/****************************************************************************/ + +int ext4_mount(const char *dev_name, const char *mount_point, + bool read_only) +{ + int r; + uint32_t bsize; + struct ext4_bcache *bc; + struct ext4_blockdev *bd = 0; + struct ext4_mountpoint *mp = 0; + + ext4_assert(mount_point && dev_name); + + size_t mp_len = strlen(mount_point); + + if (mp_len > CONFIG_EXT4_MAX_MP_NAME) + return EINVAL; + + if (mount_point[mp_len - 1] != '/') + return ENOTSUP; + + for (size_t i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!strcmp(dev_name, s_bdevices[i].name)) { + bd = s_bdevices[i].bd; + break; + } + } + + if (!bd) + return ENODEV; + + for (size_t i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!s_mp[i].mounted) { + strcpy(s_mp[i].name, mount_point); + mp = &s_mp[i]; + break; + } + + if (!strcmp(s_mp[i].name, mount_point)) + return EOK; + } + + if (!mp) + return ENOMEM; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + r = ext4_fs_init(&mp->fs, bd, read_only); + if (r != EOK) { + ext4_block_fini(bd); + return r; + } + + bsize = ext4_sb_get_block_size(&mp->fs.sb); + ext4_block_set_lb_size(bd, bsize); + bc = &mp->bc; + + r = ext4_bcache_init_dynamic(bc, CONFIG_BLOCK_DEV_CACHE_SIZE, bsize); + if (r != EOK) { + ext4_block_fini(bd); + return r; + } + + if (bsize != bc->itemsize) + return ENOTSUP; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, bc); + if (r != EOK) { + ext4_bcache_cleanup(bc); + ext4_block_fini(bd); + ext4_bcache_fini_dynamic(bc); + return r; + } + + bd->fs = &mp->fs; + mp->mounted = 1; + return r; +} + + +int ext4_umount(const char *mount_point) +{ + int i; + int r; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(s_mp[i].name, mount_point)) { + mp = &s_mp[i]; + break; + } + } + + if (!mp) + return ENODEV; + + r = ext4_fs_fini(&mp->fs); + if (r != EOK) + goto Finish; + + mp->mounted = 0; + + ext4_bcache_cleanup(mp->fs.bdev->bc); + ext4_bcache_fini_dynamic(mp->fs.bdev->bc); + + r = ext4_block_fini(mp->fs.bdev); +Finish: + mp->fs.bdev->fs = NULL; + return r; +} + +static struct ext4_mountpoint *ext4_get_mount(const char *path) +{ + for (size_t i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + + if (!s_mp[i].mounted) + continue; + + if (!strncmp(s_mp[i].name, path, strlen(s_mp[i].name))) + return &s_mp[i]; + } + + return NULL; +} + +__unused +static int __ext4_journal_start(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EOK; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_get_fs(&mp->fs, &mp->jbd_fs); + if (r != EOK) + goto Finish; + + r = jbd_journal_start(&mp->jbd_fs, &mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + goto Finish; + } + mp->fs.jbd_fs = &mp->jbd_fs; + mp->fs.jbd_journal = &mp->jbd_journal; + } +Finish: + return r; +} + +__unused +static int __ext4_journal_stop(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EOK; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_journal_stop(&mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + r = jbd_put_fs(&mp->jbd_fs); + if (r != EOK) { + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + } +Finish: + return r; +} + +__unused +static int __ext4_recover(const char *mount_point) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + int r = ENOTSUP; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_HAS_JOURNAL)) { + struct jbd_fs *jbd_fs = ext4_calloc(1, sizeof(struct jbd_fs)); + if (!jbd_fs) { + r = ENOMEM; + goto Finish; + } + + r = jbd_get_fs(&mp->fs, jbd_fs); + if (r != EOK) { + ext4_free(jbd_fs); + goto Finish; + } + + r = jbd_recover(jbd_fs); + jbd_put_fs(jbd_fs); + ext4_free(jbd_fs); + } + if (r == EOK && !mp->fs.read_only) { + uint32_t bgid; + uint64_t free_blocks_count = 0; + uint32_t free_inodes_count = 0; + struct ext4_block_group_ref bg_ref; + + /* Update superblock's stats */ + for (bgid = 0;bgid < ext4_block_group_cnt(&mp->fs.sb);bgid++) { + r = ext4_fs_get_block_group_ref(&mp->fs, bgid, &bg_ref); + if (r != EOK) + goto Finish; + + free_blocks_count += + ext4_bg_get_free_blocks_count(bg_ref.block_group, + &mp->fs.sb); + free_inodes_count += + ext4_bg_get_free_inodes_count(bg_ref.block_group, + &mp->fs.sb); + + ext4_fs_put_block_group_ref(&bg_ref); + } + ext4_sb_set_free_blocks_cnt(&mp->fs.sb, free_blocks_count); + ext4_set32(&mp->fs.sb, free_inodes_count, free_inodes_count); + /* We don't need to save the superblock stats immediately. */ + } + +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +__unused +static int __ext4_trans_start(struct ext4_mountpoint *mp) +{ + int r = EOK; + + if (mp->fs.jbd_journal && !mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans; + trans = jbd_journal_new_trans(journal); + if (!trans) { + r = ENOMEM; + goto Finish; + } + mp->fs.curr_trans = trans; + } +Finish: + return r; +} + +__unused +static int __ext4_trans_stop(struct ext4_mountpoint *mp) +{ + int r = EOK; + + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + r = jbd_journal_commit_trans(journal, trans); + mp->fs.curr_trans = NULL; + } + return r; +} + +__unused +static void __ext4_trans_abort(struct ext4_mountpoint *mp) +{ + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + jbd_journal_free_trans(journal, trans, true); + mp->fs.curr_trans = NULL; + } +} + +int ext4_journal_start(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_start(mount_point); +#endif + return r; +} + +int ext4_journal_stop(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_stop(mount_point); +#endif + return r; +} + +int ext4_recover(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_recover(mount_point); +#endif + return r; +} + +static int ext4_trans_start(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_start(mp); +#endif + return r; +} + +static int ext4_trans_stop(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_stop(mp); +#endif + return r; +} + +static void ext4_trans_abort(struct ext4_mountpoint *mp __unused) +{ +#if CONFIG_JOURNALING_ENABLE + __ext4_trans_abort(mp); +#endif +} + + +int ext4_mount_point_stats(const char *mount_point, + struct ext4_mount_stats *stats) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + stats->inodes_count = ext4_get32(&mp->fs.sb, inodes_count); + stats->free_inodes_count = ext4_get32(&mp->fs.sb, free_inodes_count); + stats->blocks_count = ext4_sb_get_blocks_cnt(&mp->fs.sb); + stats->free_blocks_count = ext4_sb_get_free_blocks_cnt(&mp->fs.sb); + stats->block_size = ext4_sb_get_block_size(&mp->fs.sb); + + stats->block_group_count = ext4_block_group_cnt(&mp->fs.sb); + stats->blocks_per_group = ext4_get32(&mp->fs.sb, blocks_per_group); + stats->inodes_per_group = ext4_get32(&mp->fs.sb, inodes_per_group); + + memcpy(stats->volume_name, mp->fs.sb.volume_name, 16); + EXT4_MP_UNLOCK(mp); + + return EOK; +} + +int ext4_mount_setup_locks(const char *mount_point, + const struct ext4_lock *locks) +{ + uint32_t i; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(s_mp[i].name, mount_point)) { + mp = &s_mp[i]; + break; + } + } + if (!mp) + return ENOENT; + + mp->os_locks = locks; + return EOK; +} + +/********************************FILE OPERATIONS*****************************/ + +static int ext4_path_check(const char *path, bool *is_goal) +{ + int i; + + for (i = 0; i < EXT4_DIRECTORY_FILENAME_LEN; ++i) { + + if (path[i] == '/') { + *is_goal = false; + return i; + } + + if (path[i] == 0) { + *is_goal = true; + return i; + } + } + + return 0; +} + +static bool ext4_parse_flags(const char *flags, uint32_t *file_flags) +{ + if (!flags) + return false; + + if (!strcmp(flags, "r") || !strcmp(flags, "rb")) { + *file_flags = O_RDONLY; + return true; + } + + if (!strcmp(flags, "w") || !strcmp(flags, "wb")) { + *file_flags = O_WRONLY | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a") || !strcmp(flags, "ab")) { + *file_flags = O_WRONLY | O_CREAT | O_APPEND; + return true; + } + + if (!strcmp(flags, "r+") || !strcmp(flags, "rb+") || + !strcmp(flags, "r+b")) { + *file_flags = O_RDWR; + return true; + } + + if (!strcmp(flags, "w+") || !strcmp(flags, "wb+") || + !strcmp(flags, "w+b")) { + *file_flags = O_RDWR | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a+") || !strcmp(flags, "ab+") || + !strcmp(flags, "a+b")) { + *file_flags = O_RDWR | O_CREAT | O_APPEND; + return true; + } + + return false; +} + +static int ext4_trunc_inode(struct ext4_mountpoint *mp, + uint32_t index, uint64_t new_size) +{ + int r = EOK; + struct ext4_fs *const fs = &mp->fs; + struct ext4_inode_ref inode_ref; + uint64_t inode_size; + bool has_trans = mp->fs.jbd_journal && mp->fs.curr_trans; + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) + return r; + + inode_size = ext4_inode_get_size(&fs->sb, inode_ref.inode); + ext4_fs_put_inode_ref(&inode_ref); + if (has_trans) + ext4_trans_stop(mp); + + while (inode_size > new_size + CONFIG_MAX_TRUNCATE_SIZE) { + + inode_size -= CONFIG_MAX_TRUNCATE_SIZE; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + break; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } else + ext4_trans_stop(mp); + } + + if (inode_size > new_size) { + + inode_size = new_size; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + } + +Finish: + + if (has_trans) + ext4_trans_start(mp); + + return r; +} + +static int ext4_trunc_dir(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *dir) +{ + int r = EOK; + bool is_dir = ext4_inode_is_type(&mp->fs.sb, dir->inode, + EXT4_INODE_MODE_DIRECTORY); + uint32_t block_size = ext4_sb_get_block_size(&mp->fs.sb); + if (!is_dir) + return EINVAL; + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(dir, parent); + if (r != EOK) + return r; + + r = ext4_trunc_inode(mp, dir->index, + EXT4_DIR_DX_INIT_BCNT * block_size); + if (r != EOK) + return r; + } else +#endif + { + r = ext4_trunc_inode(mp, dir->index, block_size); + if (r != EOK) + return r; + } + + return ext4_fs_truncate_inode(dir, 0); +} + +/* + * NOTICE: if filetype is equal to EXT4_DIRENTRY_UNKNOWN, + * any filetype of the target dir entry will be accepted. + */ +static int ext4_generic_open2(ext4_file *f, const char *path, int flags, + int ftype, uint32_t *parent_inode, + uint32_t *name_off) +{ + bool is_goal = false; + uint32_t imode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + f->mp = 0; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + if (fs->read_only && flags & O_CREAT) + return EROFS; + + f->flags = flags; + + /*Skip mount point*/ + path += strlen(mp->name); + + if (name_off) + *name_off = strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + if (parent_inode) + *parent_inode = ref.index; + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + if (ftype == EXT4_DE_DIR || ftype == EXT4_DE_UNKNOWN) + if (is_goal) + break; + + r = ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + if (r != ENOENT) + break; + + if (!(f->flags & O_CREAT)) + break; + + /*O_CREAT allows create new entry*/ + struct ext4_inode_ref child_ref; + r = ext4_fs_alloc_inode(fs, &child_ref, + is_goal ? ftype : EXT4_DE_DIR); + + if (r != EOK) + break; + + ext4_fs_inode_blocks_init(fs, &child_ref); + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, &child_ref, path, len, false); + if (r != EOK) { + /*Fail. Free new inode.*/ + ext4_fs_free_inode(&child_ref); + /*We do not want to write new inode. + But block has to be released.*/ + child_ref.dirty = false; + ext4_fs_put_inode_ref(&child_ref); + break; + } + + ext4_fs_put_inode_ref(&child_ref); + continue; + } + + if (parent_inode) + *parent_inode = ref.index; + + next_inode = ext4_dir_en_get_inode(result.dentry); + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + imode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + imode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + /*If expected file error*/ + if (imode != EXT4_INODE_MODE_DIRECTORY && !is_goal) { + r = ENOENT; + break; + } + if (ftype != EXT4_DE_UNKNOWN) { + bool df = imode != ext4_fs_correspond_inode_mode(ftype); + if (df && is_goal) { + r = ENOENT; + break; + } + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + + if (name_off) + *name_off += len + 1; + } + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + if (is_goal) { + + if ((f->flags & O_TRUNC) && (imode == EXT4_INODE_MODE_FILE)) { + r = ext4_trunc_inode(mp, ref.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + } + + f->mp = mp; + f->fsize = ext4_inode_get_size(sb, ref.inode); + f->inode = ref.index; + f->fpos = 0; + + if (f->flags & O_APPEND) + f->fpos = f->fsize; + } + + return ext4_fs_put_inode_ref(&ref); +} + +/****************************************************************************/ + +static int ext4_generic_open(ext4_file *f, const char *path, const char *flags, + bool file_expect, uint32_t *parent_inode, + uint32_t *name_off) +{ + uint32_t iflags; + int filetype; + int r; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (ext4_parse_flags(flags, &iflags) == false) + return EINVAL; + + if (file_expect == true) + filetype = EXT4_DE_REG_FILE; + else + filetype = EXT4_DE_DIR; + + if (iflags & O_CREAT) + ext4_trans_start(mp); + + r = ext4_generic_open2(f, path, iflags, filetype, parent_inode, + name_off); + + if (iflags & O_CREAT) { + if (r == EOK) + ext4_trans_stop(mp); + else + ext4_trans_abort(mp); + } + + return r; +} + +static int ext4_create_hardlink(const char *path, + struct ext4_inode_ref *child_ref, bool rename) +{ + bool is_goal = false; + uint32_t inode_mode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + /*Skip mount point*/ + path += strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + r = is_goal ? EINVAL : ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + + if (r != ENOENT || !is_goal) + break; + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, child_ref, path, len, rename); + break; + } else if (r == EOK && is_goal) { + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + r = EEXIST; + break; + } + + next_inode = result.dentry->inode; + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + inode_mode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + inode_mode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + if (inode_mode != EXT4_INODE_MODE_DIRECTORY) { + r = is_goal ? EEXIST : ENOENT; + break; + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + }; + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + r = ext4_fs_put_inode_ref(&ref); + return r; +} + +static int ext4_remove_orig_reference(const char *path, uint32_t name_off, + struct ext4_inode_ref *parent_ref, + struct ext4_inode_ref *child_ref) +{ + bool is_goal; + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /* Remove entry from parent directory */ + r = ext4_dir_remove_entry(parent_ref, path, len); + if (r != EOK) + goto Finish; + + if (ext4_inode_is_type(&mp->fs.sb, child_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + ext4_fs_inode_links_count_dec(parent_ref); + parent_ref->dirty = true; + } +Finish: + return r; +} + +int ext4_flink(const char *path, const char *hardlink_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_mountpoint *target_mp = ext4_get_mount(hardlink_path); + struct ext4_inode_ref child_ref; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + /* Will that happen? Anyway return EINVAL for such case. */ + if (mp != target_mp) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + /* Creating hardlink for directory is not allowed. */ + if (ext4_inode_is_type(&mp->fs.sb, child_ref.inode, + EXT4_INODE_MODE_DIRECTORY)) { + r = EINVAL; + goto Finish; + } + + r = ext4_create_hardlink(hardlink_path, &child_ref, false); + +Finish: + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +int ext4_frename(const char *path, const char *new_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool parent_loaded = false, child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref child_ref, parent_ref; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent_ref); + if (r != EOK) + goto Finish; + + parent_loaded = true; + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + r = ext4_create_hardlink(new_path, &child_ref, true); + if (r != EOK) + goto Finish; + + r = ext4_remove_orig_reference(path, name_off, &parent_ref, &child_ref); + if (r != EOK) + goto Finish; + +Finish: + if (parent_loaded) + ext4_fs_put_inode_ref(&parent_ref); + + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/****************************************************************************/ + +int ext4_get_sblock(const char *mount_point, struct ext4_sblock **sb) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + *sb = &mp->fs.sb; + return EOK; +} + +int ext4_cache_write_back(const char *path, bool on) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int ret; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ret = ext4_block_cache_write_back(mp->fs.bdev, on); + EXT4_MP_UNLOCK(mp); + return ret; +} + +int ext4_cache_flush(const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int ret; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ret = ext4_block_cache_flush(mp->fs.bdev); + EXT4_MP_UNLOCK(mp); + return ret; +} + +int ext4_fremove(const char *path) +{ + ext4_file f; + uint32_t parent_inode; + uint32_t child_inode; + uint32_t name_off; + bool is_goal; + int r; + int len; + struct ext4_inode_ref child; + struct ext4_inode_ref parent; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + child_inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*We have file to delete. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + /* We do not allow opening files here. */ + if (ext4_inode_type(&mp->fs.sb, child.inode) == + EXT4_INODE_MODE_DIRECTORY) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Link count will be zero, the inode should be freed. */ + if (ext4_inode_get_links_cnt(child.inode) == 1) { + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_trunc_inode(mp, child.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ext4_block_cache_write_back(mp->fs.bdev, 0); + } + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /*Unlink from parent*/ + r = ext4_unlink(mp, &parent, &child, path, len); + if (r != EOK) + goto Finish; + + /*Link count is zero, the inode should be freed. */ + if (!ext4_inode_get_links_cnt(child.inode)) { + ext4_inode_set_del_time(child.inode, -1L); + + r = ext4_fs_free_inode(&child); + if (r != EOK) + goto Finish; + } + +Finish: + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&parent); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fopen(ext4_file *file, const char *path, const char *flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open(file, path, flags, true, 0, 0); + ext4_block_cache_write_back(mp->fs.bdev, 0); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fopen2(ext4_file *file, const char *path, int flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + int filetype; + + if (!mp) + return ENOENT; + + filetype = EXT4_DE_REG_FILE; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + + if (flags & O_CREAT) + ext4_trans_start(mp); + + r = ext4_generic_open2(file, path, flags, filetype, NULL, NULL); + + if (flags & O_CREAT) { + if (r == EOK) + ext4_trans_stop(mp); + else + ext4_trans_abort(mp); + } + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_fclose(ext4_file *file) +{ + ext4_assert(file && file->mp); + + file->mp = 0; + file->flags = 0; + file->inode = 0; + file->fpos = file->fsize = 0; + + return EOK; +} + +static int ext4_ftruncate_no_lock(ext4_file *file, uint64_t size) +{ + struct ext4_inode_ref ref; + int r; + + + r = ext4_fs_get_inode_ref(&file->mp->fs, file->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(&file->mp->fs.sb, ref.inode); + if (file->fsize <= size) { + r = EOK; + goto Finish; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(file->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + r = ext4_trunc_inode(file->mp, ref.index, size); + if (r != EOK) + goto Finish; + + file->fsize = size; + if (file->fpos > size) + file->fpos = size; + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(file->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; + +} + +int ext4_ftruncate(ext4_file *f, uint64_t size) +{ + int r; + ext4_assert(f && f->mp); + + if (f->mp->fs.read_only) + return EROFS; + + if (f->flags & O_RDONLY) + return EPERM; + + EXT4_MP_LOCK(f->mp); + + ext4_trans_start(f->mp); + r = ext4_ftruncate_no_lock(f, size); + if (r != EOK) + ext4_trans_abort(f->mp); + else + ext4_trans_stop(f->mp); + + EXT4_MP_UNLOCK(f->mp); + return r; +} + +int ext4_fread(ext4_file *file, void *buf, size_t size, size_t *rcnt) +{ + uint32_t unalg; + uint32_t iblock_idx; + uint32_t iblock_last; + uint32_t block_size; + + ext4_fsblk_t fblock; + ext4_fsblk_t fblock_start; + uint32_t fblock_count; + + uint8_t *u8_buf = buf; + int r; + struct ext4_inode_ref ref; + + ext4_assert(file && file->mp); + + if (file->flags & O_WRONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(file->mp); + + struct ext4_fs *const fs = &file->mp->fs; + struct ext4_sblock *const sb = &file->mp->fs.sb; + + if (rcnt) + *rcnt = 0; + + r = ext4_fs_get_inode_ref(fs, file->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(sb, ref.inode); + + block_size = ext4_sb_get_block_size(sb); + size = ((uint64_t)size > (file->fsize - file->fpos)) + ? ((size_t)(file->fsize - file->fpos)) : size; + + iblock_idx = (uint32_t)((file->fpos) / block_size); + iblock_last = (uint32_t)((file->fpos + size) / block_size); + unalg = (file->fpos) % block_size; + + /*If the size of symlink is smaller than 60 bytes*/ + bool softlink; + softlink = ext4_inode_is_type(sb, ref.inode, EXT4_INODE_MODE_SOFTLINK); + if (softlink && file->fsize < sizeof(ref.inode->blocks) + && !ext4_inode_get_blocks_count(sb, ref.inode)) { + + char *content = (char *)ref.inode->blocks; + if (file->fpos < file->fsize) { + size_t len = size; + if (unalg + size > (uint32_t)file->fsize) + len = (uint32_t)file->fsize - unalg; + memcpy(buf, content + unalg, len); + if (rcnt) + *rcnt = len; + + } + + r = EOK; + goto Finish; + } + + if (unalg) { + size_t len = size; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + /* Do we get an unwritten range? */ + if (fblock != 0) { + uint64_t off = fblock * block_size + unalg; + r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + } else { + /* Yes, we do. */ + memset(u8_buf, 0, len); + } + + u8_buf += len; + size -= len; + file->fpos += len; + + if (rcnt) + *rcnt += len; + + iblock_idx++; + } + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + while (iblock_idx < iblock_last) { + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, + &fblock, true); + if (r != EOK) + goto Finish; + + iblock_idx++; + + if (!fblock_start) + fblock_start = fblock; + + if ((fblock_start + fblock_count) != fblock) + break; + + fblock_count++; + } + + r = ext4_blocks_get_direct(file->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + goto Finish; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + file->fpos += block_size * fblock_count; + + if (rcnt) + *rcnt += block_size * fblock_count; + + fblock_start = fblock; + fblock_count = 1; + } + + if (size) { + uint64_t off; + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + off = fblock * block_size; + r = ext4_block_readbytes(file->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + file->fpos += size; + + if (rcnt) + *rcnt += size; + } + +Finish: + ext4_fs_put_inode_ref(&ref); + EXT4_MP_UNLOCK(file->mp); + return r; +} + +int ext4_fwrite(ext4_file *file, const void *buf, size_t size, size_t *wcnt) +{ + uint32_t unalg; + uint32_t iblk_idx; + uint32_t iblock_last; + uint32_t ifile_blocks; + uint32_t block_size; + + uint32_t fblock_count; + ext4_fsblk_t fblk; + ext4_fsblk_t fblock_start; + + struct ext4_inode_ref ref; + const uint8_t *u8_buf = buf; + int r, rr = EOK; + + ext4_assert(file && file->mp); + + if (file->mp->fs.read_only) + return EROFS; + + if (file->flags & O_RDONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(file->mp); + ext4_trans_start(file->mp); + + struct ext4_fs *const fs = &file->mp->fs; + struct ext4_sblock *const sb = &file->mp->fs.sb; + + if (wcnt) + *wcnt = 0; + + r = ext4_fs_get_inode_ref(fs, file->inode, &ref); + if (r != EOK) { + ext4_trans_abort(file->mp); + EXT4_MP_UNLOCK(file->mp); + return r; + } + + /*Sync file size*/ + file->fsize = ext4_inode_get_size(sb, ref.inode); + block_size = ext4_sb_get_block_size(sb); + + iblock_last = (uint32_t)((file->fpos + size) / block_size); + iblk_idx = (uint32_t)(file->fpos / block_size); + ifile_blocks = (uint32_t)((file->fsize + block_size - 1) / block_size); + + unalg = (file->fpos) % block_size; + + if (unalg) { + size_t len = size; + uint64_t off; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + + off = fblk * block_size + unalg; + r = ext4_block_writebytes(file->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + u8_buf += len; + size -= len; + file->fpos += len; + + if (wcnt) + *wcnt += len; + + iblk_idx++; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(file->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + + while (iblk_idx < iblock_last) { + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, + &fblk); + if (r != EOK) + goto Finish; + } else { + rr = ext4_fs_append_inode_dblk(&ref, &fblk, + &iblk_idx); + if (rr != EOK) { + /* Unable to append more blocks. But + * some block might be allocated already + * */ + break; + } + } + + iblk_idx++; + + if (!fblock_start) { + fblock_start = fblk; + } + + if ((fblock_start + fblock_count) != fblk) + break; + + fblock_count++; + } + + r = ext4_blocks_set_direct(file->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + break; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + file->fpos += block_size * fblock_count; + + if (wcnt) + *wcnt += block_size * fblock_count; + + fblock_start = fblk; + fblock_count = 1; + + if (rr != EOK) { + /*ext4_fs_append_inode_block has failed and no + * more blocks might be written. But node size + * should be updated.*/ + r = rr; + goto out_fsize; + } + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(file->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + if (size) { + uint64_t off; + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + } else { + r = ext4_fs_append_inode_dblk(&ref, &fblk, &iblk_idx); + if (r != EOK) + /*Node size sholud be updated.*/ + goto out_fsize; + } + + off = fblk * block_size; + r = ext4_block_writebytes(file->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + file->fpos += size; + + if (wcnt) + *wcnt += size; + } + +out_fsize: + if (file->fpos > file->fsize) { + file->fsize = file->fpos; + ext4_inode_set_size(ref.inode, file->fsize); + ref.dirty = true; + } + +Finish: + r = ext4_fs_put_inode_ref(&ref); + + if (r != EOK) + ext4_trans_abort(file->mp); + else + ext4_trans_stop(file->mp); + + EXT4_MP_UNLOCK(file->mp); + return r; +} + +int ext4_fseek(ext4_file *file, int64_t offset, uint32_t origin) +{ + switch (origin) { + case SEEK_SET: + if (offset < 0 || (uint64_t)offset > file->fsize) + return EINVAL; + + file->fpos = offset; + return EOK; + case SEEK_CUR: + if ((offset < 0 && (uint64_t)(-offset) > file->fpos) || + (offset > 0 && + (uint64_t)offset > (file->fsize - file->fpos))) + return EINVAL; + + file->fpos += offset; + return EOK; + case SEEK_END: + if (offset < 0 || (uint64_t)offset > file->fsize) + return EINVAL; + + file->fpos = file->fsize - offset; + return EOK; + } + return EINVAL; +} + +uint64_t ext4_ftell(ext4_file *file) +{ + return file->fpos; +} + +uint64_t ext4_fsize(ext4_file *file) +{ + return file->fsize; +} + + +static int ext4_trans_get_inode_ref(const char *path, + struct ext4_mountpoint *mp, + struct ext4_inode_ref *inode_ref) +{ + int r; + ext4_file f; + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + return r; + + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + return r; + } + + return r; +} + +static int ext4_trans_put_inode_ref(struct ext4_mountpoint *mp, + struct ext4_inode_ref *inode_ref) +{ + int r; + + r = ext4_fs_put_inode_ref(inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + return r; +} + + +int ext4_raw_inode_fill(const char *path, uint32_t *ret_ino, + struct ext4_inode *inode) +{ + int r; + ext4_file f; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + if (ret_ino) + *ret_ino = f.inode; + + memcpy(inode, inode_ref.inode, sizeof(struct ext4_inode)); + ext4_fs_put_inode_ref(&inode_ref); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_inode_exist(const char *path, int type) +{ + int r; + ext4_file f; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, type, NULL, NULL); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mode_set(const char *path, uint32_t mode) +{ + int r; + uint32_t orig_mode; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + orig_mode = ext4_inode_get_mode(&mp->fs.sb, inode_ref.inode); + orig_mode &= ~0xFFF; + orig_mode |= mode & 0xFFF; + ext4_inode_set_mode(&mp->fs.sb, inode_ref.inode, orig_mode); + + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_owner_set(const char *path, uint32_t uid, uint32_t gid) +{ + int r; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_uid(inode_ref.inode, uid); + ext4_inode_set_gid(inode_ref.inode, gid); + + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mode_get(const char *path, uint32_t *mode) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *mode = ext4_inode_get_mode(&mp->fs.sb, inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_owner_get(const char *path, uint32_t *uid, uint32_t *gid) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *uid = ext4_inode_get_uid(inode_ref.inode); + *gid = ext4_inode_get_gid(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_atime_set(const char *path, uint32_t atime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_access_time(inode_ref.inode, atime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mtime_set(const char *path, uint32_t mtime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_modif_time(inode_ref.inode, mtime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_ctime_set(const char *path, uint32_t ctime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + r = ext4_trans_get_inode_ref(path, mp, &inode_ref); + if (r != EOK) + goto Finish; + + ext4_inode_set_change_inode_time(inode_ref.inode, ctime); + inode_ref.dirty = true; + r = ext4_trans_put_inode_ref(mp, &inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_atime_get(const char *path, uint32_t *atime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *atime = ext4_inode_get_access_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_mtime_get(const char *path, uint32_t *mtime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *mtime = ext4_inode_get_modif_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_ctime_get(const char *path, uint32_t *ctime) +{ + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + ext4_file f; + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &inode_ref); + if (r != EOK) + goto Finish; + + *ctime = ext4_inode_get_change_inode_time(inode_ref.inode); + r = ext4_fs_put_inode_ref(&inode_ref); + + Finish: + EXT4_MP_UNLOCK(mp); + + return r; +} + +static int ext4_fsymlink_set(ext4_file *f, const void *buf, uint32_t size) +{ + struct ext4_inode_ref ref; + uint32_t sblock; + ext4_fsblk_t fblock; + uint32_t block_size; + int r; + + ext4_assert(f && f->mp); + + if (!size) + return EOK; + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) + return r; + + /*Sync file size*/ + block_size = ext4_sb_get_block_size(&f->mp->fs.sb); + if (size > block_size) { + r = EINVAL; + goto Finish; + } + r = ext4_ftruncate_no_lock(f, 0); + if (r != EOK) + goto Finish; + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(f->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + /*If the size of symlink is smaller than 60 bytes*/ + if (size < sizeof(ref.inode->blocks)) { + memset(ref.inode->blocks, 0, sizeof(ref.inode->blocks)); + memcpy(ref.inode->blocks, buf, size); + ext4_inode_clear_flag(ref.inode, EXT4_INODE_FLAG_EXTENTS); + } else { + uint64_t off; + ext4_fs_inode_blocks_init(&f->mp->fs, &ref); + r = ext4_fs_append_inode_dblk(&ref, &fblock, &sblock); + if (r != EOK) + goto Finish; + + off = fblock * block_size; + r = ext4_block_writebytes(f->mp->fs.bdev, off, buf, size); + if (r != EOK) + goto Finish; + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(f->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + ext4_inode_set_size(ref.inode, size); + ref.dirty = true; + + f->fsize = size; + if (f->fpos > size) + f->fpos = size; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; +} + +int ext4_fsymlink(const char *target, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR | O_CREAT, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fsymlink_set(&f, target, strlen(target)); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_readlink(const char *path, char *buf, size_t bufsize, size_t *rcnt) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + if (!buf) + return EINVAL; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open2(&f, path, O_RDONLY, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fread(&f, buf, bufsize, rcnt); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +static int ext4_mknod_set(ext4_file *f, uint32_t dev) +{ + struct ext4_inode_ref ref; + int r; + + ext4_assert(f && f->mp); + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) + return r; + + ext4_inode_set_dev(ref.inode, dev); + + ext4_inode_set_size(ref.inode, 0); + ref.dirty = true; + + f->fsize = 0; + f->fpos = 0; + + r = ext4_fs_put_inode_ref(&ref); + return r; +} + +int ext4_mknod(const char *path, int filetype, uint32_t dev) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + /* + * The filetype shouldn't be normal file, directory or + * unknown. + */ + if (filetype == EXT4_DE_UNKNOWN || + filetype == EXT4_DE_REG_FILE || + filetype == EXT4_DE_DIR || + filetype == EXT4_DE_SYMLINK) + return EINVAL; + + /* + * Nor should it be any bogus value. + */ + if (filetype != EXT4_DE_CHRDEV && + filetype != EXT4_DE_BLKDEV && + filetype != EXT4_DE_FIFO && + filetype != EXT4_DE_SOCK) + return EINVAL; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR | O_CREAT, filetype, NULL, NULL); + if (r == EOK) { + if (filetype == EXT4_DE_CHRDEV || + filetype == EXT4_DE_BLKDEV) + r = ext4_mknod_set(&f, dev); + } else { + goto Finish; + } + + ext4_fclose(&f); + +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_setxattr(const char *path, const char *name, size_t name_len, + const void *data, size_t data_size) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_set(&inode_ref, name_index, dissected_name, + dissected_len, data, data_size); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_getxattr(const char *path, const char *name, size_t name_len, + void *buf, size_t buf_size, size_t *data_size) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_get(&inode_ref, name_index, dissected_name, + dissected_len, buf, buf_size, data_size); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + size_t list_len, list_size = 0; + struct ext4_inode_ref inode_ref; + struct ext4_xattr_list_entry *xattr_list = NULL, + *entry = NULL; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_list(&inode_ref, NULL, &list_len); + if (r == EOK && list_len) { + xattr_list = ext4_malloc(list_len); + if (!xattr_list) { + ext4_fs_put_inode_ref(&inode_ref); + r = ENOMEM; + goto Finish; + } + entry = xattr_list; + r = ext4_xattr_list(&inode_ref, entry, &list_len); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + for (;entry;entry = entry->next) { + size_t prefix_len; + const char *prefix = + ext4_get_xattr_name_prefix(entry->name_index, + &prefix_len); + if (size) { + if (prefix_len + entry->name_len + 1 > size) { + ext4_fs_put_inode_ref(&inode_ref); + r = ERANGE; + goto Finish; + } + } + + if (list && size) { + memcpy(list, prefix, prefix_len); + list += prefix_len; + memcpy(list, entry->name, + entry->name_len); + list[entry->name_len] = 0; + list += entry->name_len + 1; + + size -= prefix_len + entry->name_len + 1; + } + + list_size += prefix_len + entry->name_len + 1; + } + if (ret_size) + *ret_size = list_size; + + } + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + if (xattr_list) + ext4_free(xattr_list); + + return r; + +} + +int ext4_removexattr(const char *path, const char *name, size_t name_len) +{ + bool found; + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len, + &found); + if (!found) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_LOCK(mp); + return r; + } + + inode = f.inode; + ext4_fclose(&f); + ext4_trans_start(mp); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_xattr_remove(&inode_ref, name_index, dissected_name, + dissected_len); + + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/*********************************DIRECTORY OPERATION************************/ + +int ext4_dir_rm(const char *path) +{ + int r; + int len; + ext4_file f; + + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref act; + struct ext4_inode_ref child; + struct ext4_dir_iter it; + + uint32_t name_off; + uint32_t inode_up; + uint32_t inode_current; + uint32_t depth = 1; + + bool has_children; + bool is_goal; + bool dir_end; + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + struct ext4_fs *const fs = &mp->fs; + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, &inode_up, &name_off); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + path += name_off; + len = ext4_path_check(path, &is_goal); + inode_current = f.inode; + + ext4_block_cache_write_back(mp->fs.bdev, 1); + + do { + + uint64_t act_curr_pos = 0; + has_children = false; + dir_end = false; + + while (r == EOK && !has_children && !dir_end) { + + /*Load directory node.*/ + r = ext4_fs_get_inode_ref(fs, inode_current, &act); + if (r != EOK) { + break; + } + + /*Initialize iterator.*/ + r = ext4_dir_iterator_init(&it, &act, act_curr_pos); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + break; + } + + if (!it.curr) { + dir_end = true; + goto End; + } + + ext4_trans_start(mp); + + /*Get up directory inode when ".." entry*/ + if ((it.curr->name_len == 2) && + ext4_is_dots(it.curr->name, it.curr->name_len)) { + inode_up = ext4_dir_en_get_inode(it.curr); + } + + /*If directory or file entry, but not "." ".." entry*/ + if (!ext4_is_dots(it.curr->name, it.curr->name_len)) { + + /*Get child inode reference do unlink + * directory/file.*/ + uint32_t cinode; + uint32_t inode_type; + cinode = ext4_dir_en_get_inode(it.curr); + r = ext4_fs_get_inode_ref(fs, cinode, &child); + if (r != EOK) + goto End; + + /*If directory with no leaf children*/ + r = ext4_has_children(&has_children, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + if (has_children) { + /*Has directory children. Go into this + * directory.*/ + inode_up = inode_current; + inode_current = cinode; + depth++; + ext4_fs_put_inode_ref(&child); + goto End; + } + inode_type = ext4_inode_type(&mp->fs.sb, + child.inode); + + /* Truncate */ + if (inode_type != EXT4_INODE_MODE_DIRECTORY) + r = ext4_trunc_inode(mp, child.index, 0); + else + r = ext4_trunc_dir(mp, &act, &child); + + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + /*No children in child directory or file. Just + * unlink.*/ + r = ext4_unlink(f.mp, &act, &child, + (char *)it.curr->name, + it.curr->name_len); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + ext4_inode_set_del_time(child.inode, -1L); + ext4_inode_set_links_cnt(child.inode, 0); + child.dirty = true; + + r = ext4_fs_free_inode(&child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + r = ext4_fs_put_inode_ref(&child); + if (r != EOK) + goto End; + + } + + r = ext4_dir_iterator_next(&it); + if (r != EOK) + goto End; + + act_curr_pos = it.curr_off; +End: + ext4_dir_iterator_fini(&it); + if (r == EOK) + r = ext4_fs_put_inode_ref(&act); + else + ext4_fs_put_inode_ref(&act); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + if (dir_end) { + /*Directory iterator reached last entry*/ + depth--; + if (depth) + inode_current = inode_up; + + } + + if (r != EOK) + break; + + } while (depth); + + /*Last unlink*/ + if (r == EOK && !depth) { + /*Load parent.*/ + struct ext4_inode_ref parent; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_up, + &parent); + if (r != EOK) + goto Finish; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_current, + &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + ext4_trans_start(mp); + + /* In this place all directories should be + * unlinked. + * Last unlink from root of current directory*/ + r = ext4_unlink(f.mp, &parent, &act, + (char *)path, len); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + if (ext4_inode_get_links_cnt(act.inode) == 2) { + ext4_inode_set_del_time(act.inode, -1L); + ext4_inode_set_links_cnt(act.inode, 0); + act.dirty = true; + /*Truncate*/ + r = ext4_trunc_dir(mp, &parent, &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + r = ext4_fs_free_inode(&act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + } + + r = ext4_fs_put_inode_ref(&parent); + if (r != EOK) + goto Finish; + + r = ext4_fs_put_inode_ref(&act); + Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_dir_mv(const char *path, const char *new_path) +{ + return ext4_frename(path, new_path); +} + +int ext4_dir_mk(const char *path) +{ + int r; + ext4_file f; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + if (mp->fs.read_only) + return EROFS; + + EXT4_MP_LOCK(mp); + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, 0, 0); + if (r == EOK) + goto Finish; + + /*Create new directory.*/ + r = ext4_generic_open(&f, path, "w", false, 0, 0); + +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_open(ext4_dir *dir, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open(&dir->f, path, "r", false, 0, 0); + dir->next_off = 0; + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_close(ext4_dir *dir) +{ + return ext4_fclose(&dir->f); +} + +const ext4_direntry *ext4_dir_entry_next(ext4_dir *dir) +{ +#define EXT4_DIR_ENTRY_OFFSET_TERM (uint64_t)(-1) + + int r; + uint16_t name_length; + ext4_direntry *de = 0; + struct ext4_inode_ref dir_inode; + struct ext4_dir_iter it; + + EXT4_MP_LOCK(dir->f.mp); + + if (dir->next_off == EXT4_DIR_ENTRY_OFFSET_TERM) { + EXT4_MP_UNLOCK(dir->f.mp); + return 0; + } + + r = ext4_fs_get_inode_ref(&dir->f.mp->fs, dir->f.inode, &dir_inode); + if (r != EOK) { + goto Finish; + } + + r = ext4_dir_iterator_init(&it, &dir_inode, dir->next_off); + if (r != EOK) { + ext4_fs_put_inode_ref(&dir_inode); + goto Finish; + } + + memset(&dir->de.name, 0, sizeof(dir->de.name)); + name_length = ext4_dir_en_get_name_len(&dir->f.mp->fs.sb, + it.curr); + memcpy(&dir->de.name, it.curr->name, name_length); + + /* Directly copying the content isn't safe for Big-endian targets*/ + dir->de.inode = ext4_dir_en_get_inode(it.curr); + dir->de.entry_length = ext4_dir_en_get_entry_len(it.curr); + dir->de.name_length = name_length; + dir->de.inode_type = ext4_dir_en_get_inode_type(&dir->f.mp->fs.sb, + it.curr); + + de = &dir->de; + + ext4_dir_iterator_next(&it); + + dir->next_off = it.curr ? it.curr_off : EXT4_DIR_ENTRY_OFFSET_TERM; + + ext4_dir_iterator_fini(&it); + ext4_fs_put_inode_ref(&dir_inode); + +Finish: + EXT4_MP_UNLOCK(dir->f.mp); + return de; +} + +void ext4_dir_entry_rewind(ext4_dir *dir) +{ + dir->next_off = 0; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_balloc.c b/lib/lwext4_rust/c/lwext4/src/ext4_balloc.c new file mode 100644 index 0000000..7984e7c --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_balloc.c @@ -0,0 +1,669 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_balloc.c + * @brief Physical block allocator. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**@brief Compute number of block group from block address. + * @param s superblock pointer. + * @param baddr Absolute address of block. + * @return Block group index + */ +uint32_t ext4_balloc_get_bgid_of_block(struct ext4_sblock *s, + uint64_t baddr) +{ + if (ext4_get32(s, first_data_block) && baddr) + baddr--; + + return (uint32_t)(baddr / ext4_get32(s, blocks_per_group)); +} + +/**@brief Compute the starting block address of a block group + * @param s superblock pointer. + * @param bgid block group index + * @return Block address + */ +uint64_t ext4_balloc_get_block_of_bgid(struct ext4_sblock *s, + uint32_t bgid) +{ + uint64_t baddr = 0; + if (ext4_get32(s, first_data_block)) + baddr++; + + baddr += bgid * ext4_get32(s, blocks_per_group); + return baddr; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_balloc_bitmap_csum(struct ext4_sblock *sb, + void *bitmap) +{ + uint32_t checksum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t blocks_per_group = ext4_get32(sb, blocks_per_group); + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bitmap, blocks_per_group / 8); + } + return checksum; +} +#else +#define ext4_balloc_bitmap_csum(...) 0 +#endif + +void ext4_balloc_set_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->block_bitmap_csum_lo = lo_checksum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->block_bitmap_csum_hi = hi_checksum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_balloc_verify_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->block_bitmap_csum_lo != lo_checksum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->block_bitmap_csum_hi != hi_checksum) + return false; + + return true; +} +#else +#define ext4_balloc_verify_bitmap_csum(...) true +#endif + +int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr) +{ + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bg_id, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block bitmap_block; + + rc = ext4_trans_block_get(fs->bdev, &bitmap_block, bitmap_block_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, bitmap_block.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Modify bitmap */ + ext4_bmap_bit_clr(bitmap_block.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg, bitmap_block.data); + ext4_trans_set_block_dirty(bitmap_block.buf); + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &bitmap_block); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks++; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks++; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + + bg_ref.dirty = true; + + rc = ext4_trans_try_revoke_block(fs->bdev, baddr); + if (rc != EOK) { + bg_ref.dirty = false; + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + ext4_bcache_invalidate_lba(fs->bdev->bc, baddr, 1); + /* Release block group reference */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + + return rc; +} + +int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t first, uint32_t count) +{ + int rc = EOK; + uint32_t blk_cnt = count; + ext4_fsblk_t start_block = first; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t bg_first = ext4_balloc_get_bgid_of_block(sb, first); + + /* Compute indexes */ + uint32_t bg_last = ext4_balloc_get_bgid_of_block(sb, first + count - 1); + + if (!ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG)) { + /*It is not possible without flex_bg that blocks are continuous + * and and last block belongs to other bg.*/ + if (bg_last != bg_first) { + ext4_dbg(DEBUG_BALLOC, DBG_WARN "FLEX_BG: disabled & " + "bg_last: %"PRIu32" bg_first: %"PRIu32"\n", + bg_last, bg_first); + } + } + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + while (bg_first <= bg_last) { + + rc = ext4_fs_get_block_group_ref(fs, bg_first, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + uint32_t idx_in_bg_first; + idx_in_bg_first = ext4_fs_addr_to_idx_bg(sb, first); + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_blk = ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block blk; + rc = ext4_trans_block_get(fs->bdev, &blk, bitmap_blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, blk.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + uint32_t free_cnt; + free_cnt = ext4_sb_get_block_size(sb) * 8 - idx_in_bg_first; + + /*If last block, free only count blocks*/ + free_cnt = count > free_cnt ? free_cnt : count; + + /* Modify bitmap */ + ext4_bmap_bits_free(blk.data, idx_in_bg_first, free_cnt); + ext4_balloc_set_bitmap_csum(sb, bg, blk.data); + ext4_trans_set_block_dirty(blk.buf); + + count -= free_cnt; + first += free_cnt; + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks += free_cnt; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks; + ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= free_cnt * (block_size / EXT4_INODE_BLOCK_SIZE); + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks += free_cnt; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + bg_ref.dirty = true; + + /* Release block group reference */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + break; + + bg_first++; + } + + uint32_t i; + for (i = 0;i < blk_cnt;i++) { + rc = ext4_trans_try_revoke_block(fs->bdev, start_block + i); + if (rc != EOK) + return rc; + + } + + ext4_bcache_invalidate_lba(fs->bdev->bc, start_block, blk_cnt); + /*All blocks should be released*/ + ext4_assert(count == 0); + + return rc; +} + +int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *fblock) +{ + ext4_fsblk_t alloc = 0; + ext4_fsblk_t bmp_blk_adr; + uint32_t rel_blk_idx = 0; + uint64_t free_blocks; + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Load block group number for goal and relative index */ + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, goal); + uint32_t idx_in_bg = ext4_fs_addr_to_idx_bg(sb, goal); + + struct ext4_block b; + struct ext4_block_group_ref bg_ref; + + /* Load block group reference */ + r = ext4_fs_get_block_group_ref(inode_ref->fs, bg_id, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + free_blocks = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto goal_failed; + } + + /* Compute indexes */ + ext4_fsblk_t first_in_bg; + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref.index); + + uint32_t first_in_bg_index; + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if goal is free */ + if (ext4_bmap_is_bit_clr(b.data, idx_in_bg)) { + ext4_bmap_bit_set(b.data, idx_in_bg); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, + b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, idx_in_bg, bg_id); + goto success; + } + + uint32_t blk_in_bg = ext4_blocks_in_group_cnt(sb, bg_id); + + uint32_t end_idx = (idx_in_bg + 63) & ~63; + if (end_idx > blk_in_bg) + end_idx = blk_in_bg; + + /* Try to find free block near to goal */ + uint32_t tmp_idx; + for (tmp_idx = idx_in_bg + 1; tmp_idx < end_idx; ++tmp_idx) { + if (ext4_bmap_is_bit_clr(b.data, tmp_idx)) { + ext4_bmap_bit_set(b.data, tmp_idx); + + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, tmp_idx, bg_id); + goto success; + } + } + + /* Find free bit in bitmap */ + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bg_id); + goto success; + } + + /* No free block found yet */ + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + +goal_failed: + + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) + return r; + + /* Try other block groups */ + uint32_t block_group_count = ext4_block_group_cnt(sb); + uint32_t bgid = (bg_id + 1) % block_group_count; + uint32_t count = block_group_count; + + while (count > 0) { + r = ext4_fs_get_block_group_ref(inode_ref->fs, bgid, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto next_group; + } + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg, sb); + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Compute indexes */ + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bgid); + idx_in_bg = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + blk_in_bg = ext4_blocks_in_group_cnt(sb, bgid); + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, + &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bgid); + goto success; + } + + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + next_group: + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) { + return r; + } + + /* Goto next group */ + bgid = (bgid + 1) % block_group_count; + count--; + } + + return ENOSPC; + +success: + /* Empty command - because of syntax */ + ; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks (different block size!) count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + r = ext4_fs_put_block_group_ref(&bg_ref); + + *fblock = alloc; + return r; +} + +int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr, bool *free) +{ + int rc; + + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t block_group = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_addr; + bmp_blk_addr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg_ref.block_group, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if block is free */ + *free = ext4_bmap_is_bit_clr(b.data, index_in_group); + + /* Allocate block if possible */ + if (*free) { + ext4_bmap_bit_set(b.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + } + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If block is not free, return */ + if (!(*free)) + goto terminate; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + +terminate: + return ext4_fs_put_block_group_ref(&bg_ref); +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_bcache.c b/lib/lwext4_rust/c/lwext4/src/ext4_bcache.c new file mode 100644 index 0000000..9d3c7fb --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_bcache.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bcache.c + * @brief Block cache allocator. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static int ext4_bcache_lba_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +static int ext4_bcache_lru_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lru_id > b->lru_id) + return 1; + else if (a->lru_id < b->lru_id) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(ext4_buf_lba, ext4_buf, lba_node, + ext4_bcache_lba_compare, static inline) +RB_GENERATE_INTERNAL(ext4_buf_lru, ext4_buf, lru_node, + ext4_bcache_lru_compare, static inline) + +int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt, + uint32_t itemsize) +{ + ext4_assert(bc && cnt && itemsize); + + memset(bc, 0, sizeof(struct ext4_bcache)); + + bc->cnt = cnt; + bc->itemsize = itemsize; + bc->ref_blocks = 0; + bc->max_ref_blocks = 0; + + return EOK; +} + +void ext4_bcache_cleanup(struct ext4_bcache *bc) +{ + struct ext4_buf *buf, *tmp; + RB_FOREACH_SAFE(buf, ext4_buf_lba, &bc->lba_root, tmp) { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_drop_buf(bc, buf); + } +} + +int ext4_bcache_fini_dynamic(struct ext4_bcache *bc) +{ + memset(bc, 0, sizeof(struct ext4_bcache)); + return EOK; +} + +/**@brief: + * + * This is ext4_bcache, the module handling basic buffer-cache stuff. + * + * Buffers in a bcache are sorted by their LBA and stored in a + * RB-Tree(lba_root). + * + * Bcache also maintains another RB-Tree(lru_root) right now, where + * buffers are sorted by their LRU id. + * + * A singly-linked list is used to track those dirty buffers which are + * ready to be flushed. (Those buffers which are dirty but also referenced + * are not considered ready to be flushed.) + * + * When a buffer is not referenced, it will be stored in both lba_root + * and lru_root, while it will only be stored in lba_root when it is + * referenced. + */ + +static struct ext4_buf * +ext4_buf_alloc(struct ext4_bcache *bc, uint64_t lba) +{ + void *data; + struct ext4_buf *buf; + data = ext4_malloc(bc->itemsize); + if (!data) + return NULL; + + buf = ext4_calloc(1, sizeof(struct ext4_buf)); + if (!buf) { + ext4_free(data); + return NULL; + } + + buf->lba = lba; + buf->data = data; + buf->bc = bc; + return buf; +} + +static void ext4_buf_free(struct ext4_buf *buf) +{ + ext4_free(buf->data); + ext4_free(buf); +} + +static struct ext4_buf * +ext4_buf_lookup(struct ext4_bcache *bc, uint64_t lba) +{ + struct ext4_buf tmp = { + .lba = lba + }; + + return RB_FIND(ext4_buf_lba, &bc->lba_root, &tmp); +} + +struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc) +{ + return RB_MIN(ext4_buf_lru, &bc->lru_root); +} + +void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf) +{ + /* Warn on dropping any referenced buffers.*/ + if (buf->refctr) { + ext4_dbg(DEBUG_BCACHE, DBG_WARN "Buffer is still referenced. " + "lba: %" PRIu64 ", refctr: %" PRIu32 "\n", + buf->lba, buf->refctr); + } else + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + + RB_REMOVE(ext4_buf_lba, &bc->lba_root, buf); + + /*Forcibly drop dirty buffer.*/ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + ext4_buf_free(buf); + bc->ref_blocks--; +} + +void ext4_bcache_invalidate_buf(struct ext4_bcache *bc, + struct ext4_buf *buf) +{ + buf->end_write = NULL; + buf->end_write_arg = NULL; + + /* Clear both dirty and up-to-date flags. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + ext4_bcache_clear_dirty(buf); +} + +void ext4_bcache_invalidate_lba(struct ext4_bcache *bc, + uint64_t from, + uint32_t cnt) +{ + uint64_t end = from + cnt - 1; + struct ext4_buf *tmp = ext4_buf_lookup(bc, from), *buf; + RB_FOREACH_FROM(buf, ext4_buf_lba, tmp) { + if (buf->lba > end) + break; + + ext4_bcache_invalidate_buf(bc, buf); + } +} + +struct ext4_buf * +ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b, + uint64_t lba) +{ + struct ext4_buf *buf = ext4_buf_lookup(bc, lba); + if (buf) { + /* If buffer is not referenced. */ + if (!buf->refctr) { + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + } + + ext4_bcache_inc_ref(buf); + + b->lb_id = lba; + b->buf = buf; + b->data = buf->data; + } + return buf; +} + +int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b, + bool *is_new) +{ + /* Try to search the buffer with exaxt LBA. */ + struct ext4_buf *buf = ext4_bcache_find_get(bc, b, b->lb_id); + if (buf) { + *is_new = false; + return EOK; + } + + /* We need to allocate one buffer.*/ + buf = ext4_buf_alloc(bc, b->lb_id); + if (!buf) + return ENOMEM; + + RB_INSERT(ext4_buf_lba, &bc->lba_root, buf); + /* One more buffer in bcache now. :-) */ + bc->ref_blocks++; + + /*Calc ref blocks max depth*/ + if (bc->max_ref_blocks < bc->ref_blocks) + bc->max_ref_blocks = bc->ref_blocks; + + + ext4_bcache_inc_ref(buf); + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + + b->buf = buf; + b->data = buf->data; + + *is_new = true; + return EOK; +} + +int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b) +{ + struct ext4_buf *buf = b->buf; + + ext4_assert(bc && b); + + /*Check if valid.*/ + ext4_assert(b->lb_id); + + /*Block should have a valid pointer to ext4_buf.*/ + ext4_assert(buf); + + /*Check if someone don't try free unreferenced block cache.*/ + ext4_assert(buf->refctr); + + /*Just decrease reference counter*/ + ext4_bcache_dec_ref(buf); + + /* We are the last one touching this buffer, do the cleanups. */ + if (!buf->refctr) { + RB_INSERT(ext4_buf_lru, &bc->lru_root, buf); + /* This buffer is ready to be flushed. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + if (bc->bdev->cache_write_back && + !ext4_bcache_test_flag(buf, BC_FLUSH) && + !ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_insert_dirty_node(bc, buf); + else { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_clear_flag(buf, BC_FLUSH); + } + } + + /* The buffer is invalidated...drop it. */ + if (!ext4_bcache_test_flag(buf, BC_UPTODATE) || + ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_drop_buf(bc, buf); + + } + + b->lb_id = 0; + b->data = 0; + + return EOK; +} + +bool ext4_bcache_is_full(struct ext4_bcache *bc) +{ + return (bc->cnt <= bc->ref_blocks); +} + + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_bitmap.c b/lib/lwext4_rust/c/lwext4/src/ext4_bitmap.c new file mode 100644 index 0000000..43b6431 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_bitmap.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bitmap.c + * @brief Block/inode bitmap allocator. + */ + +#include +#include +#include +#include +#include + +#include + +void ext4_bmap_bits_free(uint8_t *bmap, uint32_t sbit, uint32_t bcnt) +{ + uint32_t i = sbit; + + while (i & 7) { + + if (!bcnt) + return; + + ext4_bmap_bit_clr(bmap, i); + + bcnt--; + i++; + } + sbit = i; + bmap += (sbit >> 3); + +#if CONFIG_UNALIGNED_ACCESS + while (bcnt >= 32) { + *(uint32_t *)bmap = 0; + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + *(uint16_t *)bmap = 0; + bmap += 2; + bcnt -= 16; + sbit += 16; + } +#endif + + while (bcnt >= 8) { + *bmap = 0; + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + ext4_bmap_bit_clr(bmap, i); + } +} + +int ext4_bmap_bit_find_clr(uint8_t *bmap, uint32_t sbit, uint32_t ebit, + uint32_t *bit_id) +{ + uint32_t i; + uint32_t bcnt = ebit - sbit; + + i = sbit; + + while (i & 7) { + + if (!bcnt) + return ENOSPC; + + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit; + return EOK; + } + + i++; + bcnt--; + } + + sbit = i; + bmap += (sbit >> 3); + +#if CONFIG_UNALIGNED_ACCESS + while (bcnt >= 32) { + if (*(uint32_t *)bmap != 0xFFFFFFFF) + goto finish_it; + + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + if (*(uint16_t *)bmap != 0xFFFF) + goto finish_it; + + bmap += 2; + bcnt -= 16; + sbit += 16; + } +finish_it: +#endif + while (bcnt >= 8) { + if (*bmap != 0xFF) { + for (i = 0; i < 8; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + } + + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_block_group.c b/lib/lwext4_rust/c/lwext4/src/ext4_block_group.c new file mode 100644 index 0000000..d2bb1b1 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_block_group.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_block_group.c + * @brief Block group function set. + */ + +#include +#include +#include +#include +#include + +#include + +/**@brief CRC-16 look up table*/ +static uint16_t const crc16_tab[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, + 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, + 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, + 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, + 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, + 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, + 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, + 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, + 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, + 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, + 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, + 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, + 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, + 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, + 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, + 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, + 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, + 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, + 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, + 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, + 0x4100, 0x81C1, 0x8081, 0x4040}; + +uint16_t ext4_bg_crc16(uint16_t crc, const uint8_t *buffer, size_t len) +{ + while (len--) + + crc = (((crc >> 8) & 0xffU) ^ + crc16_tab[(crc ^ *buffer++) & 0xffU]) & + 0x0000ffffU; + return crc; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_blockdev.c b/lib/lwext4_rust/c/lwext4/src/ext4_blockdev.c new file mode 100644 index 0000000..c01093a --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_blockdev.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_blockdev.c + * @brief Block device module. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +static void ext4_bdif_lock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->lock) + return; + + int r = bdev->bdif->lock(bdev); + ext4_assert(r == EOK); +} + +static void ext4_bdif_unlock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->unlock) + return; + + int r = bdev->bdif->unlock(bdev); + ext4_assert(r == EOK); +} + +static int ext4_bdif_bread(struct ext4_blockdev *bdev, void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bread(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bread_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +static int ext4_bdif_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bwrite(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bwrite_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +int ext4_block_init(struct ext4_blockdev *bdev) +{ + int rc; + ext4_assert(bdev); + ext4_assert(bdev->bdif); + ext4_assert(bdev->bdif->open && + bdev->bdif->close && + bdev->bdif->bread && + bdev->bdif->bwrite); + + if (bdev->bdif->ph_refctr) { + bdev->bdif->ph_refctr++; + return EOK; + } + + /*Low level block init*/ + rc = bdev->bdif->open(bdev); + if (rc != EOK) + return rc; + + bdev->bdif->ph_refctr = 1; + return EOK; +} + +int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc) +{ + ext4_assert(bdev && bc); + bdev->bc = bc; + bc->bdev = bdev; + return EOK; +} + +void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint32_t lb_bsize) +{ + /*Logical block size has to be multiply of physical */ + ext4_assert(!(lb_bsize % bdev->bdif->ph_bsize)); + + bdev->lg_bsize = lb_bsize; + bdev->lg_bcnt = bdev->part_size / lb_bsize; +} + +int ext4_block_fini(struct ext4_blockdev *bdev) +{ + ext4_assert(bdev); + + if (!bdev->bdif->ph_refctr) + return EOK; + + bdev->bdif->ph_refctr--; + if (bdev->bdif->ph_refctr) + return EOK; + + /*Low level block fini*/ + return bdev->bdif->close(bdev); +} + +int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf) +{ + int r; + struct ext4_bcache *bc = bdev->bc; + + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + r = ext4_blocks_set_direct(bdev, buf->data, buf->lba, 1); + if (r) { + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + + return r; + } + + ext4_bcache_remove_dirty_node(bc, buf); + ext4_bcache_clear_flag(buf, BC_DIRTY); + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + } + return EOK; +} + +int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba) +{ + int r = EOK; + struct ext4_buf *buf; + struct ext4_block b; + buf = ext4_bcache_find_get(bdev->bc, &b, lba); + if (buf) { + r = ext4_block_flush_buf(bdev, buf); + ext4_bcache_free(bdev->bc, &b); + } + return r; +} + +int ext4_block_cache_shake(struct ext4_blockdev *bdev) +{ + int r = EOK; + struct ext4_buf *buf; + if (bdev->bc->dont_shake) + return EOK; + + bdev->bc->dont_shake = true; + + while (!RB_EMPTY(&bdev->bc->lru_root) && + ext4_bcache_is_full(bdev->bc)) { + + buf = ext4_buf_lowest_lru(bdev->bc); + ext4_assert(buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) { + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + break; + + } + + ext4_bcache_drop_buf(bdev->bc, buf); + } + bdev->bc->dont_shake = false; + return r; +} + +int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + bool is_new; + int r; + + ext4_assert(bdev && b); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (!(lba < bdev->lg_bcnt)) + return ENXIO; + + b->lb_id = lba; + + /*If cache is full we have to (flush and) drop it anyway :(*/ + r = ext4_block_cache_shake(bdev); + if (r != EOK) + return r; + + r = ext4_bcache_alloc(bdev->bc, b, &is_new); + if (r != EOK) + return r; + + if (!b->data) + return ENOMEM; + + return EOK; +} + +int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + if (ext4_bcache_test_flag(b->buf, BC_UPTODATE)) { + /* Data in the cache is up-to-date. + * Reading from physical device is not required */ + return EOK; + } + + r = ext4_blocks_get_direct(bdev, b->data, lba, 1); + if (r != EOK) { + ext4_bcache_free(bdev->bc, b); + b->lb_id = 0; + return r; + } + + /* Mark buffer up-to-date, since + * fresh data is read from physical device just now. */ + ext4_bcache_set_flag(b->buf, BC_UPTODATE); + return EOK; +} + +int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b) +{ + ext4_assert(bdev && b); + ext4_assert(b->buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + return ext4_bcache_free(bdev->bc, b); +} + +int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba, + uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bread(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf, + uint64_t lba, uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bwrite(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off, + const void *buf, uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + const uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t wlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf + unalg, p, wlen); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + p += wlen; + len -= wlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + if (blen != 0) { + r = ext4_bdif_bwrite(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + } + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf, p, len); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + } + + return r; +} + +int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf, + uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t rlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf + unalg, rlen); + + p += rlen; + len -= rlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + + if (blen != 0) { + r = ext4_bdif_bread(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + } + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf, len); + } + + return r; +} + +int ext4_block_cache_flush(struct ext4_blockdev *bdev) +{ + while (!SLIST_EMPTY(&bdev->bc->dirty_list)) { + int r; + struct ext4_buf *buf = SLIST_FIRST(&bdev->bc->dirty_list); + ext4_assert(buf); + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + return r; + + } + return EOK; +} + +int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off) +{ + if (on_off) + bdev->cache_write_back++; + + if (!on_off && bdev->cache_write_back) + bdev->cache_write_back--; + + if (bdev->cache_write_back) + return EOK; + + /*Flush data in all delayed cache blocks*/ + return ext4_block_cache_flush(bdev); +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_crc32.c b/lib/lwext4_rust/c/lwext4/src/ext4_crc32.c new file mode 100644 index 0000000..17ae0d0 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_crc32.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Based on FreeBSD. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_crc32c.c + * @brief Crc32c routine. Taken from FreeBSD kernel. + */ + +#include +#include +#include +#include +#include + +#include "ext4_crc32.h" + +static const uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +static const uint32_t crc32c_tab[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, + 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, + 0x6BE22838L, 0x9989AB3BL, 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, + 0x5E133C24L, 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, 0x9A879FA0L, + 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, 0x5D1D08BFL, 0xAF768BBCL, + 0xBC267848L, 0x4E4DFB4BL, 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, + 0x33ED7D2AL, 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, 0x6DFE410EL, + 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, 0x30E349B1L, 0xC288CAB2L, + 0xD1D83946L, 0x23B3BA45L, 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, + 0xE4292D5AL, 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, 0x417B1DBCL, + 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, 0x86E18AA3L, 0x748A09A0L, + 0x67DAFA54L, 0x95B17957L, 0xCBA24573L, 0x39C9C670L, 0x2A993584L, + 0xD8F2B687L, 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, 0x96BF4DCCL, + 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, 0xDBFC821CL, 0x2997011FL, + 0x3AC7F2EBL, 0xC8AC71E8L, 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, + 0x0F36E6F7L, 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, 0xEB1FCBADL, + 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, 0x2C855CB2L, 0xDEEEDFB1L, + 0xCDBE2C45L, 0x3FD5AF46L, 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, + 0x62C8A7F9L, 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, 0x3CDB9BDDL, + 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, 0x82F63B78L, 0x709DB87BL, + 0x63CD4B8FL, 0x91A6C88CL, 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, + 0x563C5F93L, 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, 0x92A8FC17L, + 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, 0x55326B08L, 0xA759E80BL, + 0xB4091BFFL, 0x466298FCL, 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, + 0x0B21572CL, 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, 0x65D122B9L, + 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, 0x2892ED69L, 0xDAF96E6AL, + 0xC9A99D9EL, 0x3BC21E9DL, 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, + 0xFC588982L, 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, 0x38CC2A06L, + 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, 0xFF56BD19L, 0x0D3D3E1AL, + 0x1E6DCDEEL, 0xEC064EEDL, 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, + 0xD0DDD530L, 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, 0x8ECEE914L, + 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, 0xD3D3E1ABL, 0x21B862A8L, + 0x32E8915CL, 0xC083125FL, 0x144976B4L, 0xE622F5B7L, 0xF5720643L, + 0x07198540L, 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, 0xE330A81AL, + 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, 0x24AA3F05L, 0xD6C1BC06L, + 0xC5914FF2L, 0x37FACCF1L, 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, + 0x7AB90321L, 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, 0x34F4F86AL, + 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, 0x79B737BAL, 0x8BDCB4B9L, + 0x988C474DL, 0x6AE7C44EL, 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, + 0xAD7D5351L}; + +static inline uint32_t crc32(uint32_t crc, const void *buf, uint32_t size, + const uint32_t *tab) +{ + const uint8_t *p = (const uint8_t *)buf; + + while (size--) + crc = tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return (crc); +} + +uint32_t ext4_crc32(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32_tab); +} + +uint32_t ext4_crc32c(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32c_tab); +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_debug.c b/lib/lwext4_rust/c/lwext4/src/ext4_debug.c new file mode 100644 index 0000000..356a157 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_debug.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_debug.c + * @brief Debug printf and assert macros. + */ + +#include +#include +#include +#include +#include + +#include + +static uint32_t debug_mask; + +void ext4_dmask_set(uint32_t m) +{ + debug_mask |= m; +} + +void ext4_dmask_clr(uint32_t m) +{ + debug_mask &= ~m; +} + +uint32_t ext4_dmask_get(void) +{ + return debug_mask; +} + + + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_dir.c b/lib/lwext4_rust/c/lwext4/src/ext4_dir.c new file mode 100644 index 0000000..29a51c5 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_dir.c @@ -0,0 +1,708 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir.h + * @brief Directory handle procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/****************************************************************************/ + +/* Walk through a dirent block to find a checksum "dirent" at the tail */ +static struct ext4_dir_entry_tail * +ext4_dir_get_tail(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *de) +{ + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + t = EXT4_DIRENT_TAIL(de, ext4_sb_get_block_size(sb)); + + if (t->reserved_zero1 || t->reserved_zero2) + return NULL; + if (to_le16(t->rec_len) != sizeof(struct ext4_dir_entry_tail)) + return NULL; + if (t->reserved_ft != EXT4_DIRENTRY_DIR_CSUM) + return NULL; + + return t; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int size) +{ + uint32_t csum; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); + + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against directory entries */ + csum = ext4_crc32c(csum, dirent, size); + return csum; +} +#else +#define ext4_dir_csum(...) 0 +#endif + +bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ +#ifdef CONFIG_META_CSUM_ENABLE + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + t = ext4_dir_get_tail(inode_ref, dirent); + if (!t) { + /* There is no space to hold the checksum */ + return false; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + if (t->checksum != to_le32(csum)) + return false; + + } +#endif + return true; +} + +void ext4_dir_init_entry_tail(struct ext4_dir_entry_tail *t) +{ + memset(t, 0, sizeof(struct ext4_dir_entry_tail)); + t->rec_len = to_le16(sizeof(struct ext4_dir_entry_tail)); + t->reserved_ft = EXT4_DIRENTRY_DIR_CSUM; +} + +void ext4_dir_set_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ + struct ext4_dir_entry_tail *t; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + t = ext4_dir_get_tail(inode_ref, dirent); + if (!t) { + /* There is no space to hold the checksum */ + return; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + t->checksum = to_le32(csum); + } +} + +/**@brief Do some checks before returning iterator. + * @param it Iterator to be checked + * @param block_size Size of data block + * @return Error code + */ +static int ext4_dir_iterator_set(struct ext4_dir_iter *it, + uint32_t block_size) +{ + uint32_t off_in_block = it->curr_off % block_size; + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + + it->curr = NULL; + + /* Ensure proper alignment */ + if ((off_in_block % 4) != 0) + return EIO; + + /* Ensure that the core of the entry does not overflow the block */ + if (off_in_block > block_size - 8) + return EIO; + + struct ext4_dir_en *en; + en = (void *)(it->curr_blk.data + off_in_block); + + /* Ensure that the whole entry does not overflow the block */ + uint16_t length = ext4_dir_en_get_entry_len(en); + if (off_in_block + length > block_size) + return EIO; + + /* Ensure the name length is not too large */ + if (ext4_dir_en_get_name_len(sb, en) > length - 8) + return EIO; + + /* Everything OK - "publish" the entry */ + it->curr = en; + return EOK; +} + +/**@brief Seek to next valid directory entry. + * Here can be jumped to the next data block. + * @param it Initialized iterator + * @param pos Position of the next entry + * @return Error code + */ +static int ext4_dir_iterator_seek(struct ext4_dir_iter *it, uint64_t pos) +{ + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + struct ext4_inode *inode = it->inode_ref->inode; + struct ext4_blockdev *bdev = it->inode_ref->fs->bdev; + uint64_t size = ext4_inode_get_size(sb, inode); + int r; + + /* The iterator is not valid until we seek to the desired position */ + it->curr = NULL; + + /* Are we at the end? */ + if (pos >= size) { + if (it->curr_blk.lb_id) { + + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + if (r != EOK) + return r; + } + + it->curr_off = pos; + return EOK; + } + + /* Compute next block address */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t current_blk_idx = it->curr_off / block_size; + uint32_t next_blk_idx = (uint32_t)(pos / block_size); + + /* + * If we don't have a block or are moving across block boundary, + * we need to get another block + */ + if ((it->curr_blk.lb_id == 0) || + (current_blk_idx != next_blk_idx)) { + if (it->curr_blk.lb_id) { + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + + if (r != EOK) + return r; + } + + ext4_fsblk_t next_blk; + r = ext4_fs_get_inode_dblk_idx(it->inode_ref, next_blk_idx, + &next_blk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(bdev, &it->curr_blk, next_blk); + if (r != EOK) { + it->curr_blk.lb_id = 0; + return r; + } + } + + it->curr_off = pos; + return ext4_dir_iterator_set(it, block_size); +} + +int ext4_dir_iterator_init(struct ext4_dir_iter *it, + struct ext4_inode_ref *inode_ref, uint64_t pos) +{ + it->inode_ref = inode_ref; + it->curr = 0; + it->curr_off = 0; + it->curr_blk.lb_id = 0; + + return ext4_dir_iterator_seek(it, pos); +} + +int ext4_dir_iterator_next(struct ext4_dir_iter *it) +{ + int r = EOK; + uint16_t skip; + + while (r == EOK) { + skip = ext4_dir_en_get_entry_len(it->curr); + r = ext4_dir_iterator_seek(it, it->curr_off + skip); + + if (!it->curr) + break; + /*Skip NULL referenced entry*/ + if (ext4_dir_en_get_inode(it->curr) != 0) + break; + } + + return r; +} + +int ext4_dir_iterator_fini(struct ext4_dir_iter *it) +{ + it->curr = 0; + + if (it->curr_blk.lb_id) + return ext4_block_set(it->inode_ref->fs->bdev, &it->curr_blk); + + return EOK; +} + +void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, + uint16_t entry_len, struct ext4_inode_ref *child, + const char *name, size_t name_len) +{ + /* Check maximum entry length */ + ext4_assert(entry_len <= ext4_sb_get_block_size(sb)); + + /* Set type of entry */ + switch (ext4_inode_type(sb, child->inode)) { + case EXT4_INODE_MODE_DIRECTORY: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_DIR); + break; + case EXT4_INODE_MODE_FILE: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_REG_FILE); + break; + case EXT4_INODE_MODE_SOFTLINK: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_SYMLINK); + break; + case EXT4_INODE_MODE_CHARDEV: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_CHRDEV); + break; + case EXT4_INODE_MODE_BLOCKDEV: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_BLKDEV); + break; + case EXT4_INODE_MODE_FIFO: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_FIFO); + break; + case EXT4_INODE_MODE_SOCKET: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_SOCK); + break; + default: + /* FIXME: unsupported filetype */ + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_UNKNOWN); + } + + /* Set basic attributes */ + ext4_dir_en_set_inode(en, child->index); + ext4_dir_en_set_entry_len(en, entry_len); + ext4_dir_en_set_name_len(sb, en, (uint16_t)name_len); + + /* Write name */ + memcpy(en->name, name, name_len); +} + +int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len, struct ext4_inode_ref *child) +{ + int r; + struct ext4_fs *fs = parent->fs; + struct ext4_sblock *sb = &parent->fs->sb; + +#if CONFIG_DIR_INDEX_ENABLE + /* Index adding (if allowed) */ + if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_add_entry(parent, child, name, name_len); + + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + uint32_t iblock = 0; + ext4_fsblk_t fblock = 0; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Find block, where is space for new entry and try to add */ + bool success = false; + for (iblock = 0; iblock < total_blocks; ++iblock) { + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + struct ext4_block block; + r = ext4_trans_block_get(fs->bdev, &block, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)block.data)) { + ext4_dbg(DEBUG_DIR, + DBG_WARN "Leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + iblock); + } + + /* If adding is successful, function can finish */ + r = ext4_dir_try_insert_entry(sb, parent, &block, child, + name, name_len); + if (r == EOK) + success = true; + + r = ext4_block_set(fs->bdev, &block); + if (r != EOK) + return r; + + if (success) + return EOK; + } + + /* No free block found - needed to allocate next data block */ + + iblock = 0; + fblock = 0; + r = ext4_fs_append_inode_dblk(parent, &fblock, &iblock); + if (r != EOK) + return r; + + /* Load new block */ + struct ext4_block b; + + r = ext4_trans_block_get_noread(fs->bdev, &b, fblock); + if (r != EOK) + return r; + + /* Fill block with zeroes */ + memset(b.data, 0, block_size); + struct ext4_dir_en *blk_en = (void *)b.data; + + /* Save new block */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t el = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_write_entry(sb, blk_en, el, child, name, name_len); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(b.data, block_size)); + } else { + ext4_dir_write_entry(sb, blk_en, block_size, child, name, + name_len); + } + + ext4_dir_set_csum(parent, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(fs->bdev, &b); + + return r; +} + +int ext4_dir_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + int r; + struct ext4_sblock *sb = &parent->fs->sb; + + /* Entry clear */ + result->block.lb_id = 0; + result->dentry = NULL; + +#if CONFIG_DIR_INDEX_ENABLE + /* Index search */ + if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_find_entry(result, parent, name_len, name); + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + + uint32_t iblock; + ext4_fsblk_t fblock; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Walk through all data blocks */ + for (iblock = 0; iblock < total_blocks; ++iblock) { + /* Load block address */ + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + /* Load data block */ + struct ext4_block b; + r = ext4_trans_block_get(parent->fs->bdev, &b, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)b.data)) { + ext4_dbg(DEBUG_DIR, + DBG_WARN "Leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + iblock); + } + + /* Try to find entry in block */ + struct ext4_dir_en *res_entry; + r = ext4_dir_find_in_block(&b, sb, name_len, name, &res_entry); + if (r == EOK) { + result->block = b; + result->dentry = res_entry; + return EOK; + } + + /* Entry not found - put block and continue to the next block */ + + r = ext4_block_set(parent->fs->bdev, &b); + if (r != EOK) + return r; + } + + return ENOENT; +} + +int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + struct ext4_sblock *sb = &parent->fs->sb; + /* Check if removing from directory */ + if (!ext4_inode_is_type(sb, parent->inode, EXT4_INODE_MODE_DIRECTORY)) + return ENOTDIR; + + /* Try to find entry */ + struct ext4_dir_search_result result; + int rc = ext4_dir_find_entry(&result, parent, name, name_len); + if (rc != EOK) + return rc; + + /* Invalidate entry */ + ext4_dir_en_set_inode(result.dentry, 0); + + /* Store entry position in block */ + uint32_t pos = (uint8_t *)result.dentry - result.block.data; + + /* + * If entry is not the first in block, it must be merged + * with previous entry + */ + if (pos != 0) { + uint32_t offset = 0; + + /* Start from the first entry in block */ + struct ext4_dir_en *tmp_de =(void *)result.block.data; + uint16_t de_len = ext4_dir_en_get_entry_len(tmp_de); + + /* Find direct predecessor of removed entry */ + while ((offset + de_len) < pos) { + offset += ext4_dir_en_get_entry_len(tmp_de); + tmp_de = (void *)(result.block.data + offset); + de_len = ext4_dir_en_get_entry_len(tmp_de); + } + + ext4_assert(de_len + offset == pos); + + /* Add to removed entry length to predecessor's length */ + uint16_t del_len; + del_len = ext4_dir_en_get_entry_len(result.dentry); + ext4_dir_en_set_entry_len(tmp_de, de_len + del_len); + } + + ext4_dir_set_csum(parent, + (struct ext4_dir_en *)result.block.data); + ext4_trans_set_block_dirty(result.block.buf); + + return ext4_dir_destroy_result(parent, &result); +} + +int ext4_dir_try_insert_entry(struct ext4_sblock *sb, + struct ext4_inode_ref *inode_ref, + struct ext4_block *dst_blk, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + /* Compute required length entry and align it to 4 bytes */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint16_t required_len = sizeof(struct ext4_fake_dir_entry) + name_len; + + if ((required_len % 4) != 0) + required_len += 4 - (required_len % 4); + + /* Initialize pointers, stop means to upper bound */ + struct ext4_dir_en *start = (void *)dst_blk->data; + struct ext4_dir_en *stop = (void *)(dst_blk->data + block_size); + + /* + * Walk through the block and check for invalid entries + * or entries with free space for new entry + */ + while (start < stop) { + uint32_t inode = ext4_dir_en_get_inode(start); + uint16_t rec_len = ext4_dir_en_get_entry_len(start); + uint8_t itype = ext4_dir_en_get_inode_type(sb, start); + + /* If invalid and large enough entry, use it */ + if ((inode == 0) && (itype != EXT4_DIRENTRY_DIR_CSUM) && + (rec_len >= required_len)) { + ext4_dir_write_entry(sb, start, rec_len, child, name, + name_len); + ext4_dir_set_csum(inode_ref, (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + + return EOK; + } + + /* Valid entry, try to split it */ + if (inode != 0) { + uint16_t used_len; + used_len = ext4_dir_en_get_name_len(sb, start); + + uint16_t sz; + sz = sizeof(struct ext4_fake_dir_entry) + used_len; + + if ((used_len % 4) != 0) + sz += 4 - (used_len % 4); + + uint16_t free_space = rec_len - sz; + + /* There is free space for new entry */ + if (free_space >= required_len) { + /* Cut tail of current entry */ + struct ext4_dir_en * new_entry; + new_entry = (void *)((uint8_t *)start + sz); + ext4_dir_en_set_entry_len(start, sz); + ext4_dir_write_entry(sb, new_entry, free_space, + child, name, name_len); + + ext4_dir_set_csum(inode_ref, + (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + return EOK; + } + } + + /* Jump to the next entry */ + start = (void *)((uint8_t *)start + rec_len); + } + + /* No free space found for new entry */ + return ENOSPC; +} + +int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, + size_t name_len, const char *name, + struct ext4_dir_en **res_entry) +{ + /* Start from the first entry in block */ + struct ext4_dir_en *de = (struct ext4_dir_en *)block->data; + + /* Set upper bound for cycling */ + uint8_t *addr_limit = block->data + ext4_sb_get_block_size(sb); + + /* Walk through the block and check entries */ + while ((uint8_t *)de < addr_limit) { + /* Termination condition */ + if ((uint8_t *)de + name_len > addr_limit) + break; + + /* Valid entry - check it */ + if (ext4_dir_en_get_inode(de) != 0) { + /* For more efficient compare only lengths firstly*/ + uint16_t el = ext4_dir_en_get_name_len(sb, de); + if (el == name_len) { + /* Compare names */ + if (memcmp(name, de->name, name_len) == 0) { + *res_entry = de; + return EOK; + } + } + } + + uint16_t de_len = ext4_dir_en_get_entry_len(de); + + /* Corrupted entry */ + if (de_len == 0) + return EINVAL; + + /* Jump to next entry */ + de = (struct ext4_dir_en *)((uint8_t *)de + de_len); + } + + /* Entry not found */ + return ENOENT; +} + +int ext4_dir_destroy_result(struct ext4_inode_ref *parent, + struct ext4_dir_search_result *result) +{ + if (result->block.lb_id) + return ext4_block_set(parent->fs->bdev, &result->block); + + return EOK; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_dir_idx.c b/lib/lwext4_rust/c/lwext4/src/ext4_dir_idx.c new file mode 100644 index 0000000..f916cc6 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_dir_idx.c @@ -0,0 +1,1402 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir_idx.c + * @brief Directory indexing procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Get hash version used in directory index. + * @param ri Pointer to root info structure of index + * @return Hash algorithm version + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_hash_version(struct ext4_dir_idx_rinfo *ri) +{ + return ri->hash_version; +} + +/**@brief Set hash version, that will be used in directory index. + * @param ri Pointer to root info structure of index + * @param v Hash algorithm version + */ +static inline void +ext4_dir_dx_rinfo_set_hash_version(struct ext4_dir_idx_rinfo *ri, uint8_t v) +{ + ri->hash_version = v; +} + +/**@brief Get length of root_info structure in bytes. + * @param ri Pointer to root info structure of index + * @return Length of the structure + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_info_length(struct ext4_dir_idx_rinfo *ri) +{ + return ri->info_length; +} + +/**@brief Set length of root_info structure in bytes. + * @param ri Pointer to root info structure of index + * @param len Length of the structure + */ +static inline void +ext4_dir_dx_root_info_set_info_length(struct ext4_dir_idx_rinfo *ri, + uint8_t len) +{ + ri->info_length = len; +} + +/**@brief Get number of indirect levels of HTree. + * @param ri Pointer to root info structure of index + * @return Height of HTree (actually only 0 or 1) + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_indirect_levels(struct ext4_dir_idx_rinfo *ri) +{ + return ri->indirect_levels; +} + +/**@brief Set number of indirect levels of HTree. + * @param ri Pointer to root info structure of index + * @param l Height of HTree (actually only 0 or 1) + */ +static inline void +ext4_dir_dx_rinfo_set_indirect_levels(struct ext4_dir_idx_rinfo *ri, uint8_t l) +{ + ri->indirect_levels = l; +} + +/**@brief Get maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @return Maximum of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_limit(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->limit); +} + +/**@brief Set maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @param limit Maximum of entries in node + */ +static inline void +ext4_dir_dx_climit_set_limit(struct ext4_dir_idx_climit *climit, uint16_t limit) +{ + climit->limit = to_le16(limit); +} + +/**@brief Get current number of index node entries. + * @param climit Pointer to counlimit structure + * @return Number of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_count(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->count); +} + +/**@brief Set current number of index node entries. + * @param climit Pointer to counlimit structure + * @param count Number of entries in node + */ +static inline void +ext4_dir_dx_climit_set_count(struct ext4_dir_idx_climit *climit, uint16_t count) +{ + climit->count = to_le16(count); +} + +/**@brief Get hash value of index entry. + * @param entry Pointer to index entry + * @return Hash value + */ +static inline uint32_t +ext4_dir_dx_entry_get_hash(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->hash); +} + +/**@brief Set hash value of index entry. + * @param entry Pointer to index entry + * @param hash Hash value + */ +static inline void +ext4_dir_dx_entry_set_hash(struct ext4_dir_idx_entry *entry, uint32_t hash) +{ + entry->hash = to_le32(hash); +} + +/**@brief Get block address where child node is located. + * @param entry Pointer to index entry + * @return Block address of child node + */ +static inline uint32_t +ext4_dir_dx_entry_get_block(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->block); +} + +/**@brief Set block address where child node is located. + * @param entry Pointer to index entry + * @param block Block address of child node + */ +static inline void +ext4_dir_dx_entry_set_block(struct ext4_dir_idx_entry *entry, uint32_t block) +{ + entry->block = to_le32(block); +} + +/**@brief Sort entry item.*/ +struct ext4_dx_sort_entry { + uint32_t hash; + uint32_t rec_len; + void *dentry; +}; + +static int ext4_dir_dx_hash_string(struct ext4_hash_info *hinfo, int len, + const char *name) +{ + return ext2_htree_hash(name, len, hinfo->seed, hinfo->hash_version, + &hinfo->hash, &hinfo->minor_hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_dx_checksum(struct ext4_inode_ref *inode_ref, void *de, + int count_offset, int count, + struct ext4_dir_idx_tail *t) +{ + uint32_t orig_cum, csum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + int sz; + + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen; + ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); + + sz = count_offset + (count * sizeof(struct ext4_dir_idx_tail)); + orig_cum = t->checksum; + t->checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* After that calculate crc32 checksum against all the dx_entry */ + csum = ext4_crc32c(csum, de, sz); + /* Finally calculate crc32 checksum for dx_tail */ + csum = ext4_crc32c(csum, t, sizeof(struct ext4_dir_idx_tail)); + t->checksum = orig_cum; + } + return csum; +} + +static struct ext4_dir_idx_climit * +ext4_dir_dx_get_climit(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int *offset) +{ + struct ext4_dir_en *dp; + struct ext4_dir_idx_root *root; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint16_t entry_len = ext4_dir_en_get_entry_len(dirent); + int count_offset; + + + if (entry_len == 12) { + root = (struct ext4_dir_idx_root *)dirent; + dp = (struct ext4_dir_en *)&root->dots[1]; + if (ext4_dir_en_get_entry_len(dp) != (block_size - 12)) + return NULL; + if (root->info.reserved_zero) + return NULL; + if (root->info.info_length != sizeof(struct ext4_dir_idx_rinfo)) + return NULL; + count_offset = 32; + } else if (entry_len == block_size) { + count_offset = 8; + } else { + return NULL; + } + + if (offset) + *offset = count_offset; + return (struct ext4_dir_idx_climit *)(((char *)dirent) + count_offset); +} + +/* + * BIG FAT NOTES: + * Currently we do not verify the checksum of HTree node. + */ +static bool ext4_dir_dx_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *de) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + int coff, limit, cnt; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, de, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return true; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + cnt = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return true; + } + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + + uint32_t c; + c = to_le32(ext4_dir_dx_checksum(inode_ref, de, coff, cnt, t)); + if (t->checksum != c) + return false; + } + return true; +} + + +static void ext4_dir_set_dx_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ + int coff, limit, count; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, dirent, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + count = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return; + } + + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + t->checksum = to_le32(ext4_dir_dx_checksum(inode_ref, dirent, + coff, count, t)); + } +} +#else +#define ext4_dir_dx_csum_verify(...) true +#define ext4_dir_set_dx_csum(...) +#endif + +/****************************************************************************/ + +int ext4_dir_dx_init(struct ext4_inode_ref *dir, struct ext4_inode_ref *parent) +{ + /* Load block 0, where will be index root located */ + ext4_fsblk_t fblock; + uint32_t iblock = 0; + bool need_append = + (ext4_inode_get_size(&dir->fs->sb, dir->inode) + < EXT4_DIR_DX_INIT_BCNT) + ? true : false; + struct ext4_sblock *sb = &dir->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&dir->fs->sb); + struct ext4_block block; + + int rc; + + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) + return rc; + + rc = ext4_trans_block_get_noread(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + struct ext4_dir_idx_rinfo *info = &(root->info); + + memset(root, 0, sizeof(struct ext4_dir_idx_root)); + struct ext4_dir_en *de; + + /* Initialize dot entries */ + de = (struct ext4_dir_en *)root->dots; + ext4_dir_write_entry(sb, de, 12, dir, ".", strlen(".")); + + de = (struct ext4_dir_en *)(root->dots + 1); + uint16_t elen = block_size - 12; + ext4_dir_write_entry(sb, de, elen, parent, "..", strlen("..")); + + /* Initialize root info structure */ + uint8_t hash_version = ext4_get8(&dir->fs->sb, default_hash_version); + + ext4_dir_dx_rinfo_set_hash_version(info, hash_version); + ext4_dir_dx_rinfo_set_indirect_levels(info, 0); + ext4_dir_dx_root_info_set_info_length(info, 8); + + /* Set limit and current number of entries */ + struct ext4_dir_idx_climit *climit; + climit = (struct ext4_dir_idx_climit *)&root->en; + + ext4_dir_dx_climit_set_count(climit, 1); + + uint32_t entry_space; + entry_space = block_size - 2 * sizeof(struct ext4_dir_idx_dot_en) - + sizeof(struct ext4_dir_idx_rinfo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + uint16_t root_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + ext4_dir_dx_climit_set_limit(climit, root_limit); + + /* Append new block, where will be new entries inserted in the future */ + iblock++; + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + struct ext4_block new_block; + rc = ext4_trans_block_get_noread(dir->fs->bdev, &new_block, fblock); + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + /* Fill the whole block with empty entry */ + struct ext4_dir_en *be = (void *)new_block.data; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t len = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_en_set_entry_len(be, len); + ext4_dir_en_set_name_len(sb, be, 0); + ext4_dir_en_set_inode_type(sb, be, EXT4_DE_UNKNOWN); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(be, block_size)); + ext4_dir_set_csum(dir, be); + } else { + ext4_dir_en_set_entry_len(be, block_size); + } + + ext4_dir_en_set_inode(be, 0); + + ext4_trans_set_block_dirty(new_block.buf); + rc = ext4_block_set(dir->fs->bdev, &new_block); + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + /* Connect new block to the only entry in index */ + struct ext4_dir_idx_entry *entry = root->en; + ext4_dir_dx_entry_set_block(entry, iblock); + + ext4_dir_set_dx_csum(dir, (struct ext4_dir_en *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/**@brief Initialize hash info structure necessary for index operations. + * @param hinfo Pointer to hinfo to be initialized + * @param root_block Root block (number 0) of index + * @param sb Pointer to superblock + * @param name_len Length of name to be computed hash value from + * @param name Name to be computed hash value from + * @return Standard error code + */ +static int ext4_dir_hinfo_init(struct ext4_hash_info *hinfo, + struct ext4_block *root_block, + struct ext4_sblock *sb, size_t name_len, + const char *name) +{ + struct ext4_dir_idx_root *root; + + root = (struct ext4_dir_idx_root *)root_block->data; + if ((root->info.hash_version != EXT2_HTREE_LEGACY) && + (root->info.hash_version != EXT2_HTREE_HALF_MD4) && + (root->info.hash_version != EXT2_HTREE_TEA)) + return EXT4_ERR_BAD_DX_DIR; + + /* Check unused flags */ + if (root->info.unused_flags != 0) + return EXT4_ERR_BAD_DX_DIR; + + /* Check indirect levels */ + if (root->info.indirect_levels > 1) + return EXT4_ERR_BAD_DX_DIR; + + /* Check if node limit is correct */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t entry_space = block_size; + entry_space -= 2 * sizeof(struct ext4_dir_idx_dot_en); + entry_space -= sizeof(struct ext4_dir_idx_rinfo); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + struct ext4_dir_idx_climit *climit = (void *)&root->en; + uint16_t limit = ext4_dir_dx_climit_get_limit(climit); + if (limit != entry_space) + return EXT4_ERR_BAD_DX_DIR; + + /* Check hash version and modify if necessary */ + hinfo->hash_version = ext4_dir_dx_rinfo_get_hash_version(&root->info); + if ((hinfo->hash_version <= EXT2_HTREE_TEA) && + (ext4_sb_check_flag(sb, EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH))) { + /* Use unsigned hash */ + hinfo->hash_version += 3; + } + + /* Load hash seed from superblock */ + hinfo->seed = ext4_get8(sb, hash_seed); + + /* Compute hash value of name */ + if (name) + return ext4_dir_dx_hash_string(hinfo, name_len, name); + + return EOK; +} + +/**@brief Walk through index tree and load leaf with corresponding hash value. + * @param hinfo Initialized hash info structure + * @param inode_ref Current i-node + * @param root_block Root block (iblock 0), where is root node located + * @param dx_block Pointer to leaf node in dx_blocks array + * @param dx_blocks Array with the whole path from root to leaf + * @return Standard error code + */ +static int ext4_dir_dx_get_leaf(struct ext4_hash_info *hinfo, + struct ext4_inode_ref *inode_ref, + struct ext4_block *root_block, + struct ext4_dir_idx_block **dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + struct ext4_dir_idx_root *root; + struct ext4_dir_idx_entry *entries; + struct ext4_dir_idx_entry *p; + struct ext4_dir_idx_entry *q; + struct ext4_dir_idx_entry *m; + struct ext4_dir_idx_entry *at; + ext4_fsblk_t fblk; + uint32_t block_size; + uint16_t limit; + uint16_t entry_space; + uint8_t ind_level; + int r; + + struct ext4_dir_idx_block *tmp_dx_blk = dx_blocks; + struct ext4_block *tmp_blk = root_block; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + block_size = ext4_sb_get_block_size(sb); + root = (struct ext4_dir_idx_root *)root_block->data; + entries = (struct ext4_dir_idx_entry *)&root->en; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + ind_level = ext4_dir_dx_rinfo_get_indirect_levels(&root->info); + + /* Walk through the index tree */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)entries); + if ((cnt == 0) || (cnt > limit)) + return EXT4_ERR_BAD_DX_DIR; + + /* Do binary search in every node */ + p = entries + 1; + q = entries + cnt - 1; + + while (p <= q) { + m = p + (q - p) / 2; + if (ext4_dir_dx_entry_get_hash(m) > hinfo->hash) + q = m - 1; + else + p = m + 1; + } + + at = p - 1; + + /* Write results */ + memcpy(&tmp_dx_blk->b, tmp_blk, sizeof(struct ext4_block)); + tmp_dx_blk->entries = entries; + tmp_dx_blk->position = at; + + /* Is algorithm in the leaf? */ + if (ind_level == 0) { + *dx_block = tmp_dx_blk; + return EOK; + } + + /* Goto child node */ + uint32_t n_blk = ext4_dir_dx_entry_get_block(at); + + ind_level--; + + r = ext4_fs_get_inode_dblk_idx(inode_ref, n_blk, &fblk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(inode_ref->fs->bdev, tmp_blk, fblk); + if (r != EOK) + return r; + + entries = ((struct ext4_dir_idx_node *)tmp_blk->data)->entries; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + + entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + if (limit != entry_space) { + ext4_block_set(inode_ref->fs->bdev, tmp_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)tmp_blk->data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + n_blk); + } + + ++tmp_dx_blk; + } + + /* Unreachable */ + return EOK; +} + +/**@brief Check if the the next block would be checked during entry search. + * @param inode_ref Directory i-node + * @param hash Hash value to check + * @param dx_block Current block + * @param dx_blocks Array with path from root to leaf node + * @return Standard Error code + */ +static int ext4_dir_dx_next_block(struct ext4_inode_ref *inode_ref, + uint32_t hash, + struct ext4_dir_idx_block *dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + int r; + uint32_t num_handles = 0; + ext4_fsblk_t blk_adr; + struct ext4_dir_idx_block *p = dx_block; + + /* Try to find data block with next bunch of entries */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)p->entries); + + p->position++; + if (p->position < p->entries + cnt) + break; + + if (p == dx_blocks) + return EOK; + + num_handles++; + p--; + } + + /* Check hash collision (if not occurred - no next block cannot be + * used)*/ + uint32_t current_hash = ext4_dir_dx_entry_get_hash(p->position); + if ((hash & 1) == 0) { + if ((current_hash & ~1) != hash) + return 0; + } + + /* Fill new path */ + while (num_handles--) { + uint32_t blk = ext4_dir_dx_entry_get_block(p->position); + r = ext4_fs_get_inode_dblk_idx(inode_ref, blk, &blk_adr, false); + if (r != EOK) + return r; + + struct ext4_block b; + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, blk_adr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + blk); + } + + p++; + + /* Don't forget to put old block (prevent memory leak) */ + r = ext4_block_set(inode_ref->fs->bdev, &p->b); + if (r != EOK) + return r; + + memcpy(&p->b, &b, sizeof(b)); + p->entries = ((struct ext4_dir_idx_node *)b.data)->entries; + p->position = p->entries; + } + + return ENOENT; +} + +int ext4_dir_dx_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *inode_ref, size_t name_len, + const char *name) +{ + /* Load direct block 0 (index root) */ + ext4_fsblk_t root_block_addr; + int rc2; + int rc; + rc = ext4_fs_get_inode_dblk_idx(inode_ref, 0, &root_block_addr, false); + if (rc != EOK) + return rc; + + struct ext4_fs *fs = inode_ref->fs; + + struct ext4_block root_block; + rc = ext4_trans_block_get(fs->bdev, &root_block, root_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)root_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + (uint32_t)0); + } + + /* Initialize hash info (compute hash value) */ + struct ext4_hash_info hinfo; + rc = ext4_dir_hinfo_init(&hinfo, &root_block, &fs->sb, name_len, name); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index tree, + * specified in the Linux driver. + */ + struct ext4_dir_idx_block dx_blocks[2]; + struct ext4_dir_idx_block *dx_block; + struct ext4_dir_idx_block *tmp; + + rc = ext4_dir_dx_get_leaf(&hinfo, inode_ref, &root_block, &dx_block, + dx_blocks); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + do { + /* Load leaf block */ + uint32_t leaf_blk_idx; + ext4_fsblk_t leaf_block_addr; + struct ext4_block b; + + leaf_blk_idx = ext4_dir_dx_entry_get_block(dx_block->position); + rc = ext4_fs_get_inode_dblk_idx(inode_ref, leaf_blk_idx, + &leaf_block_addr, false); + if (rc != EOK) + goto cleanup; + + rc = ext4_trans_block_get(fs->bdev, &b, leaf_block_addr); + if (rc != EOK) + goto cleanup; + + if (!ext4_dir_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + leaf_blk_idx); + } + + /* Linear search inside block */ + struct ext4_dir_en *de; + rc = ext4_dir_find_in_block(&b, &fs->sb, name_len, name, &de); + + /* Found => return it */ + if (rc == EOK) { + result->block = b; + result->dentry = de; + goto cleanup; + } + + /* Not found, leave untouched */ + rc2 = ext4_block_set(fs->bdev, &b); + if (rc2 != EOK) + goto cleanup; + + if (rc != ENOENT) + goto cleanup; + + /* check if the next block could be checked */ + rc = ext4_dir_dx_next_block(inode_ref, hinfo.hash, dx_block, + &dx_blocks[0]); + if (rc < 0) + goto cleanup; + } while (rc == ENOENT); + + /* Entry not found */ + rc = ENOENT; + +cleanup: + /* The whole path must be released (preventing memory leak) */ + tmp = dx_blocks; + + while (tmp <= dx_block) { + rc2 = ext4_block_set(fs->bdev, &tmp->b); + if (rc == EOK && rc2 != EOK) + rc = rc2; + ++tmp; + } + + return rc; +} + +/**@brief Compare function used to pass in quicksort implementation. + * It can compare two entries by hash value. + * @param arg1 First entry + * @param arg2 Second entry + * + * @return Classic compare result + * (0: equal, -1: arg1 < arg2, 1: arg1 > arg2) + */ +static int ext4_dir_dx_entry_comparator(const void *arg1, const void *arg2) +{ + struct ext4_dx_sort_entry *entry1 = (void *)arg1; + struct ext4_dx_sort_entry *entry2 = (void *)arg2; + + if (entry1->hash == entry2->hash) + return 0; + + if (entry1->hash < entry2->hash) + return -1; + else + return 1; +} + +/**@brief Insert new index entry to block. + * Note that space for new entry must be checked by caller. + * @param inode_ref Directory i-node + * @param index_block Block where to insert new entry + * @param hash Hash value covered by child node + * @param iblock Logical number of child block + * + */ +static void +ext4_dir_dx_insert_entry(struct ext4_inode_ref *inode_ref __unused, + struct ext4_dir_idx_block *index_block, + uint32_t hash, uint32_t iblock) +{ + struct ext4_dir_idx_entry *old_index_entry = index_block->position; + struct ext4_dir_idx_entry *new_index_entry = old_index_entry + 1; + struct ext4_dir_idx_climit *climit = (void *)index_block->entries; + struct ext4_dir_idx_entry *start_index = index_block->entries; + uint32_t count = ext4_dir_dx_climit_get_count(climit); + + size_t bytes; + bytes = (uint8_t *)(start_index + count) - (uint8_t *)(new_index_entry); + + memmove(new_index_entry + 1, new_index_entry, bytes); + + ext4_dir_dx_entry_set_block(new_index_entry, iblock); + ext4_dir_dx_entry_set_hash(new_index_entry, hash); + ext4_dir_dx_climit_set_count(climit, count + 1); + ext4_dir_set_dx_csum(inode_ref, (void *)index_block->b.data); + ext4_trans_set_block_dirty(index_block->b.buf); +} + +/**@brief Split directory entries to two parts preventing node overflow. + * @param inode_ref Directory i-node + * @param hinfo Hash info + * @param old_data_block Block with data to be split + * @param index_block Block where index entries are located + * @param new_data_block Output value for newly allocated data block + */ +static int ext4_dir_dx_split_data(struct ext4_inode_ref *inode_ref, + struct ext4_hash_info *hinfo, + struct ext4_block *old_data_block, + struct ext4_dir_idx_block *index_block, + struct ext4_block *new_data_block) +{ + int rc = EOK; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Allocate buffer for directory entries */ + uint8_t *entry_buffer = ext4_malloc(block_size); + if (entry_buffer == NULL) + return ENOMEM; + + /* dot entry has the smallest size available */ + uint32_t max_ecnt = block_size / sizeof(struct ext4_dir_idx_dot_en); + + /* Allocate sort entry */ + struct ext4_dx_sort_entry *sort; + + sort = ext4_malloc(max_ecnt * sizeof(struct ext4_dx_sort_entry)); + if (sort == NULL) { + ext4_free(entry_buffer); + return ENOMEM; + } + + uint32_t idx = 0; + uint32_t real_size = 0; + + /* Initialize hinfo */ + struct ext4_hash_info hinfo_tmp; + memcpy(&hinfo_tmp, hinfo, sizeof(struct ext4_hash_info)); + + /* Load all valid entries to the buffer */ + struct ext4_dir_en *de = (void *)old_data_block->data; + uint8_t *entry_buffer_ptr = entry_buffer; + while ((void *)de < (void *)(old_data_block->data + block_size)) { + /* Read only valid entries */ + if (ext4_dir_en_get_inode(de) && de->name_len) { + uint16_t len = ext4_dir_en_get_name_len(sb, de); + rc = ext4_dir_dx_hash_string(&hinfo_tmp, len, + (char *)de->name); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + uint32_t rec_len = 8 + len; + if ((rec_len % 4) != 0) + rec_len += 4 - (rec_len % 4); + + memcpy(entry_buffer_ptr, de, rec_len); + + sort[idx].dentry = entry_buffer_ptr; + sort[idx].rec_len = rec_len; + sort[idx].hash = hinfo_tmp.hash; + + entry_buffer_ptr += rec_len; + real_size += rec_len; + idx++; + } + + size_t elen = ext4_dir_en_get_entry_len(de); + de = (void *)((uint8_t *)de + elen); + } + + qsort(sort, idx, sizeof(struct ext4_dx_sort_entry), + ext4_dir_dx_entry_comparator); + + /* Allocate new block for store the second part of entries */ + ext4_fsblk_t new_fblock; + uint32_t new_iblock; + rc = ext4_fs_append_inode_dblk(inode_ref, &new_fblock, &new_iblock); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + /* Load new block */ + struct ext4_block new_data_block_tmp; + rc = ext4_trans_block_get_noread(inode_ref->fs->bdev, &new_data_block_tmp, + new_fblock); + if (rc != EOK) { + ext4_free(sort); + ext4_free(entry_buffer); + return rc; + } + + /* + * Distribute entries to two blocks (by size) + * - compute the half + */ + uint32_t new_hash = 0; + uint32_t current_size = 0; + uint32_t mid = 0; + uint32_t i; + for (i = 0; i < idx; ++i) { + if ((current_size + sort[i].rec_len) > (block_size / 2)) { + new_hash = sort[i].hash; + mid = i; + break; + } + + current_size += sort[i].rec_len; + } + + /* Check hash collision */ + uint32_t continued = 0; + if (new_hash == sort[mid - 1].hash) + continued = 1; + + uint32_t off = 0; + void *ptr; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + block_size -= sizeof(struct ext4_dir_entry_tail); + + /* First part - to the old block */ + for (i = 0; i < mid; ++i) { + ptr = old_data_block->data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (mid - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + /* Second part - to the new block */ + off = 0; + for (i = mid; i < idx; ++i) { + ptr = new_data_block_tmp.data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (idx - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Do some steps to finish operation */ + sb = &inode_ref->fs->sb; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_entry_tail *t; + + t = EXT4_DIRENT_TAIL(old_data_block->data, block_size); + ext4_dir_init_entry_tail(t); + t = EXT4_DIRENT_TAIL(new_data_block_tmp.data, block_size); + ext4_dir_init_entry_tail(t); + } + ext4_dir_set_csum(inode_ref, (void *)old_data_block->data); + ext4_dir_set_csum(inode_ref, (void *)new_data_block_tmp.data); + ext4_trans_set_block_dirty(old_data_block->buf); + ext4_trans_set_block_dirty(new_data_block_tmp.buf); + + ext4_free(sort); + ext4_free(entry_buffer); + + ext4_dir_dx_insert_entry(inode_ref, index_block, new_hash + continued, + new_iblock); + + *new_data_block = new_data_block_tmp; + return EOK; +} + +/**@brief Split index node and maybe some parent nodes in the tree hierarchy. + * @param ino_ref Directory i-node + * @param dx_blks Array with path from root to leaf node + * @param dxb Leaf block to be split if needed + * @return Error code + */ +static int +ext4_dir_dx_split_index(struct ext4_inode_ref *ino_ref, + struct ext4_dir_idx_block *dx_blks, + struct ext4_dir_idx_block *dxb, + struct ext4_dir_idx_block **new_dx_block) +{ + struct ext4_sblock *sb = &ino_ref->fs->sb; + struct ext4_dir_idx_entry *e; + int r; + + uint32_t block_size = ext4_sb_get_block_size(&ino_ref->fs->sb); + uint32_t entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + uint32_t node_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + + bool meta_csum = ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM); + + if (dxb == dx_blks) + e = ((struct ext4_dir_idx_root *)dxb->b.data)->en; + else + e = ((struct ext4_dir_idx_node *)dxb->b.data)->entries; + + struct ext4_dir_idx_climit *climit = (struct ext4_dir_idx_climit *)e; + + uint16_t leaf_limit = ext4_dir_dx_climit_get_limit(climit); + uint16_t leaf_count = ext4_dir_dx_climit_get_count(climit); + + /* Check if is necessary to split index block */ + if (leaf_limit == leaf_count) { + struct ext4_dir_idx_entry *ren; + ptrdiff_t levels = dxb - dx_blks; + + ren = ((struct ext4_dir_idx_root *)dx_blks[0].b.data)->en; + struct ext4_dir_idx_climit *rclimit = (void *)ren; + uint16_t root_limit = ext4_dir_dx_climit_get_limit(rclimit); + uint16_t root_count = ext4_dir_dx_climit_get_count(rclimit); + + + /* Linux limitation */ + if ((levels > 0) && (root_limit == root_count)) + return ENOSPC; + + /* Add new block to directory */ + ext4_fsblk_t new_fblk; + uint32_t new_iblk; + r = ext4_fs_append_inode_dblk(ino_ref, &new_fblk, &new_iblk); + if (r != EOK) + return r; + + /* load new block */ + struct ext4_block b; + r = ext4_trans_block_get_noread(ino_ref->fs->bdev, &b, new_fblk); + if (r != EOK) + return r; + + struct ext4_dir_idx_node *new_node = (void *)b.data; + struct ext4_dir_idx_entry *new_en = new_node->entries; + + memset(&new_node->fake, 0, sizeof(struct ext4_fake_dir_entry)); + new_node->fake.entry_length = block_size; + + /* Split leaf node */ + if (levels > 0) { + uint32_t count_left = leaf_count / 2; + uint32_t count_right = leaf_count - count_left; + uint32_t hash_right; + size_t sz; + + struct ext4_dir_idx_climit *left_climit; + struct ext4_dir_idx_climit *right_climit; + + hash_right = ext4_dir_dx_entry_get_hash(e + count_left); + /* Copy data to new node */ + sz = count_right * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e + count_left, sz); + + /* Initialize new node */ + left_climit = (struct ext4_dir_idx_climit *)e; + right_climit = (struct ext4_dir_idx_climit *)new_en; + + ext4_dir_dx_climit_set_count(left_climit, count_left); + ext4_dir_dx_climit_set_count(right_climit, count_right); + + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(right_climit, node_limit); + + /* Which index block is target for new entry */ + uint32_t position_index = + (dxb->position - dxb->entries); + if (position_index >= count_left) { + ext4_dir_set_dx_csum( + ino_ref, + (struct ext4_dir_en *) + dxb->b.data); + ext4_trans_set_block_dirty(dxb->b.buf); + + struct ext4_block block_tmp = dxb->b; + + dxb->b = b; + + dxb->position = + new_en + position_index - count_left; + dxb->entries = new_en; + + b = block_tmp; + } + + /* Finally insert new entry */ + ext4_dir_dx_insert_entry(ino_ref, dx_blks, hash_right, + new_iblk); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + + ext4_dir_set_dx_csum(ino_ref, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + return ext4_block_set(ino_ref->fs->bdev, &b); + } else { + size_t sz; + /* Copy data from root to child block */ + sz = leaf_count * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e, sz); + + struct ext4_dir_idx_climit *new_climit = (void*)new_en; + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(new_climit, node_limit); + + /* Set values in root node */ + struct ext4_dir_idx_climit *new_root_climit = (void *)e; + + ext4_dir_dx_climit_set_count(new_root_climit, 1); + ext4_dir_dx_entry_set_block(e, new_iblk); + + struct ext4_dir_idx_root *r = (void *)dx_blks[0].b.data; + r->info.indirect_levels = 1; + + /* Add new entry to the path */ + dxb = dx_blks + 1; + dxb->position = dx_blks->position - e + new_en; + dxb->entries = new_en; + dxb->b = b; + *new_dx_block = dxb; + + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + } + } + + return EOK; +} + +int ext4_dir_dx_add_entry(struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, uint32_t name_len) +{ + int rc2 = EOK; + int r; + /* Get direct block 0 (index root) */ + ext4_fsblk_t rblock_addr; + r = ext4_fs_get_inode_dblk_idx(parent, 0, &rblock_addr, false); + if (r != EOK) + return r; + + struct ext4_fs *fs = parent->fs; + struct ext4_block root_blk; + + r = ext4_trans_block_get(fs->bdev, &root_blk, rblock_addr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(parent, (void*)root_blk.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + (uint32_t)0); + } + + /* Initialize hinfo structure (mainly compute hash) */ + struct ext4_hash_info hinfo; + r = ext4_dir_hinfo_init(&hinfo, &root_blk, &fs->sb, name_len, name); + if (r != EOK) { + ext4_block_set(fs->bdev, &root_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index + * tree defined in Linux. + */ + struct ext4_dir_idx_block dx_blks[2]; + struct ext4_dir_idx_block *dx_blk; + struct ext4_dir_idx_block *dx_it; + + r = ext4_dir_dx_get_leaf(&hinfo, parent, &root_blk, &dx_blk, dx_blks); + if (r != EOK) { + r = EXT4_ERR_BAD_DX_DIR; + goto release_index; + } + + /* Try to insert to existing data block */ + uint32_t leaf_block_idx = ext4_dir_dx_entry_get_block(dx_blk->position); + ext4_fsblk_t leaf_block_addr; + r = ext4_fs_get_inode_dblk_idx(parent, leaf_block_idx, + &leaf_block_addr, false); + if (r != EOK) + goto release_index; + + /* + * Check if there is needed to split index node + * (and recursively also parent nodes) + */ + r = ext4_dir_dx_split_index(parent, dx_blks, dx_blk, &dx_blk); + if (r != EOK) + goto release_target_index; + + struct ext4_block target_block; + r = ext4_trans_block_get(fs->bdev, &target_block, leaf_block_addr); + if (r != EOK) + goto release_index; + + if (!ext4_dir_csum_verify(parent,(void *)target_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + leaf_block_idx); + } + + /* Check if insert operation passed */ + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, child, + name, name_len); + if (r == EOK) + goto release_target_index; + + /* Split entries to two blocks (includes sorting by hash value) */ + struct ext4_block new_block; + r = ext4_dir_dx_split_data(parent, &hinfo, &target_block, dx_blk, + &new_block); + if (r != EOK) { + rc2 = r; + goto release_target_index; + } + + /* Where to save new entry */ + uint32_t blk_hash = ext4_dir_dx_entry_get_hash(dx_blk->position + 1); + if (hinfo.hash >= blk_hash) + r = ext4_dir_try_insert_entry(&fs->sb, parent, &new_block, + child, name, name_len); + else + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, + child, name, name_len); + + /* Cleanup */ + r = ext4_block_set(fs->bdev, &new_block); + if (r != EOK) + return r; + +/* Cleanup operations */ + +release_target_index: + rc2 = r; + + r = ext4_block_set(fs->bdev, &target_block); + if (r != EOK) + return r; + +release_index: + if (r != EOK) + rc2 = r; + + dx_it = dx_blks; + + while (dx_it <= dx_blk) { + r = ext4_block_set(fs->bdev, &dx_it->b); + if (r != EOK) + return r; + + dx_it++; + } + + return rc2; +} + +int ext4_dir_dx_reset_parent_inode(struct ext4_inode_ref *dir, + uint32_t parent_inode) +{ + /* Load block 0, where will be index root located */ + ext4_fsblk_t fblock; + int rc = ext4_fs_get_inode_dblk_idx(dir, 0, &fblock, false); + if (rc != EOK) + return rc; + + struct ext4_block block; + rc = ext4_trans_block_get(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(dir, (void *)block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + dir->index, + (uint32_t)0); + } + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + + /* Fill the inode field with a new parent ino. */ + ext4_dx_dot_en_set_inode(&root->dots[1], parent_inode); + + ext4_dir_set_dx_csum(dir, (void *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_extent.c b/lib/lwext4_rust/c/lwext4/src/ext4_extent.c new file mode 100644 index 0000000..abac59b --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_extent.c @@ -0,0 +1,2140 @@ +/* + * Copyright (c) 2017 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2017 Kaho Ng (ngkaho1234@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if CONFIG_EXTENTS_ENABLE +/* + * used by extent splitting. + */ +#define EXT4_EXT_MARK_UNWRIT1 0x02 /* mark first half unwritten */ +#define EXT4_EXT_MARK_UNWRIT2 0x04 /* mark second half unwritten */ +#define EXT4_EXT_DATA_VALID1 0x08 /* first half contains valid data */ +#define EXT4_EXT_DATA_VALID2 0x10 /* second half contains valid data */ +#define EXT4_EXT_NO_COMBINE 0x20 /* do not combine two extents */ + +#define EXT4_EXT_UNWRITTEN_MASK (1L << 15) + +#define EXT4_EXT_MAX_LEN_WRITTEN (1L << 15) +#define EXT4_EXT_MAX_LEN_UNWRITTEN \ + (EXT4_EXT_MAX_LEN_WRITTEN - 1) + +#define EXT4_EXT_GET_LEN(ex) to_le16((ex)->block_count) +#define EXT4_EXT_GET_LEN_UNWRITTEN(ex) \ + (EXT4_EXT_GET_LEN(ex) & ~(EXT4_EXT_UNWRITTEN_MASK)) +#define EXT4_EXT_SET_LEN(ex, count) \ + ((ex)->block_count = to_le16(count)) + +#define EXT4_EXT_IS_UNWRITTEN(ex) \ + (EXT4_EXT_GET_LEN(ex) > EXT4_EXT_MAX_LEN_WRITTEN) +#define EXT4_EXT_SET_UNWRITTEN(ex) \ + ((ex)->block_count |= to_le16(EXT4_EXT_UNWRITTEN_MASK)) +#define EXT4_EXT_SET_WRITTEN(ex) \ + ((ex)->block_count &= ~(to_le16(EXT4_EXT_UNWRITTEN_MASK))) + +/* + * Array of ext4_ext_path contains path to some extent. + * Creation/lookup routines use it for traversal/splitting/etc. + * Truncate uses it to simulate recursive walking. + */ +struct ext4_extent_path { + ext4_fsblk_t p_block; + struct ext4_block block; + int32_t depth; + int32_t maxdepth; + struct ext4_extent_header *header; + struct ext4_extent_index *index; + struct ext4_extent *extent; +}; + + +#pragma pack(push, 1) + +/* + * This is the extent tail on-disk structure. + * All other extent structures are 12 bytes long. It turns out that + * block_size % 12 >= 4 for at least all powers of 2 greater than 512, which + * covers all valid ext4 block sizes. Therefore, this tail structure can be + * crammed into the end of the block without having to rebalance the tree. + */ +struct ext4_extent_tail +{ + uint32_t et_checksum; /* crc32c(uuid+inum+extent_block) */ +}; + +/* + * This is the extent on-disk structure. + * It's used at the bottom of the tree. + */ +struct ext4_extent { + uint32_t first_block; /* First logical block extent covers */ + uint16_t block_count; /* Number of blocks covered by extent */ + uint16_t start_hi; /* High 16 bits of physical block */ + uint32_t start_lo; /* Low 32 bits of physical block */ +}; + +/* + * This is index on-disk structure. + * It's used at all the levels except the bottom. + */ +struct ext4_extent_index { + uint32_t first_block; /* Index covers logical blocks from 'block' */ + + /** + * Pointer to the physical block of the next + * level. leaf or next index could be there + * high 16 bits of physical block + */ + uint32_t leaf_lo; + uint16_t leaf_hi; + uint16_t padding; +}; + +/* + * Each block (leaves and indexes), even inode-stored has header. + */ +struct ext4_extent_header { + uint16_t magic; + uint16_t entries_count; /* Number of valid entries */ + uint16_t max_entries_count; /* Capacity of store in entries */ + uint16_t depth; /* Has tree real underlying blocks? */ + uint32_t generation; /* generation of the tree */ +}; + +#pragma pack(pop) + + +#define EXT4_EXTENT_MAGIC 0xF30A + +#define EXT4_EXTENT_FIRST(header) \ + ((struct ext4_extent *)(((char *)(header)) + \ + sizeof(struct ext4_extent_header))) + +#define EXT4_EXTENT_FIRST_INDEX(header) \ + ((struct ext4_extent_index *)(((char *)(header)) + \ + sizeof(struct ext4_extent_header))) + +/* + * EXT_INIT_MAX_LEN is the maximum number of blocks we can have in an + * initialized extent. This is 2^15 and not (2^16 - 1), since we use the + * MSB of ee_len field in the extent datastructure to signify if this + * particular extent is an initialized extent or an uninitialized (i.e. + * preallocated). + * EXT_UNINIT_MAX_LEN is the maximum number of blocks we can have in an + * uninitialized extent. + * If ee_len is <= 0x8000, it is an initialized extent. Otherwise, it is an + * uninitialized one. In other words, if MSB of ee_len is set, it is an + * uninitialized extent with only one special scenario when ee_len = 0x8000. + * In this case we can not have an uninitialized extent of zero length and + * thus we make it as a special case of initialized extent with 0x8000 length. + * This way we get better extent-to-group alignment for initialized extents. + * Hence, the maximum number of blocks we can have in an *initialized* + * extent is 2^15 (32768) and in an *uninitialized* extent is 2^15-1 (32767). + */ +#define EXT_INIT_MAX_LEN (1L << 15) +#define EXT_UNWRITTEN_MAX_LEN (EXT_INIT_MAX_LEN - 1) + +#define EXT_EXTENT_SIZE sizeof(struct ext4_extent) +#define EXT_INDEX_SIZE sizeof(struct ext4_extent_idx) + +#define EXT_FIRST_EXTENT(__hdr__) \ + ((struct ext4_extent *)(((char *)(__hdr__)) + \ + sizeof(struct ext4_extent_header))) +#define EXT_FIRST_INDEX(__hdr__) \ + ((struct ext4_extent_index *)(((char *)(__hdr__)) + \ + sizeof(struct ext4_extent_header))) +#define EXT_HAS_FREE_INDEX(__path__) \ + (to_le16((__path__)->header->entries_count) < \ + to_le16((__path__)->header->max_entries_count)) +#define EXT_LAST_EXTENT(__hdr__) \ + (EXT_FIRST_EXTENT((__hdr__)) + to_le16((__hdr__)->entries_count) - 1) +#define EXT_LAST_INDEX(__hdr__) \ + (EXT_FIRST_INDEX((__hdr__)) + to_le16((__hdr__)->entries_count) - 1) +#define EXT_MAX_EXTENT(__hdr__) \ + (EXT_FIRST_EXTENT((__hdr__)) + to_le16((__hdr__)->max_entries_count) - 1) +#define EXT_MAX_INDEX(__hdr__) \ + (EXT_FIRST_INDEX((__hdr__)) + to_le16((__hdr__)->max_entries_count) - 1) + +#define EXT4_EXTENT_TAIL_OFFSET(hdr) \ + (sizeof(struct ext4_extent_header) + \ + (sizeof(struct ext4_extent) * to_le16((hdr)->max_entries_count))) + + +/**@brief Get logical number of the block covered by extent. + * @param extent Extent to load number from + * @return Logical number of the first block covered by extent */ +static inline uint32_t ext4_extent_get_first_block(struct ext4_extent *extent) +{ + return to_le32(extent->first_block); +} + +/**@brief Set logical number of the first block covered by extent. + * @param extent Extent to set number to + * @param iblock Logical number of the first block covered by extent */ +static inline void ext4_extent_set_first_block(struct ext4_extent *extent, + uint32_t iblock) +{ + extent->first_block = to_le32(iblock); +} + +/**@brief Get number of blocks covered by extent. + * @param extent Extent to load count from + * @return Number of blocks covered by extent */ +static inline uint16_t ext4_extent_get_block_count(struct ext4_extent *extent) +{ + if (EXT4_EXT_IS_UNWRITTEN(extent)) + return EXT4_EXT_GET_LEN_UNWRITTEN(extent); + else + return EXT4_EXT_GET_LEN(extent); +} +/**@brief Set number of blocks covered by extent. + * @param extent Extent to load count from + * @param count Number of blocks covered by extent + * @param unwritten Whether the extent is unwritten or not */ +static inline void ext4_extent_set_block_count(struct ext4_extent *extent, + uint16_t count, bool unwritten) +{ + EXT4_EXT_SET_LEN(extent, count); + if (unwritten) + EXT4_EXT_SET_UNWRITTEN(extent); +} + +/**@brief Get physical number of the first block covered by extent. + * @param extent Extent to load number + * @return Physical number of the first block covered by extent */ +static inline uint64_t ext4_extent_get_start(struct ext4_extent *extent) +{ + return ((uint64_t)to_le16(extent->start_hi)) << 32 | + ((uint64_t)to_le32(extent->start_lo)); +} + + +/**@brief Set physical number of the first block covered by extent. + * @param extent Extent to load number + * @param fblock Physical number of the first block covered by extent */ +static inline void ext4_extent_set_start(struct ext4_extent *extent, uint64_t fblock) +{ + extent->start_lo = to_le32((fblock << 32) >> 32); + extent->start_hi = to_le16((uint16_t)(fblock >> 32)); +} + + +/**@brief Get logical number of the block covered by extent index. + * @param index Extent index to load number from + * @return Logical number of the first block covered by extent index */ +static inline uint32_t +ext4_extent_index_get_first_block(struct ext4_extent_index *index) +{ + return to_le32(index->first_block); +} + +/**@brief Set logical number of the block covered by extent index. + * @param index Extent index to set number to + * @param iblock Logical number of the first block covered by extent index */ +static inline void +ext4_extent_index_set_first_block(struct ext4_extent_index *index, + uint32_t iblock) +{ + index->first_block = to_le32(iblock); +} + +/**@brief Get physical number of block where the child node is located. + * @param index Extent index to load number from + * @return Physical number of the block with child node */ +static inline uint64_t +ext4_extent_index_get_leaf(struct ext4_extent_index *index) +{ + return ((uint64_t)to_le16(index->leaf_hi)) << 32 | + ((uint64_t)to_le32(index->leaf_lo)); +} + +/**@brief Set physical number of block where the child node is located. + * @param index Extent index to set number to + * @param fblock Ohysical number of the block with child node */ +static inline void ext4_extent_index_set_leaf(struct ext4_extent_index *index, + uint64_t fblock) +{ + index->leaf_lo = to_le32((fblock << 32) >> 32); + index->leaf_hi = to_le16((uint16_t)(fblock >> 32)); +} + +/**@brief Get magic value from extent header. + * @param header Extent header to load value from + * @return Magic value of extent header */ +static inline uint16_t +ext4_extent_header_get_magic(struct ext4_extent_header *header) +{ + return to_le16(header->magic); +} + +/**@brief Set magic value to extent header. + * @param header Extent header to set value to + * @param magic Magic value of extent header */ +static inline void ext4_extent_header_set_magic(struct ext4_extent_header *header, + uint16_t magic) +{ + header->magic = to_le16(magic); +} + +/**@brief Get number of entries from extent header + * @param header Extent header to get value from + * @return Number of entries covered by extent header */ +static inline uint16_t +ext4_extent_header_get_entries_count(struct ext4_extent_header *header) +{ + return to_le16(header->entries_count); +} + +/**@brief Set number of entries to extent header + * @param header Extent header to set value to + * @param count Number of entries covered by extent header */ +static inline void +ext4_extent_header_set_entries_count(struct ext4_extent_header *header, + uint16_t count) +{ + header->entries_count = to_le16(count); +} + +/**@brief Get maximum number of entries from extent header + * @param header Extent header to get value from + * @return Maximum number of entries covered by extent header */ +static inline uint16_t +ext4_extent_header_get_max_entries_count(struct ext4_extent_header *header) +{ + return to_le16(header->max_entries_count); +} + +/**@brief Set maximum number of entries to extent header + * @param header Extent header to set value to + * @param max_count Maximum number of entries covered by extent header */ +static inline void +ext4_extent_header_set_max_entries_count(struct ext4_extent_header *header, + uint16_t max_count) +{ + header->max_entries_count = to_le16(max_count); +} + +/**@brief Get depth of extent subtree. + * @param header Extent header to get value from + * @return Depth of extent subtree */ +static inline uint16_t +ext4_extent_header_get_depth(struct ext4_extent_header *header) +{ + return to_le16(header->depth); +} + +/**@brief Set depth of extent subtree. + * @param header Extent header to set value to + * @param depth Depth of extent subtree */ +static inline void +ext4_extent_header_set_depth(struct ext4_extent_header *header, uint16_t depth) +{ + header->depth = to_le16(depth); +} + +/**@brief Get generation from extent header + * @param header Extent header to get value from + * @return Generation */ +static inline uint32_t +ext4_extent_header_get_generation(struct ext4_extent_header *header) +{ + return to_le32(header->generation); +} + +/**@brief Set generation to extent header + * @param header Extent header to set value to + * @param generation Generation */ +static inline void +ext4_extent_header_set_generation(struct ext4_extent_header *header, + uint32_t generation) +{ + header->generation = to_le32(generation); +} + +void ext4_extent_tree_init(struct ext4_inode_ref *inode_ref) +{ + /* Initialize extent root header */ + struct ext4_extent_header *header = + ext4_inode_get_extent_header(inode_ref->inode); + ext4_extent_header_set_depth(header, 0); + ext4_extent_header_set_entries_count(header, 0); + ext4_extent_header_set_generation(header, 0); + ext4_extent_header_set_magic(header, EXT4_EXTENT_MAGIC); + + uint16_t max_entries = (EXT4_INODE_BLOCKS * sizeof(uint32_t) - + sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); + + ext4_extent_header_set_max_entries_count(header, max_entries); + inode_ref->dirty = true; +} + + +static struct ext4_extent_tail * +find_ext4_extent_tail(struct ext4_extent_header *eh) +{ + return (struct ext4_extent_tail *)(((char *)eh) + + EXT4_EXTENT_TAIL_OFFSET(eh)); +} + +static struct ext4_extent_header *ext_inode_hdr(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +static struct ext4_extent_header *ext_block_hdr(struct ext4_block *block) +{ + return (struct ext4_extent_header *)block->data; +} + +static uint16_t ext_depth(struct ext4_inode *inode) +{ + return to_le16(ext_inode_hdr(inode)->depth); +} + +static uint16_t ext4_ext_get_actual_len(struct ext4_extent *ext) +{ + return (to_le16(ext->block_count) <= EXT_INIT_MAX_LEN + ? to_le16(ext->block_count) + : (to_le16(ext->block_count) - EXT_INIT_MAX_LEN)); +} + +static void ext4_ext_mark_initialized(struct ext4_extent *ext) +{ + ext->block_count = to_le16(ext4_ext_get_actual_len(ext)); +} + +static void ext4_ext_mark_unwritten(struct ext4_extent *ext) +{ + ext->block_count |= to_le16(EXT_INIT_MAX_LEN); +} + +static int ext4_ext_is_unwritten(struct ext4_extent *ext) +{ + /* Extent with ee_len of 0x8000 is treated as an initialized extent */ + return (to_le16(ext->block_count) > EXT_INIT_MAX_LEN); +} + +/* + * ext4_ext_pblock: + * combine low and high parts of physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_ext_pblock(struct ext4_extent *ex) +{ + ext4_fsblk_t block; + + block = to_le32(ex->start_lo); + block |= ((ext4_fsblk_t)to_le16(ex->start_hi) << 31) << 1; + return block; +} + +/* + * ext4_idx_pblock: + * combine low and high parts of a leaf physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_idx_pblock(struct ext4_extent_index *ix) +{ + ext4_fsblk_t block; + + block = to_le32(ix->leaf_lo); + block |= ((ext4_fsblk_t)to_le16(ix->leaf_hi) << 31) << 1; + return block; +} + +/* + * ext4_ext_store_pblock: + * stores a large physical block number into an extent struct, + * breaking it into parts + */ +static void ext4_ext_store_pblock(struct ext4_extent *ex, ext4_fsblk_t pb) +{ + ex->start_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ex->start_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +/* + * ext4_idx_store_pblock: + * stores a large physical block number into an index struct, + * breaking it into parts + */ +static void ext4_idx_store_pblock(struct ext4_extent_index *ix, ext4_fsblk_t pb) +{ + ix->leaf_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ix->leaf_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +static int ext4_allocate_single_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, ext4_fsblk_t *blockp) +{ + return ext4_balloc_alloc_block(inode_ref, goal, blockp); +} + +static ext4_fsblk_t ext4_new_meta_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + uint32_t flags __unused, + uint32_t *count, int *errp) +{ + ext4_fsblk_t block = 0; + + *errp = ext4_allocate_single_block(inode_ref, goal, &block); + if (count) + *count = 1; + return block; +} + +static void ext4_ext_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, uint32_t count, + uint32_t flags __unused) +{ + ext4_balloc_free_blocks(inode_ref, block, count); +} + +static uint16_t ext4_ext_space_block(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 6) + size = 6; +#endif + return size; +} + +static uint16_t ext4_ext_space_block_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 5) + size = 5; +#endif + return size; +} + +static uint16_t ext4_ext_space_root(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 3) + size = 3; +#endif + return size; +} + +static uint16_t ext4_ext_space_root_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 4) + size = 4; +#endif + return size; +} + +static uint16_t ext4_ext_max_entries(struct ext4_inode_ref *inode_ref, + uint32_t depth) +{ + uint16_t max; + + if (depth == ext_depth(inode_ref->inode)) { + if (depth == 0) + max = ext4_ext_space_root(inode_ref); + else + max = ext4_ext_space_root_idx(inode_ref); + } else { + if (depth == 0) + max = ext4_ext_space_block(inode_ref); + else + max = ext4_ext_space_block_idx(inode_ref); + } + + return max; +} + +static ext4_fsblk_t ext4_ext_find_goal(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + ext4_lblk_t block) +{ + if (path) { + uint32_t depth = path->depth; + struct ext4_extent *ex; + + /* + * Try to predict block placement assuming that we are + * filling in a file which will eventually be + * non-sparse --- i.e., in the case of libbfd writing + * an ELF object sections out-of-order but in a way + * the eventually results in a contiguous object or + * executable file, or some database extending a table + * space file. However, this is actually somewhat + * non-ideal if we are writing a sparse file such as + * qemu or KVM writing a raw image file that is going + * to stay fairly sparse, since it will end up + * fragmenting the file system's free space. Maybe we + * should have some hueristics or some way to allow + * userspace to pass a hint to file system, + * especially if the latter case turns out to be + * common. + */ + ex = path[depth].extent; + if (ex) { + ext4_fsblk_t ext_pblk = ext4_ext_pblock(ex); + ext4_lblk_t ext_block = to_le32(ex->first_block); + + if (block > ext_block) + return ext_pblk + (block - ext_block); + else + return ext_pblk - (ext_block - block); + } + + /* it looks like index is empty; + * try to find starting block from index itself */ + if (path[depth].block.lb_id) + return path[depth].block.lb_id; + } + + /* OK. use inode's group */ + return ext4_fs_inode_to_goal_block(inode_ref); +} + +/* + * Allocation for a meta data block + */ +static ext4_fsblk_t ext4_ext_new_meta_block(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext4_extent *ex, int *err, + uint32_t flags) +{ + ext4_fsblk_t goal, newblock; + + goal = ext4_ext_find_goal(inode_ref, path, to_le32(ex->first_block)); + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, err); + return newblock; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_ext_block_csum(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh) +{ + uint32_t checksum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = + to_le32(ext4_inode_get_generation(inode_ref->inode)); + /* First calculate crc32 checksum against fs uuid */ + checksum = + ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + checksum = ext4_crc32c(checksum, &ino_index, sizeof(ino_index)); + checksum = ext4_crc32c(checksum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against + * the entire extent block up to the checksum field */ + checksum = + ext4_crc32c(checksum, eh, EXT4_EXTENT_TAIL_OFFSET(eh)); + } + return checksum; +} +#else +#define ext4_ext_block_csum(...) 0 +#endif + +static void +ext4_extent_block_csum_set(struct ext4_inode_ref *inode_ref __unused, + struct ext4_extent_header *eh) +{ + struct ext4_extent_tail *tail; + + tail = find_ext4_extent_tail(eh); + tail->et_checksum = to_le32(ext4_ext_block_csum(inode_ref, eh)); +} + +static int ext4_ext_dirty(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + if (path->block.lb_id) + ext4_trans_set_block_dirty(path->block.buf); + else + inode_ref->dirty = true; + + return EOK; +} + +static void ext4_ext_drop_refs(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, bool keep_other) +{ + int32_t depth, i; + + if (!path) + return; + if (keep_other) + depth = 0; + else + depth = path->depth; + + for (i = 0; i <= depth; i++, path++) { + if (path->block.lb_id) { + if (ext4_bcache_test_flag(path->block.buf, BC_DIRTY)) + ext4_extent_block_csum_set(inode_ref, + path->header); + + ext4_block_set(inode_ref->fs->bdev, &path->block); + } + } +} + +/* + * Check that whether the basic information inside the extent header + * is correct or not. + */ +static int ext4_ext_check(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, uint16_t depth, + ext4_fsblk_t pblk __unused) +{ + struct ext4_extent_tail *tail; + struct ext4_sblock *sb = &inode_ref->fs->sb; + const char *error_msg; + (void)error_msg; + + if (to_le16(eh->magic) != EXT4_EXTENT_MAGIC) { + error_msg = "invalid magic"; + goto corrupted; + } + if (to_le16(eh->depth) != depth) { + error_msg = "unexpected eh_depth"; + goto corrupted; + } + if (eh->max_entries_count == 0) { + error_msg = "invalid eh_max"; + goto corrupted; + } + if (to_le16(eh->entries_count) > to_le16(eh->max_entries_count)) { + error_msg = "invalid eh_entries"; + goto corrupted; + } + + tail = find_ext4_extent_tail(eh); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + if (tail->et_checksum != + to_le32(ext4_ext_block_csum(inode_ref, eh))) { + ext4_dbg(DEBUG_EXTENT, + DBG_WARN "Extent block checksum failed." + "Blocknr: %" PRIu64 "\n", + pblk); + } + } + + return EOK; + +corrupted: + ext4_dbg(DEBUG_EXTENT, "Bad extents B+ tree block: %s. " + "Blocknr: %" PRId64 "\n", + error_msg, pblk); + return EIO; +} + +static int read_extent_tree_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t pblk, int32_t depth, + struct ext4_block *bh, + uint32_t flags __unused) +{ + int err; + + err = ext4_trans_block_get(inode_ref->fs->bdev, bh, pblk); + if (err != EOK) + goto errout; + + err = ext4_ext_check(inode_ref, ext_block_hdr(bh), depth, pblk); + if (err != EOK) + goto errout; + + return EOK; +errout: + if (bh->lb_id) + ext4_block_set(inode_ref->fs->bdev, bh); + + return err; +} + +/* + * ext4_ext_binsearch_idx: + * binary search for the closest index of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch_idx(struct ext4_extent_path *path, + ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent_index *r, *l, *m; + + l = EXT_FIRST_INDEX(eh) + 1; + r = EXT_LAST_INDEX(eh); + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->index = l - 1; +} + +/* + * ext4_ext_binsearch: + * binary search for closest extent of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch(struct ext4_extent_path *path, ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent *r, *l, *m; + + if (eh->entries_count == 0) { + /* + * this leaf is empty: + * we get such a leaf in split/add case + */ + return; + } + + l = EXT_FIRST_EXTENT(eh) + 1; + r = EXT_LAST_EXTENT(eh); + + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->extent = l - 1; +} + +static int ext4_find_extent(struct ext4_inode_ref *inode_ref, ext4_lblk_t block, + struct ext4_extent_path **orig_path, uint32_t flags) +{ + struct ext4_extent_header *eh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t buf_block = 0; + struct ext4_extent_path *path = *orig_path; + int32_t depth, ppos = 0; + int32_t i; + int ret; + + eh = ext_inode_hdr(inode_ref->inode); + depth = ext_depth(inode_ref->inode); + + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + if (depth > path[0].maxdepth) { + ext4_free(path); + *orig_path = path = NULL; + } + } + if (!path) { + int32_t path_depth = depth + 1; + /* account possible depth increase */ + path = ext4_calloc(1, sizeof(struct ext4_extent_path) * + (path_depth + 1)); + if (!path) + return ENOMEM; + path[0].maxdepth = path_depth; + } + path[0].header = eh; + path[0].block = bh; + + i = depth; + /* walk through the tree */ + while (i) { + ext4_ext_binsearch_idx(path + ppos, block); + path[ppos].p_block = ext4_idx_pblock(path[ppos].index); + path[ppos].depth = i; + path[ppos].extent = NULL; + buf_block = path[ppos].p_block; + + i--; + ppos++; + if (!path[ppos].block.lb_id || + path[ppos].block.lb_id != buf_block) { + ret = read_extent_tree_block(inode_ref, buf_block, i, + &bh, flags); + if (ret != EOK) { + goto err; + } + if (ppos > depth) { + ext4_block_set(inode_ref->fs->bdev, &bh); + ret = EIO; + goto err; + } + + eh = ext_block_hdr(&bh); + path[ppos].block = bh; + path[ppos].header = eh; + } + } + + path[ppos].depth = i; + path[ppos].extent = NULL; + path[ppos].index = NULL; + + /* find extent */ + ext4_ext_binsearch(path + ppos, block); + /* if not an empty leaf */ + if (path[ppos].extent) + path[ppos].p_block = ext4_ext_pblock(path[ppos].extent); + + *orig_path = path; + + ret = EOK; + return ret; + +err: + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + if (orig_path) + *orig_path = NULL; + return ret; +} + +static void ext4_ext_init_header(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, int32_t depth) +{ + eh->entries_count = 0; + eh->max_entries_count = to_le16(ext4_ext_max_entries(inode_ref, depth)); + eh->magic = to_le16(EXT4_EXTENT_MAGIC); + eh->depth = depth; +} + +static int ext4_ext_insert_index(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + ext4_lblk_t insert_index, + ext4_fsblk_t insert_block, bool set_to_ix) +{ + struct ext4_extent_index *ix; + struct ext4_extent_path *curp = path + at; + int len, err; + struct ext4_extent_header *eh; + + if (curp->index && insert_index == to_le32(curp->index->first_block)) + return EIO; + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) + return EIO; + + eh = curp->header; + if (curp->index == NULL) { + ix = EXT_FIRST_INDEX(eh); + curp->index = ix; + } else if (insert_index > to_le32(curp->index->first_block)) { + /* insert after */ + ix = curp->index + 1; + } else { + /* insert before */ + ix = curp->index; + } + + if (ix > EXT_MAX_INDEX(eh)) + return EIO; + + len = EXT_LAST_INDEX(eh) - ix + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ix + 1, ix, len * sizeof(struct ext4_extent_index)); + + ix->first_block = to_le32(insert_index); + ext4_idx_store_pblock(ix, insert_block); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ix > EXT_LAST_INDEX(eh)) { + err = EIO; + goto out; + } + + err = ext4_ext_dirty(inode_ref, curp); + +out: + if (err == EOK && set_to_ix) { + curp->index = ix; + curp->p_block = ext4_idx_pblock(ix); + } + return err; +} + +static int ext4_ext_split_node(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + struct ext4_extent *newext, + struct ext4_extent_path *npath, + bool *ins_right_leaf) +{ + int i, npath_at, ret; + ext4_lblk_t insert_index; + ext4_fsblk_t newblock = 0; + int depth = ext_depth(inode_ref->inode); + npath_at = depth - at; + + ext4_assert(at > 0); + + if (path[depth].extent != EXT_MAX_EXTENT(path[depth].header)) + insert_index = path[depth].extent[1].first_block; + else + insert_index = newext->first_block; + + for (i = depth; i >= at; i--, npath_at--) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + + /* FIXME: currently we split at the point after the current + * extent. */ + newblock = + ext4_ext_new_meta_block(inode_ref, path, newext, &ret, 0); + if (ret != EOK) + goto cleanup; + + /* For write access.*/ + ret = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, + newblock); + if (ret != EOK) + goto cleanup; + + if (i == depth) { + /* start copy from next extent */ + int m = EXT_MAX_EXTENT(path[i].header) - path[i].extent; + struct ext4_extent_header *neh; + struct ext4_extent *ex; + neh = ext_block_hdr(&bh); + ex = EXT_FIRST_EXTENT(neh); + ext4_ext_init_header(inode_ref, neh, 0); + if (m) { + memmove(ex, path[i].extent + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[i].header->entries_count = to_le16( + to_le16(path[i].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + i); + if (ret != EOK) + goto cleanup; + + npath[npath_at].p_block = ext4_ext_pblock(ex); + npath[npath_at].extent = ex; + } else { + npath[npath_at].p_block = 0; + npath[npath_at].extent = NULL; + } + + npath[npath_at].depth = to_le16(neh->depth); + npath[npath_at].maxdepth = 0; + npath[npath_at].index = NULL; + npath[npath_at].header = neh; + npath[npath_at].block = bh; + + ext4_trans_set_block_dirty(bh.buf); + } else { + int m = EXT_MAX_INDEX(path[i].header) - path[i].index; + struct ext4_extent_header *neh; + struct ext4_extent_index *ix; + neh = ext_block_hdr(&bh); + ix = EXT_FIRST_INDEX(neh); + ext4_ext_init_header(inode_ref, neh, depth - i); + ix->first_block = to_le32(insert_index); + ext4_idx_store_pblock(ix, + npath[npath_at + 1].block.lb_id); + neh->entries_count = to_le16(1); + if (m) { + memmove(ix + 1, path[i].index + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[i].header->entries_count = to_le16( + to_le16(path[i].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + i); + if (ret != EOK) + goto cleanup; + } + + npath[npath_at].p_block = ext4_idx_pblock(ix); + npath[npath_at].depth = to_le16(neh->depth); + npath[npath_at].maxdepth = 0; + npath[npath_at].extent = NULL; + npath[npath_at].index = ix; + npath[npath_at].header = neh; + npath[npath_at].block = bh; + + ext4_trans_set_block_dirty(bh.buf); + } + } + newblock = 0; + + /* + * If newext->first_block can be included into the + * right sub-tree. + */ + if (to_le32(newext->first_block) < insert_index) + *ins_right_leaf = false; + else + *ins_right_leaf = true; + + ret = ext4_ext_insert_index(inode_ref, path, at - 1, insert_index, + npath[0].block.lb_id, *ins_right_leaf); + +cleanup: + if (ret != EOK) { + if (newblock) + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + + npath_at = depth - at; + while (npath_at >= 0) { + if (npath[npath_at].block.lb_id) { + newblock = npath[npath_at].block.lb_id; + ext4_block_set(inode_ref->fs->bdev, + &npath[npath_at].block); + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + memset(&npath[npath_at].block, 0, + sizeof(struct ext4_block)); + } + npath_at--; + } + } + return ret; +} + +/* + * ext4_ext_correct_indexes: + * if leaf gets modified and modified extent is first in the leaf, + * then we have to correct all indexes above. + */ +static int ext4_ext_correct_indexes(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + struct ext4_extent_header *eh; + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex; + uint32_t border; + int32_t k; + int err = EOK; + + eh = path[depth].header; + ex = path[depth].extent; + + if (ex == NULL || eh == NULL) + return EIO; + + if (depth == 0) { + /* there is no tree at all */ + return EOK; + } + + if (ex != EXT_FIRST_EXTENT(eh)) { + /* we correct tree if first leaf got modified only */ + return EOK; + } + + k = depth - 1; + border = path[depth].extent->first_block; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + return err; + + while (k--) { + /* change all left-side indexes */ + if (path[k + 1].index != EXT_FIRST_INDEX(path[k + 1].header)) + break; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + break; + } + + return err; +} + +static inline bool ext4_ext_can_prepend(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex2) + ext4_ext_get_actual_len(ex2) != + ext4_ext_pblock(ex1)) + return 0; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return 0; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return 0; +#endif + + if (to_le32(ex2->first_block) + ext4_ext_get_actual_len(ex2) != + to_le32(ex1->first_block)) + return 0; + + return 1; +} + +static inline bool ext4_ext_can_append(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex1) + ext4_ext_get_actual_len(ex1) != + ext4_ext_pblock(ex2)) + return 0; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return 0; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return 0; +#endif + + if (to_le32(ex1->first_block) + ext4_ext_get_actual_len(ex1) != + to_le32(ex2->first_block)) + return 0; + + return 1; +} + +static int ext4_ext_insert_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int at, + struct ext4_extent *newext, int flags, + bool *need_split) +{ + struct ext4_extent_path *curp = path + at; + struct ext4_extent *ex = curp->extent; + int len, err, unwritten; + struct ext4_extent_header *eh; + + *need_split = false; + + if (curp->extent && + to_le32(newext->first_block) == to_le32(curp->extent->first_block)) + return EIO; + + if (!(flags & EXT4_EXT_NO_COMBINE)) { + if (curp->extent && ext4_ext_can_append(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + + if (curp->extent && + ext4_ext_can_prepend(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->first_block = newext->first_block; + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + } + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) { + err = EIO; + *need_split = true; + goto out; + } else { + eh = curp->header; + if (curp->extent == NULL) { + ex = EXT_FIRST_EXTENT(eh); + curp->extent = ex; + } else if (to_le32(newext->first_block) > + to_le32(curp->extent->first_block)) { + /* insert after */ + ex = curp->extent + 1; + } else { + /* insert before */ + ex = curp->extent; + } + } + + len = EXT_LAST_EXTENT(eh) - ex + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ex + 1, ex, len * sizeof(struct ext4_extent)); + + if (ex > EXT_MAX_EXTENT(eh)) { + err = EIO; + goto out; + } + + ex->first_block = newext->first_block; + ex->block_count = newext->block_count; + ext4_ext_store_pblock(ex, ext4_ext_pblock(newext)); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ex > EXT_LAST_EXTENT(eh)) { + err = EIO; + goto out; + } + + err = ext4_ext_correct_indexes(inode_ref, path); + if (err != EOK) + goto out; + err = ext4_ext_dirty(inode_ref, curp); + +out: + if (err == EOK) { + curp->extent = ex; + curp->p_block = ext4_ext_pblock(ex); + } + + return err; +} + +/* + * ext4_ext_grow_indepth: + * implements tree growing procedure: + * - allocates new block + * - moves top-level data (index block or leaf) into the new block + * - initializes new top-level, creating index that points to the + * just created block + */ +static int ext4_ext_grow_indepth(struct ext4_inode_ref *inode_ref, + uint32_t flags) +{ + struct ext4_extent_header *neh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t newblock, goal = 0; + int err = EOK; + + /* Try to prepend new index to old one */ + if (ext_depth(inode_ref->inode)) + goal = ext4_idx_pblock( + EXT_FIRST_INDEX(ext_inode_hdr(inode_ref->inode))); + else + goal = ext4_fs_inode_to_goal_block(inode_ref); + + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, &err); + if (newblock == 0) + return err; + + /* # */ + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, newblock); + if (err != EOK) { + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + return err; + } + + /* move top-level index/leaf into new block */ + memmove(bh.data, inode_ref->inode->blocks, + sizeof(inode_ref->inode->blocks)); + + /* set size of new block */ + neh = ext_block_hdr(&bh); + /* old root could have indexes or leaves + * so calculate e_max right way */ + if (ext_depth(inode_ref->inode)) + neh->max_entries_count = + to_le16(ext4_ext_space_block_idx(inode_ref)); + else + neh->max_entries_count = + to_le16(ext4_ext_space_block(inode_ref)); + + neh->magic = to_le16(EXT4_EXTENT_MAGIC); + ext4_extent_block_csum_set(inode_ref, neh); + + /* Update top-level index: num,max,pointer */ + neh = ext_inode_hdr(inode_ref->inode); + neh->entries_count = to_le16(1); + ext4_idx_store_pblock(EXT_FIRST_INDEX(neh), newblock); + if (neh->depth == 0) { + /* Root extent block becomes index block */ + neh->max_entries_count = + to_le16(ext4_ext_space_root_idx(inode_ref)); + EXT_FIRST_INDEX(neh) + ->first_block = EXT_FIRST_EXTENT(neh)->first_block; + } + neh->depth = to_le16(to_le16(neh->depth) + 1); + + ext4_trans_set_block_dirty(bh.buf); + inode_ref->dirty = true; + ext4_block_set(inode_ref->fs->bdev, &bh); + + return err; +} + +static inline void ext4_ext_replace_path(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext4_extent_path *newpath, + int at) +{ + ext4_ext_drop_refs(inode_ref, path + at, 1); + path[at] = *newpath; + memset(newpath, 0, sizeof(struct ext4_extent_path)); +} + +int ext4_ext_insert_extent(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + struct ext4_extent *newext, int flags) +{ + int depth, level = 0, ret = 0; + struct ext4_extent_path *path = *ppath; + struct ext4_extent_path *npath = NULL; + bool ins_right_leaf = false; + bool need_split; + +again: + depth = ext_depth(inode_ref->inode); + ret = ext4_ext_insert_leaf(inode_ref, path, depth, newext, flags, + &need_split); + if (ret == EIO && need_split == true) { + int i; + for (i = depth, level = 0; i >= 0; i--, level++) + if (EXT_HAS_FREE_INDEX(path + i)) + break; + + /* Do we need to grow the tree? */ + if (i < 0) { + ret = ext4_ext_grow_indepth(inode_ref, 0); + if (ret != EOK) + goto out; + + ret = ext4_find_extent( + inode_ref, to_le32(newext->first_block), ppath, 0); + if (ret != EOK) + goto out; + + path = *ppath; + /* + * After growing the tree, there should be free space in + * the only child node of the root. + */ + level--; + depth++; + } + + i = depth - (level - 1); + /* We split from leaf to the i-th node */ + if (level > 0) { + npath = ext4_calloc(1, sizeof(struct ext4_extent_path) * + (level)); + if (!npath) { + ret = ENOMEM; + goto out; + } + ret = ext4_ext_split_node(inode_ref, path, i, newext, + npath, &ins_right_leaf); + if (ret != EOK) + goto out; + + while (--level >= 0) { + if (ins_right_leaf) + ext4_ext_replace_path(inode_ref, path, + &npath[level], + i + level); + else if (npath[level].block.lb_id) + ext4_ext_drop_refs(inode_ref, + npath + level, 1); + } + } + goto again; + } + +out: + if (ret != EOK) { + if (path) + ext4_ext_drop_refs(inode_ref, path, 0); + + while (--level >= 0 && npath) { + if (npath[level].block.lb_id) { + ext4_fsblk_t block = npath[level].block.lb_id; + ext4_ext_free_blocks(inode_ref, block, 1, 0); + ext4_ext_drop_refs(inode_ref, npath + level, 1); + } + } + } + if (npath) + ext4_free(npath); + + return ret; +} + +static void ext4_ext_remove_blocks(struct ext4_inode_ref *inode_ref, + struct ext4_extent *ex, ext4_lblk_t from, + ext4_lblk_t to) +{ + ext4_lblk_t len = to - from + 1; + ext4_lblk_t num; + ext4_fsblk_t start; + num = from - to_le32(ex->first_block); + start = ext4_ext_pblock(ex) + num; + ext4_dbg(DEBUG_EXTENT, + "Freeing %" PRIu32 " at %" PRIu64 ", %" PRIu32 "\n", from, + start, len); + + ext4_ext_free_blocks(inode_ref, start, len, 0); +} + +static int ext4_ext_remove_idx(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int32_t depth) +{ + int err = EOK; + int32_t i = depth; + ext4_fsblk_t leaf; + + /* free index block */ + leaf = ext4_idx_pblock(path[i].index); + + if (path[i].index != EXT_LAST_INDEX(path[i].header)) { + ptrdiff_t len = EXT_LAST_INDEX(path[i].header) - path[i].index; + memmove(path[i].index, path[i].index + 1, + len * sizeof(struct ext4_extent_index)); + } + + path[i].header->entries_count = + to_le16(to_le16(path[i].header->entries_count) - 1); + err = ext4_ext_dirty(inode_ref, path + i); + if (err != EOK) + return err; + + ext4_dbg(DEBUG_EXTENT, "IDX: Freeing %" PRIu32 " at %" PRIu64 ", %d\n", + to_le32(path[i].index->first_block), leaf, 1); + ext4_ext_free_blocks(inode_ref, leaf, 1, 0); + + /* + * We may need to correct the paths after the first extents/indexes in + * a node being modified. + * + * We do not need to consider whether there's any extents presenting or + * not, as garbage will be cleared soon. + */ + while (i > 0) { + if (path[i].index != EXT_FIRST_INDEX(path[i].header)) + break; + + path[i - 1].index->first_block = path[i].index->first_block; + err = ext4_ext_dirty(inode_ref, path + i - 1); + if (err != EOK) + break; + + i--; + } + return err; +} + +static int ext4_ext_remove_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, ext4_lblk_t from, + ext4_lblk_t to) +{ + + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex = path[depth].extent; + struct ext4_extent *start_ex, *ex2 = NULL; + struct ext4_extent_header *eh = path[depth].header; + int32_t len; + int err = EOK; + uint16_t new_entries; + + start_ex = ex; + new_entries = to_le16(eh->entries_count); + while (ex <= EXT_LAST_EXTENT(path[depth].header) && + to_le32(ex->first_block) <= to) { + int32_t new_len = 0; + int unwritten; + ext4_lblk_t start, new_start; + ext4_fsblk_t newblock; + new_start = start = to_le32(ex->first_block); + len = ext4_ext_get_actual_len(ex); + newblock = ext4_ext_pblock(ex); + /* + * The 1st case: + * The position that we start truncation is inside the range of an + * extent. Here we should calculate the new length of that extent and + * may start the removal from the next extent. + */ + if (start < from) { + len -= from - start; + new_len = from - start; + start = from; + start_ex++; + } else { + /* + * The second case: + * The last block to be truncated is inside the range of an + * extent. We need to calculate the new length and the new + * start of the extent. + */ + if (start + len - 1 > to) { + new_len = start + len - 1 - to; + len -= new_len; + new_start = to + 1; + newblock += to + 1 - start; + ex2 = ex; + } + } + + ext4_ext_remove_blocks(inode_ref, ex, start, start + len - 1); + /* + * Set the first block of the extent if it is presented. + */ + ex->first_block = to_le32(new_start); + + /* + * If the new length of the current extent we are working on is + * zero, remove it. + */ + if (!new_len) + new_entries--; + else { + unwritten = ext4_ext_is_unwritten(ex); + ex->block_count = to_le16(new_len); + ext4_ext_store_pblock(ex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(ex); + } + + ex += 1; + } + + if (ex2 == NULL) + ex2 = ex; + + /* + * Move any remaining extents to the starting position of the node. + */ + if (ex2 <= EXT_LAST_EXTENT(eh)) + memmove(start_ex, ex2, (EXT_LAST_EXTENT(eh) - ex2 + 1) * + sizeof(struct ext4_extent)); + + eh->entries_count = to_le16(new_entries); + ext4_ext_dirty(inode_ref, path + depth); + + /* + * If the extent pointer is pointed to the first extent of the node, and + * there's still extents presenting, we may need to correct the indexes + * of the paths. + */ + if (path[depth].extent == EXT_FIRST_EXTENT(eh) && eh->entries_count) { + err = ext4_ext_correct_indexes(inode_ref, path); + if (err != EOK) + return err; + } + + /* if this leaf is free, then we should + * remove it from index block above */ + if (eh->entries_count == 0 && path[depth].block.lb_id) + err = ext4_ext_remove_idx(inode_ref, path, depth - 1); + else if (depth > 0) + path[depth - 1].index++; + + return err; +} + +/* + * Check if there's more to remove at a specific level. + */ +static bool ext4_ext_more_to_rm(struct ext4_extent_path *path, ext4_lblk_t to) +{ + if (!to_le16(path->header->entries_count)) + return false; + + if (path->index > EXT_LAST_INDEX(path->header)) + return false; + + if (to_le32(path->index->first_block) > to) + return false; + + return true; +} + +int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from, + ext4_lblk_t to) +{ + struct ext4_extent_path *path = NULL; + int ret = EOK; + int32_t depth = ext_depth(inode_ref->inode); + int32_t i; + + ret = ext4_find_extent(inode_ref, from, &path, 0); + if (ret != EOK) + goto out; + + if (!path[depth].extent) { + ret = EOK; + goto out; + } + + bool in_range = IN_RANGE(from, to_le32(path[depth].extent->first_block), + ext4_ext_get_actual_len(path[depth].extent)); + + if (!in_range) { + ret = EOK; + goto out; + } + + /* If we do remove_space inside the range of an extent */ + if ((to_le32(path[depth].extent->first_block) < from) && + (to < to_le32(path[depth].extent->first_block) + + ext4_ext_get_actual_len(path[depth].extent) - 1)) { + + struct ext4_extent *ex = path[depth].extent, newex; + int unwritten = ext4_ext_is_unwritten(ex); + ext4_lblk_t ee_block = to_le32(ex->first_block); + int32_t len = ext4_ext_get_actual_len(ex); + ext4_fsblk_t newblock = to + 1 - ee_block + ext4_ext_pblock(ex); + + ex->block_count = to_le16(from - ee_block); + if (unwritten) + ext4_ext_mark_unwritten(ex); + + ext4_ext_dirty(inode_ref, path + depth); + + newex.first_block = to_le32(to + 1); + newex.block_count = to_le16(ee_block + len - 1 - to); + ext4_ext_store_pblock(&newex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(&newex); + + ret = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + goto out; + } + + i = depth; + while (i >= 0) { + if (i == depth) { + struct ext4_extent_header *eh; + struct ext4_extent *first_ex, *last_ex; + ext4_lblk_t leaf_from, leaf_to; + eh = path[i].header; + ext4_assert(to_le16(eh->entries_count) > 0); + first_ex = EXT_FIRST_EXTENT(eh); + last_ex = EXT_LAST_EXTENT(eh); + leaf_from = to_le32(first_ex->first_block); + leaf_to = to_le32(last_ex->first_block) + + ext4_ext_get_actual_len(last_ex) - 1; + if (leaf_from < from) + leaf_from = from; + + if (leaf_to > to) + leaf_to = to; + + ext4_ext_remove_leaf(inode_ref, path, leaf_from, + leaf_to); + ext4_ext_drop_refs(inode_ref, path + i, 0); + i--; + continue; + } + + struct ext4_extent_header *eh; + eh = path[i].header; + if (ext4_ext_more_to_rm(path + i, to)) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + if (path[i + 1].block.lb_id) + ext4_ext_drop_refs(inode_ref, path + i + 1, 0); + + ret = read_extent_tree_block( + inode_ref, ext4_idx_pblock(path[i].index), + depth - i - 1, &bh, 0); + if (ret != EOK) + goto out; + + path[i].p_block = ext4_idx_pblock(path[i].index); + path[i + 1].block = bh; + path[i + 1].header = ext_block_hdr(&bh); + path[i + 1].depth = depth - i - 1; + if (i + 1 == depth) + path[i + 1].extent = + EXT_FIRST_EXTENT(path[i + 1].header); + else + path[i + 1].index = + EXT_FIRST_INDEX(path[i + 1].header); + + i++; + } else { + if (i > 0) { + /* + * Garbage entries will finally be cleared here. + */ + if (!eh->entries_count) + ret = ext4_ext_remove_idx(inode_ref, + path, i - 1); + else + path[i - 1].index++; + } + + if (i) + ext4_block_set(inode_ref->fs->bdev, + &path[i].block); + + i--; + } + } + + /* TODO: flexible tree reduction should be here */ + if (path->header->entries_count == 0) { + /* + * truncate to zero freed all the tree, + * so we need to correct eh_depth + */ + ext_inode_hdr(inode_ref->inode)->depth = 0; + ext_inode_hdr(inode_ref->inode)->max_entries_count = + to_le16(ext4_ext_space_root(inode_ref)); + ret = ext4_ext_dirty(inode_ref, path); + } + +out: + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + path = NULL; + return ret; +} + +static int ext4_ext_split_extent_at(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t split_flag) +{ + struct ext4_extent *ex, newex; + ext4_fsblk_t newblock; + ext4_lblk_t ee_block; + int32_t ee_len; + int32_t depth = ext_depth(inode_ref->inode); + int err = EOK; + + ex = (*ppath)[depth].extent; + ee_block = to_le32(ex->first_block); + ee_len = ext4_ext_get_actual_len(ex); + newblock = split - ee_block + ext4_ext_pblock(ex); + + if (split == ee_block) { + /* + * case b: block @split is the block that the extent begins with + * then we just change the state of the extent, and splitting + * is not needed. + */ + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(ex); + else + ext4_ext_mark_initialized(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + goto out; + } + + ex->block_count = to_le16(split - ee_block); + if (split_flag & EXT4_EXT_MARK_UNWRIT1) + ext4_ext_mark_unwritten(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + if (err != EOK) + goto out; + + newex.first_block = to_le32(split); + newex.block_count = to_le16(ee_len - (split - ee_block)); + ext4_ext_store_pblock(&newex, newblock); + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(&newex); + err = ext4_ext_insert_extent(inode_ref, ppath, &newex, + EXT4_EXT_NO_COMBINE); + if (err != EOK) + goto restore_extent_len; + +out: + return err; +restore_extent_len: + ex->block_count = to_le16(ee_len); + err = ext4_ext_dirty(inode_ref, *ppath + depth); + return err; +} + +static int ext4_ext_convert_to_initialized(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t blocks) +{ + int32_t depth = ext_depth(inode_ref->inode), err = EOK; + struct ext4_extent *ex = (*ppath)[depth].extent; + + ext4_assert(to_le32(ex->first_block) <= split); + + if (split + blocks == + to_le32(ex->first_block) + ext4_ext_get_actual_len(ex)) { + /* split and initialize right part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } else if (to_le32(ex->first_block) == split) { + /* split and initialize left part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT2); + } else { + /* split 1 extent to 3 and initialize the 2nd */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT1 | + EXT4_EXT_MARK_UNWRIT2); + if (err == EOK) { + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } + } + + return err; +} + +static ext4_lblk_t ext4_ext_next_allocated_block(struct ext4_extent_path *path) +{ + int32_t depth; + + depth = path->depth; + + if (depth == 0 && path->extent == NULL) + return EXT_MAX_BLOCKS; + + while (depth >= 0) { + if (depth == path->depth) { + /* leaf */ + if (path[depth].extent && + path[depth].extent != + EXT_LAST_EXTENT(path[depth].header)) + return to_le32( + path[depth].extent[1].first_block); + } else { + /* index */ + if (path[depth].index != + EXT_LAST_INDEX(path[depth].header)) + return to_le32( + path[depth].index[1].first_block); + } + depth--; + } + + return EXT_MAX_BLOCKS; +} + +static int ext4_ext_zero_unwritten_range(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, + uint32_t blocks_count) +{ + int err = EOK; + uint32_t i; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + for (i = 0; i < blocks_count; i++) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, + block + i); + if (err != EOK) + break; + + memset(bh.data, 0, block_size); + ext4_trans_set_block_dirty(bh.buf); + err = ext4_block_set(inode_ref->fs->bdev, &bh); + if (err != EOK) + break; + } + return err; +} + +__unused static void print_path(struct ext4_extent_path *path) +{ + int32_t i = path->depth; + while (i >= 0) { + + ptrdiff_t a = + (path->extent) + ? (path->extent - EXT_FIRST_EXTENT(path->header)) + : 0; + ptrdiff_t b = + (path->index) + ? (path->index - EXT_FIRST_INDEX(path->header)) + : 0; + + (void)a; + (void)b; + ext4_dbg(DEBUG_EXTENT, + "depth %" PRId32 ", p_block: %" PRIu64 "," + "p_ext offset: %td, p_idx offset: %td\n", + i, path->p_block, a, b); + i--; + path++; + } +} + +int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_lblk_t iblock, + uint32_t max_blocks, ext4_fsblk_t *result, + bool create, uint32_t *blocks_count) +{ + struct ext4_extent_path *path = NULL; + struct ext4_extent newex, *ex; + ext4_fsblk_t goal; + int err = EOK; + int32_t depth; + uint32_t allocated = 0; + ext4_lblk_t next; + ext4_fsblk_t newblock; + + if (result) + *result = 0; + + if (blocks_count) + *blocks_count = 0; + + /* find extent for this block */ + err = ext4_find_extent(inode_ref, iblock, &path, 0); + if (err != EOK) { + path = NULL; + goto out2; + } + + depth = ext_depth(inode_ref->inode); + + /* + * consistent leaf must not be empty + * this situations is possible, though, _during_ tree modification + * this is why assert can't be put in ext4_ext_find_extent() + */ + ex = path[depth].extent; + if (ex) { + ext4_lblk_t ee_block = to_le32(ex->first_block); + ext4_fsblk_t ee_start = ext4_ext_pblock(ex); + uint16_t ee_len = ext4_ext_get_actual_len(ex); + /* if found exent covers block, simple return it */ + if (IN_RANGE(iblock, ee_block, ee_len)) { + /* number of remain blocks in the extent */ + allocated = ee_len - (iblock - ee_block); + + if (!ext4_ext_is_unwritten(ex)) { + newblock = iblock - ee_block + ee_start; + goto out; + } + + if (!create) { + newblock = 0; + goto out; + } + + uint32_t zero_range; + zero_range = allocated; + if (zero_range > max_blocks) + zero_range = max_blocks; + + newblock = iblock - ee_block + ee_start; + err = ext4_ext_zero_unwritten_range(inode_ref, newblock, + zero_range); + if (err != EOK) + goto out2; + + err = ext4_ext_convert_to_initialized( + inode_ref, &path, iblock, zero_range); + if (err != EOK) + goto out2; + + goto out; + } + } + + /* + * requested block isn't allocated yet + * we couldn't try to create block if create flag is zero + */ + if (!create) { + goto out2; + } + + /* find next allocated block so that we know how many + * blocks we can allocate without ovelapping next extent */ + next = ext4_ext_next_allocated_block(path); + allocated = next - iblock; + if (allocated > max_blocks) + allocated = max_blocks; + + /* allocate new block */ + goal = ext4_ext_find_goal(inode_ref, path, iblock); + newblock = ext4_new_meta_blocks(inode_ref, goal, 0, &allocated, &err); + if (!newblock) + goto out2; + + /* try to insert new extent into found leaf and return */ + newex.first_block = to_le32(iblock); + ext4_ext_store_pblock(&newex, newblock); + newex.block_count = to_le16(allocated); + err = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + if (err != EOK) { + /* free data blocks we just allocated */ + ext4_ext_free_blocks(inode_ref, ext4_ext_pblock(&newex), + to_le16(newex.block_count), 0); + goto out2; + } + + /* previous routine could use block we allocated */ + newblock = ext4_ext_pblock(&newex); + +out: + if (allocated > max_blocks) + allocated = max_blocks; + + if (result) + *result = newblock; + + if (blocks_count) + *blocks_count = allocated; + +out2: + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + ext4_free(path); + } + + return err; +} +#endif diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_fs.c b/lib/lwext4_rust/c/lwext4/src/ext4_fs.c new file mode 100644 index 0000000..c7a99e7 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_fs.c @@ -0,0 +1,1750 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_fs.c + * @brief More complex filesystem functions. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int ext4_fs_init(struct ext4_fs *fs, struct ext4_blockdev *bdev, + bool read_only) +{ + int r, i; + uint16_t tmp; + uint32_t bsize; + + ext4_assert(fs && bdev); + + fs->bdev = bdev; + + fs->read_only = read_only; + + r = ext4_sb_read(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + if (!ext4_sb_check(&fs->sb)) + return ENOTSUP; + + bsize = ext4_sb_get_block_size(&fs->sb); + if (bsize > EXT4_MAX_BLOCK_SIZE) + return ENXIO; + + r = ext4_fs_check_features(fs, &read_only); + if (r != EOK) + return r; + + if (read_only) + fs->read_only = read_only; + + /* Compute limits for indirect block levels */ + uint32_t blocks_id = bsize / sizeof(uint32_t); + + fs->inode_block_limits[0] = EXT4_INODE_DIRECT_BLOCK_COUNT; + fs->inode_blocks_per_level[0] = 1; + + for (i = 1; i < 4; i++) { + fs->inode_blocks_per_level[i] = + fs->inode_blocks_per_level[i - 1] * blocks_id; + fs->inode_block_limits[i] = fs->inode_block_limits[i - 1] + + fs->inode_blocks_per_level[i]; + } + + /*Validate FS*/ + tmp = ext4_get16(&fs->sb, state); + if (tmp & EXT4_SUPERBLOCK_STATE_ERROR_FS) + ext4_dbg(DEBUG_FS, DBG_WARN + "last umount error: superblock fs_error flag\n"); + + + if (!fs->read_only) { + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_ERROR_FS); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, ext4_get16(&fs->sb, mount_count) + 1); + } + + return r; +} + +int ext4_fs_fini(struct ext4_fs *fs) +{ + ext4_assert(fs); + + /*Set superblock state*/ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_VALID_FS); + + if (!fs->read_only) + return ext4_sb_write(fs->bdev, &fs->sb); + + return EOK; +} + +static void ext4_fs_debug_features_inc(uint32_t features_incompatible) +{ + if (features_incompatible & EXT4_FINCOM_COMPRESSION) + ext4_dbg(DEBUG_FS, DBG_NONE "compression\n"); + if (features_incompatible & EXT4_FINCOM_FILETYPE) + ext4_dbg(DEBUG_FS, DBG_NONE "filetype\n"); + if (features_incompatible & EXT4_FINCOM_RECOVER) + ext4_dbg(DEBUG_FS, DBG_NONE "recover\n"); + if (features_incompatible & EXT4_FINCOM_JOURNAL_DEV) + ext4_dbg(DEBUG_FS, DBG_NONE "journal_dev\n"); + if (features_incompatible & EXT4_FINCOM_META_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_bg\n"); + if (features_incompatible & EXT4_FINCOM_EXTENTS) + ext4_dbg(DEBUG_FS, DBG_NONE "extents\n"); + if (features_incompatible & EXT4_FINCOM_64BIT) + ext4_dbg(DEBUG_FS, DBG_NONE "64bit\n"); + if (features_incompatible & EXT4_FINCOM_MMP) + ext4_dbg(DEBUG_FS, DBG_NONE "mnp\n"); + if (features_incompatible & EXT4_FINCOM_FLEX_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "flex_bg\n"); + if (features_incompatible & EXT4_FINCOM_EA_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "ea_inode\n"); + if (features_incompatible & EXT4_FINCOM_DIRDATA) + ext4_dbg(DEBUG_FS, DBG_NONE "dirdata\n"); + if (features_incompatible & EXT4_FINCOM_BG_USE_META_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_csum\n"); + if (features_incompatible & EXT4_FINCOM_LARGEDIR) + ext4_dbg(DEBUG_FS, DBG_NONE "largedir\n"); + if (features_incompatible & EXT4_FINCOM_INLINE_DATA) + ext4_dbg(DEBUG_FS, DBG_NONE "inline_data\n"); +} +static void ext4_fs_debug_features_comp(uint32_t features_compatible) +{ + if (features_compatible & EXT4_FCOM_DIR_PREALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_prealloc\n"); + if (features_compatible & EXT4_FCOM_IMAGIC_INODES) + ext4_dbg(DEBUG_FS, DBG_NONE "imagic_inodes\n"); + if (features_compatible & EXT4_FCOM_HAS_JOURNAL) + ext4_dbg(DEBUG_FS, DBG_NONE "has_journal\n"); + if (features_compatible & EXT4_FCOM_EXT_ATTR) + ext4_dbg(DEBUG_FS, DBG_NONE "ext_attr\n"); + if (features_compatible & EXT4_FCOM_RESIZE_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "resize_inode\n"); + if (features_compatible & EXT4_FCOM_DIR_INDEX) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_index\n"); +} + +static void ext4_fs_debug_features_ro(uint32_t features_ro) +{ + if (features_ro & EXT4_FRO_COM_SPARSE_SUPER) + ext4_dbg(DEBUG_FS, DBG_NONE "sparse_super\n"); + if (features_ro & EXT4_FRO_COM_LARGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "large_file\n"); + if (features_ro & EXT4_FRO_COM_BTREE_DIR) + ext4_dbg(DEBUG_FS, DBG_NONE "btree_dir\n"); + if (features_ro & EXT4_FRO_COM_HUGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "huge_file\n"); + if (features_ro & EXT4_FRO_COM_GDT_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "gtd_csum\n"); + if (features_ro & EXT4_FRO_COM_DIR_NLINK) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_nlink\n"); + if (features_ro & EXT4_FRO_COM_EXTRA_ISIZE) + ext4_dbg(DEBUG_FS, DBG_NONE "extra_isize\n"); + if (features_ro & EXT4_FRO_COM_QUOTA) + ext4_dbg(DEBUG_FS, DBG_NONE "quota\n"); + if (features_ro & EXT4_FRO_COM_BIGALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "bigalloc\n"); + if (features_ro & EXT4_FRO_COM_METADATA_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "metadata_csum\n"); +} + +int ext4_fs_check_features(struct ext4_fs *fs, bool *read_only) +{ + ext4_assert(fs && read_only); + uint32_t v; + if (ext4_get32(&fs->sb, rev_level) == 0) { + *read_only = false; + return EOK; + } + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_incompatible:\n"); + ext4_fs_debug_features_inc(ext4_get32(&fs->sb, features_incompatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_compatible:\n"); + ext4_fs_debug_features_comp(ext4_get32(&fs->sb, features_compatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_read_only:\n"); + ext4_fs_debug_features_ro(ext4_get32(&fs->sb, features_read_only)); + + /*Check features_incompatible*/ + v = (ext4_get32(&fs->sb, features_incompatible) & + (~CONFIG_SUPPORTED_FINCOM)); + if (v) { + ext4_dbg(DEBUG_FS, DBG_ERROR + "sblock has unsupported features incompatible:\n"); + ext4_fs_debug_features_inc(v); + return ENOTSUP; + } + + /*Check features_read_only*/ + v = ext4_get32(&fs->sb, features_read_only); + v &= ~CONFIG_SUPPORTED_FRO_COM; + if (v) { + ext4_dbg(DEBUG_FS, DBG_WARN + "sblock has unsupported features read only:\n"); + ext4_fs_debug_features_ro(v); + *read_only = true; + return EOK; + } + *read_only = false; + + return EOK; +} + +/**@brief Determine whether the block is inside the group. + * @param baddr block address + * @param bgid block group id + * @return Error code + */ +static bool ext4_block_in_group(struct ext4_sblock *s, ext4_fsblk_t baddr, + uint32_t bgid) +{ + uint32_t actual_bgid; + actual_bgid = ext4_balloc_get_bgid_of_block(s, baddr); + if (actual_bgid == bgid) + return true; + return false; +} + +/**@brief To avoid calling the atomic setbit hundreds or thousands of times, we only + * need to use it within a single byte (to ensure we get endianness right). + * We can use memset for the rest of the bitmap as there are no other users. + */ +static void ext4_fs_mark_bitmap_end(int start_bit, int end_bit, void *bitmap) +{ + int i; + + if (start_bit >= end_bit) + return; + + for (i = start_bit; (unsigned)i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(bitmap, i); + + if (i < end_bit) + memset((char *)bitmap + (i >> 3), 0xff, (end_bit - i) >> 3); +} + +/**@brief Initialize block bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_block_bitmap(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + int rc; + + uint32_t bit, bit_max; + uint32_t group_blocks; + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + ext4_fsblk_t i; + ext4_fsblk_t bmp_blk = ext4_bg_get_block_bitmap(bg, sb); + ext4_fsblk_t bmp_inode = ext4_bg_get_inode_bitmap(bg, sb); + ext4_fsblk_t inode_table = ext4_bg_get_inode_table_first_block(bg, sb); + ext4_fsblk_t first_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref->index); + + uint32_t dsc_per_block = block_size / ext4_sb_get_desc_size(sb); + + bool flex_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG); + bool meta_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_META_BG); + + uint32_t inode_table_bcnt = inodes_per_group * inode_size / block_size; + + struct ext4_block block_bitmap; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &block_bitmap, bmp_blk); + if (rc != EOK) + return rc; + + memset(block_bitmap.data, 0, block_size); + bit_max = ext4_sb_is_super_in_bg(sb, bg_ref->index); + + uint32_t count = ext4_sb_first_meta_bg(sb) * dsc_per_block; + if (!meta_bg || bg_ref->index < count) { + if (bit_max) { + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + bit_max += ext4_get16(sb, s_reserved_gdt_blocks); + } + } else { /* For META_BG_BLOCK_GROUPS */ + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + } + for (bit = 0; bit < bit_max; bit++) + ext4_bmap_bit_set(block_bitmap.data, bit); + + if (bg_ref->index == ext4_block_group_cnt(sb) - 1) { + /* + * Even though mke2fs always initialize first and last group + * if some other tool enabled the EXT4_BG_BLOCK_UNINIT we need + * to make sure we calculate the right free blocks + */ + + group_blocks = (uint32_t)(ext4_sb_get_blocks_cnt(sb) - + ext4_get32(sb, first_data_block) - + ext4_get32(sb, blocks_per_group) * + (ext4_block_group_cnt(sb) - 1)); + } else { + group_blocks = ext4_get32(sb, blocks_per_group); + } + + bool in_bg; + in_bg = ext4_block_in_group(sb, bmp_blk, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_blk - first_bg)); + + in_bg = ext4_block_in_group(sb, bmp_inode, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_inode - first_bg)); + + for (i = inode_table; i < inode_table + inode_table_bcnt; i++) { + in_bg = ext4_block_in_group(sb, i, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(i - first_bg)); + } + /* + * Also if the number of blocks within the group is + * less than the blocksize * 8 ( which is the size + * of bitmap ), set rest of the block bitmap to 1 + */ + ext4_fs_mark_bitmap_end(group_blocks, block_size * 8, block_bitmap.data); + ext4_trans_set_block_dirty(block_bitmap.buf); + + ext4_balloc_set_bitmap_csum(sb, bg_ref->block_group, block_bitmap.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &block_bitmap); +} + +/**@brief Initialize i-node bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_bitmap(struct ext4_block_group_ref *bg_ref) +{ + int rc; + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + /* Load bitmap */ + ext4_fsblk_t bitmap_block_addr = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + /* Initialize all bitmap bits to zero */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + memset(b.data, 0, (inodes_per_group + 7) / 8); + + uint32_t start_bit = inodes_per_group; + uint32_t end_bit = block_size * 8; + + uint32_t i; + for (i = start_bit; i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(b.data, i); + + if (i < end_bit) + memset(b.data + (i >> 3), 0xff, (end_bit - i) >> 3); + + ext4_trans_set_block_dirty(b.buf); + + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &b); +} + +/**@brief Initialize i-node table in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_table(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + uint32_t inode_size = ext4_get16(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_block = block_size / inode_size; + uint32_t inodes_in_group = ext4_inodes_in_group_cnt(sb, bg_ref->index); + uint32_t table_blocks = inodes_in_group / inodes_per_block; + ext4_fsblk_t fblock; + + if (inodes_in_group % inodes_per_block) + table_blocks++; + + /* Compute initialization bounds */ + ext4_fsblk_t first_block = ext4_bg_get_inode_table_first_block(bg, sb); + + ext4_fsblk_t last_block = first_block + table_blocks - 1; + + /* Initialization of all itable blocks */ + for (fblock = first_block; fblock <= last_block; ++fblock) { + struct ext4_block b; + int rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, fblock); + if (rc != EOK) + return rc; + + memset(b.data, 0, block_size); + ext4_trans_set_block_dirty(b.buf); + + rc = ext4_block_set(bg_ref->fs->bdev, &b); + if (rc != EOK) + return rc; + } + + return EOK; +} + +static ext4_fsblk_t ext4_fs_get_descriptor_block(struct ext4_sblock *s, + uint32_t bgid, + uint32_t dsc_per_block) +{ + uint32_t first_meta_bg, dsc_id; + int has_super = 0; + dsc_id = bgid / dsc_per_block; + first_meta_bg = ext4_sb_first_meta_bg(s); + + bool meta_bg = ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG); + + if (!meta_bg || dsc_id < first_meta_bg) + return ext4_get32(s, first_data_block) + dsc_id + 1; + + if (ext4_sb_is_super_in_bg(s, bgid)) + has_super = 1; + + return (has_super + ext4_fs_first_bg_block_no(s, bgid)); +} + +/**@brief Compute checksum of block group descriptor. + * @param sb Superblock + * @param bgid Index of block group in the filesystem + * @param bg Block group to compute checksum for + * @return Checksum value + */ +static uint16_t ext4_fs_bg_checksum(struct ext4_sblock *sb, uint32_t bgid, + struct ext4_bgroup *bg) +{ + /* If checksum not supported, 0 will be returned */ + uint16_t crc = 0; +#if CONFIG_META_CSUM_ENABLE + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + /* Use metadata_csum algorithm instead */ + uint32_t le32_bgid = to_le32(bgid); + uint32_t orig_checksum, checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = bg->checksum; + bg->checksum = 0; + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against bgid */ + checksum = ext4_crc32c(checksum, &le32_bgid, sizeof(bgid)); + /* Finally calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bg, ext4_sb_get_desc_size(sb)); + bg->checksum = orig_checksum; + + crc = checksum & 0xFFFF; + return crc; + } +#endif + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_GDT_CSUM)) { + uint8_t *base = (uint8_t *)bg; + uint8_t *checksum = (uint8_t *)&bg->checksum; + + uint32_t offset = (uint32_t)(checksum - base); + + /* Convert block group index to little endian */ + uint32_t group = to_le32(bgid); + + /* Initialization */ + crc = ext4_bg_crc16(~0, sb->uuid, sizeof(sb->uuid)); + + /* Include index of block group */ + crc = ext4_bg_crc16(crc, (uint8_t *)&group, sizeof(group)); + + /* Compute crc from the first part (stop before checksum field) + */ + crc = ext4_bg_crc16(crc, (uint8_t *)bg, offset); + + /* Skip checksum */ + offset += sizeof(bg->checksum); + + /* Checksum of the rest of block group descriptor */ + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_64BIT)) && + (offset < ext4_sb_get_desc_size(sb))) { + + const uint8_t *start = ((uint8_t *)bg) + offset; + size_t len = ext4_sb_get_desc_size(sb) - offset; + crc = ext4_bg_crc16(crc, start, len); + } + } + return crc; +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_bg_csum(struct ext4_sblock *sb, + uint32_t bgid, + struct ext4_bgroup *bg) +{ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_fs_bg_checksum(sb, bgid, bg) == to_le16(bg->checksum); +} +#else +#define ext4_fs_verify_bg_csum(...) true +#endif + +int ext4_fs_get_block_group_ref(struct ext4_fs *fs, uint32_t bgid, + struct ext4_block_group_ref *ref) +{ + /* Compute number of descriptors, that fits in one data block */ + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t dsc_cnt = block_size / ext4_sb_get_desc_size(&fs->sb); + + /* Block group descriptor table starts at the next block after + * superblock */ + uint64_t block_id = ext4_fs_get_descriptor_block(&fs->sb, bgid, dsc_cnt); + + uint32_t offset = (bgid % dsc_cnt) * ext4_sb_get_desc_size(&fs->sb); + + int rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) + return rc; + + ref->block_group = (void *)(ref->block.data + offset); + ref->fs = fs; + ref->index = bgid; + ref->dirty = false; + struct ext4_bgroup *bg = ref->block_group; + + if (!ext4_fs_verify_bg_csum(&fs->sb, bgid, bg)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Block group descriptor checksum failed." + "Block group index: %" PRIu32"\n", + bgid); + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT)) { + rc = ext4_fs_init_block_bitmap(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT); + ref->dirty = true; + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT)) { + rc = ext4_fs_init_inode_bitmap(ref); + if (rc != EOK) { + ext4_block_set(ref->fs->bdev, &ref->block); + return rc; + } + + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT); + + if (!ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED)) { + rc = ext4_fs_init_inode_table(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + + ext4_bg_set_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED); + } + + ref->dirty = true; + } + + return EOK; +} + +int ext4_fs_put_block_group_ref(struct ext4_block_group_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Compute new checksum of block group */ + uint16_t cs; + cs = ext4_fs_bg_checksum(&ref->fs->sb, ref->index, + ref->block_group); + ref->block_group->checksum = to_le16(cs); + + /* Mark block dirty for writing changes to physical device */ + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains block group descriptor */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_fs_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + uint32_t checksum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint16_t inode_size = ext4_get16(sb, inode_size); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = + to_le32(ext4_inode_get_generation(inode_ref->inode)); + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = ext4_inode_get_csum(sb, inode_ref->inode); + ext4_inode_set_csum(sb, inode_ref->inode, 0); + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + checksum = ext4_crc32c(checksum, &ino_index, sizeof(ino_index)); + checksum = ext4_crc32c(checksum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against + * the entire inode */ + checksum = ext4_crc32c(checksum, inode_ref->inode, inode_size); + ext4_inode_set_csum(sb, inode_ref->inode, orig_checksum); + + /* If inode size is not large enough to hold the + * upper 16bit of the checksum */ + if (inode_size == EXT4_GOOD_OLD_INODE_SIZE) + checksum &= 0xFFFF; + + } + return checksum; +} +#else +#define ext4_fs_inode_checksum(...) 0 +#endif + +static void ext4_fs_set_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + uint32_t csum = ext4_fs_inode_checksum(inode_ref); + ext4_inode_set_csum(sb, inode_ref->inode, csum); +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_inode_csum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_inode_get_csum(sb, inode_ref->inode) == + ext4_fs_inode_checksum(inode_ref); +} +#else +#define ext4_fs_verify_inode_csum(...) true +#endif + +static int +__ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref, + bool initialized) +{ + /* Compute number of i-nodes, that fits in one data block */ + uint32_t inodes_per_group = ext4_get32(&fs->sb, inodes_per_group); + + /* + * Inode numbers are 1-based, but it is simpler to work with 0-based + * when computing indices + */ + index -= 1; + uint32_t block_group = index / inodes_per_group; + uint32_t offset_in_group = index % inodes_per_group; + + /* Load block group, where i-node is located */ + struct ext4_block_group_ref bg_ref; + + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) { + return rc; + } + + /* Load block address, where i-node table is located */ + ext4_fsblk_t inode_table_start = + ext4_bg_get_inode_table_first_block(bg_ref.block_group, &fs->sb); + + /* Put back block group reference (not needed more) */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the block group */ + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t byte_offset_in_group = offset_in_group * inode_size; + + /* Compute block address */ + ext4_fsblk_t block_id = + inode_table_start + (byte_offset_in_group / block_size); + + rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the data block */ + uint32_t offset_in_block = byte_offset_in_group % block_size; + ref->inode = (struct ext4_inode *)(ref->block.data + offset_in_block); + + /* We need to store the original value of index in the reference */ + ref->index = index + 1; + ref->fs = fs; + ref->dirty = false; + + if (initialized && !ext4_fs_verify_inode_csum(ref)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Inode checksum failed." + "Inode: %" PRIu32"\n", + ref->index); + } + + return EOK; +} + +int ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref) +{ + return __ext4_fs_get_inode_ref(fs, index, ref, true); +} + +int ext4_fs_put_inode_ref(struct ext4_inode_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Mark block dirty for writing changes to physical device */ + ext4_fs_set_inode_checksum(ref); + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains i-node */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +void ext4_fs_inode_blocks_init(struct ext4_fs *fs, + struct ext4_inode_ref *inode_ref) +{ + struct ext4_inode *inode = inode_ref->inode; + + /* Reset blocks array. For inode which is not directory or file, just + * fill in blocks with 0 */ + switch (ext4_inode_type(&fs->sb, inode_ref->inode)) { + case EXT4_INODE_MODE_FILE: + case EXT4_INODE_MODE_DIRECTORY: + break; + default: + return; + } + +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Initialize extents if needed */ + if (ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) { + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_EXTENTS); + + /* Initialize extent root header */ + ext4_extent_tree_init(inode_ref); + } + + inode_ref->dirty = true; +#endif +} + +uint32_t ext4_fs_correspond_inode_mode(int filetype) +{ + switch (filetype) { + case EXT4_DE_DIR: + return EXT4_INODE_MODE_DIRECTORY; + case EXT4_DE_REG_FILE: + return EXT4_INODE_MODE_FILE; + case EXT4_DE_SYMLINK: + return EXT4_INODE_MODE_SOFTLINK; + case EXT4_DE_CHRDEV: + return EXT4_INODE_MODE_CHARDEV; + case EXT4_DE_BLKDEV: + return EXT4_INODE_MODE_BLOCKDEV; + case EXT4_DE_FIFO: + return EXT4_INODE_MODE_FIFO; + case EXT4_DE_SOCK: + return EXT4_INODE_MODE_SOCKET; + } + /* FIXME: unsupported filetype */ + return EXT4_INODE_MODE_FILE; +} + +int ext4_fs_alloc_inode(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + int filetype) +{ + /* Check if newly allocated i-node will be a directory */ + bool is_dir; + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + + is_dir = (filetype == EXT4_DE_DIR); + + /* Allocate inode by allocation algorithm */ + uint32_t index; + int rc = ext4_ialloc_alloc_inode(fs, &index, is_dir); + if (rc != EOK) + return rc; + + /* Load i-node from on-disk i-node table */ + rc = __ext4_fs_get_inode_ref(fs, index, inode_ref, false); + if (rc != EOK) { + ext4_ialloc_free_inode(fs, index, is_dir); + return rc; + } + + /* Initialize i-node */ + struct ext4_inode *inode = inode_ref->inode; + + memset(inode, 0, inode_size); + + uint32_t mode; + if (is_dir) { + /* + * Default directory permissions to be compatible with other + * systems + * 0777 (octal) == rwxrwxrwx + */ + + mode = 0777; + mode |= EXT4_INODE_MODE_DIRECTORY; + } else if (filetype == EXT4_DE_SYMLINK) { + /* + * Default symbolic link permissions to be compatible with other systems + * 0777 (octal) == rwxrwxrwx + */ + + mode = 0777; + mode |= EXT4_INODE_MODE_SOFTLINK; + } else { + /* + * Default file permissions to be compatible with other systems + * 0666 (octal) == rw-rw-rw- + */ + + mode = 0666; + mode |= ext4_fs_correspond_inode_mode(filetype); + } + ext4_inode_set_mode(&fs->sb, inode, mode); + + ext4_inode_set_links_cnt(inode, 0); + ext4_inode_set_uid(inode, 0); + ext4_inode_set_gid(inode, 0); + ext4_inode_set_size(inode, 0); + ext4_inode_set_access_time(inode, 0); + ext4_inode_set_change_inode_time(inode, 0); + ext4_inode_set_modif_time(inode, 0); + ext4_inode_set_del_time(inode, 0); + ext4_inode_set_blocks_count(&fs->sb, inode, 0); + ext4_inode_set_flags(inode, 0); + ext4_inode_set_generation(inode, 0); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) { + uint16_t size = ext4_get16(&fs->sb, want_extra_isize); + ext4_inode_set_extra_isize(&fs->sb, inode, size); + } + + memset(inode->blocks, 0, sizeof(inode->blocks)); + inode_ref->dirty = true; + + return EOK; +} + +int ext4_fs_free_inode(struct ext4_inode_ref *inode_ref) +{ + struct ext4_fs *fs = inode_ref->fs; + uint32_t offset; + uint32_t suboff; + int rc; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* For extents must be data block destroyed by other way */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Data structures are released during truncate operation... */ + goto finish; + } +#endif + /* Release all indirect (no data) blocks */ + + /* 1) Single indirect */ + ext4_fsblk_t fblock = ext4_inode_get_indirect_block(inode_ref->inode, 0); + if (fblock != 0) { + int rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 0, 0); + } + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t count = block_size / sizeof(uint32_t); + + struct ext4_block block; + + /* 2) Double indirect */ + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 1); + if (fblock != 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 1, 0); + } + + /* 3) Tripple indirect */ + struct ext4_block subblock; + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 2); + if (fblock == 0) + goto finish; + rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_trans_block_get(fs->bdev, &subblock, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + ext4_fsblk_t ind_subblk; + for (suboff = 0; suboff < count; ++suboff) { + ind_subblk = to_le32(((uint32_t *)subblock.data)[suboff]); + + if (ind_subblk == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_subblk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &subblock); + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &subblock); + + rc = ext4_balloc_free_block(inode_ref, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 2, 0); +finish: + /* Mark inode dirty for writing to the physical device */ + inode_ref->dirty = true; + + /* Free block with extended attributes if present */ + ext4_fsblk_t xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + if (xattr_block) { + int rc = ext4_balloc_free_block(inode_ref, xattr_block); + if (rc != EOK) + return rc; + + ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, 0); + } + + /* Free inode by allocator */ + if (ext4_inode_is_type(&fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) + rc = ext4_ialloc_free_inode(fs, inode_ref->index, true); + else + rc = ext4_ialloc_free_inode(fs, inode_ref->index, false); + + return rc; +} + + +/**@brief Release data block from i-node + * @param inode_ref I-node to release block from + * @param iblock Logical block to be released + * @return Error code + */ +static int ext4_fs_release_inode_block(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock) +{ + ext4_fsblk_t fblock; + + struct ext4_fs *fs = inode_ref->fs; + + /* Extents are handled otherwise = there is not support in this function + */ + ext4_assert(!( + ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)))); + + struct ext4_inode *inode = inode_ref->inode; + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + fblock = ext4_inode_get_direct_block(inode, iblock); + + /* Sparse file */ + if (fblock == 0) + return EOK; + + ext4_inode_set_direct_block(inode, iblock, 0); + return ext4_balloc_free_block(inode_ref, fblock); + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int level = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + level = i; + break; + } + } + + if (level == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t block_offset_in_level = + (uint32_t)(iblock - fs->inode_block_limits[level - 1]); + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode, level - 1); + uint32_t offset_in_block = + (uint32_t)(block_offset_in_level / fs->inode_blocks_per_level[level - 1]); + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + struct ext4_block block; + + while (level > 0) { + + /* Sparse check */ + if (current_block == 0) + return EOK; + + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = + to_le32(((uint32_t *)block.data)[offset_in_block]); + + /* Set zero if physical data block address found */ + if (level == 1) { + ((uint32_t *)block.data)[offset_in_block] = to_le32(0); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + level--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (level == 0) + break; + + /* Visit the next level */ + block_offset_in_level %= fs->inode_blocks_per_level[level]; + offset_in_block = (uint32_t)(block_offset_in_level / + fs->inode_blocks_per_level[level - 1]); + } + + fblock = current_block; + if (fblock == 0) + return EOK; + + /* Physical block is not referenced, it can be released */ + return ext4_balloc_free_block(inode_ref, fblock); +} + +int ext4_fs_truncate_inode(struct ext4_inode_ref *inode_ref, uint64_t new_size) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t i; + int r; + bool v; + + /* Check flags, if i-node can be truncated */ + if (!ext4_inode_can_truncate(sb, inode_ref->inode)) + return EINVAL; + + /* If sizes are equal, nothing has to be done. */ + uint64_t old_size = ext4_inode_get_size(sb, inode_ref->inode); + if (old_size == new_size) + return EOK; + + /* It's not supported to make the larger file by truncate operation */ + if (old_size < new_size) + return EINVAL; + + /* For symbolic link which is small enough */ + v = ext4_inode_is_type(sb, inode_ref->inode, EXT4_INODE_MODE_SOFTLINK); + if (v && old_size < sizeof(inode_ref->inode->blocks) && + !ext4_inode_get_blocks_count(sb, inode_ref->inode)) { + char *content = (char *)inode_ref->inode->blocks + new_size; + memset(content, 0, + sizeof(inode_ref->inode->blocks) - (uint32_t)new_size); + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; + } + + i = ext4_inode_type(sb, inode_ref->inode); + if (i == EXT4_INODE_MODE_CHARDEV || + i == EXT4_INODE_MODE_BLOCKDEV || + i == EXT4_INODE_MODE_SOCKET) { + inode_ref->inode->blocks[0] = 0; + inode_ref->inode->blocks[1] = 0; + + inode_ref->dirty = true; + return EOK; + } + + /* Compute how many blocks will be released */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t new_blocks_cnt = (uint32_t)((new_size + block_size - 1) / block_size); + uint32_t old_blocks_cnt = (uint32_t)((old_size + block_size - 1) / block_size); + uint32_t diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + /* Extents require special operation */ + if (diff_blocks_cnt) { + r = ext4_extent_remove_space(inode_ref, new_blocks_cnt, + EXT_MAX_BLOCKS); + if (r != EOK) + return r; + + } + } else +#endif + { + /* Release data blocks from the end of file */ + + /* Starting from 1 because of logical blocks are numbered from 0 + */ + for (i = 0; i < diff_blocks_cnt; ++i) { + r = ext4_fs_release_inode_block(inode_ref, + new_blocks_cnt + i); + if (r != EOK) + return r; + } + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; +} + +/**@brief Compute 'goal' for inode index + * @param inode_ref Reference to inode, to allocate block for + * @return goal + */ +ext4_fsblk_t ext4_fs_inode_to_goal_block(struct ext4_inode_ref *inode_ref) +{ + uint32_t grp_inodes = ext4_get32(&inode_ref->fs->sb, inodes_per_group); + return (inode_ref->index - 1) / grp_inodes; +} + +/**@brief Compute 'goal' for allocation algorithm (For blockmap). + * @param inode_ref Reference to inode, to allocate block for + * @return error code + */ +int ext4_fs_indirect_find_goal(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *goal) +{ + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + *goal = 0; + + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t iblock_cnt = (uint32_t)(inode_size / block_size); + + if (inode_size % block_size != 0) + iblock_cnt++; + + /* If inode has some blocks, get last block address + 1 */ + if (iblock_cnt > 0) { + r = ext4_fs_get_inode_dblk_idx(inode_ref, iblock_cnt - 1, + goal, false); + if (r != EOK) + return r; + + if (*goal != 0) { + (*goal)++; + return r; + } + + /* If goal == 0, sparse file -> continue */ + } + + /* Identify block group of inode */ + + uint32_t inodes_per_bg = ext4_get32(sb, inodes_per_group); + uint32_t block_group = (inode_ref->index - 1) / inodes_per_bg; + block_size = ext4_sb_get_block_size(sb); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + r = ext4_fs_get_block_group_ref(inode_ref->fs, block_group, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Compute indexes */ + uint32_t bg_count = ext4_block_group_cnt(sb); + ext4_fsblk_t itab_first_block = ext4_bg_get_inode_table_first_block(bg, sb); + uint16_t itab_item_size = ext4_get16(sb, inode_size); + uint32_t itab_bytes; + + /* Check for last block group */ + if (block_group < bg_count - 1) { + itab_bytes = inodes_per_bg * itab_item_size; + } else { + /* Last block group could be smaller */ + uint32_t inodes_cnt = ext4_get32(sb, inodes_count); + + itab_bytes = (inodes_cnt - ((bg_count - 1) * inodes_per_bg)); + itab_bytes *= itab_item_size; + } + + ext4_fsblk_t inode_table_blocks = itab_bytes / block_size; + + if (itab_bytes % block_size) + inode_table_blocks++; + + *goal = itab_first_block + inode_table_blocks; + + return ext4_fs_put_block_group_ref(&bg_ref); +} + +static int ext4_fs_get_inode_dblk_idx_internal(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool extent_create, + bool support_unwritten __unused) +{ + struct ext4_fs *fs = inode_ref->fs; + + /* For empty file is situation simple */ + if (ext4_inode_get_size(&fs->sb, inode_ref->inode) == 0) { + *fblock = 0; + return EOK; + } + + ext4_fsblk_t current_block; + + (void)extent_create; +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle i-node using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + ext4_fsblk_t current_fsblk; + int rc = ext4_extent_get_blocks(inode_ref, iblock, 1, + ¤t_fsblk, extent_create, NULL); + if (rc != EOK) + return rc; + + current_block = current_fsblk; + *fblock = current_block; + + ext4_assert(*fblock || support_unwritten); + return EOK; + } +#endif + + struct ext4_inode *inode = inode_ref->inode; + + /* Direct block are read directly from array in i-node structure */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + current_block = + ext4_inode_get_direct_block(inode, (uint32_t)iblock); + *fblock = current_block; + return EOK; + } + + /* Determine indirection level of the target block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = (uint32_t)(iblock - fs->inode_block_limits[l - 1]); + current_block = ext4_inode_get_indirect_block(inode, l - 1); + uint32_t off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + + /* Sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + struct ext4_block block; + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + /* Load indirect block */ + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + /* Read block address from indirect block */ + current_block = + to_le32(((uint32_t *)block.data)[off_in_blk]); + + /* Put back indirect block untouched */ + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + /* Check for sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + /* Jump to the next level */ + l--; + + /* Termination condition - we have address of data block loaded + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + } + + *fblock = current_block; + + return EOK; +} + + +int ext4_fs_get_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock, + bool support_unwritten) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + false, support_unwritten); +} + +int ext4_fs_init_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t *fblock) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + true, true); +} + +static int ext4_fs_set_inode_data_block_index(struct ext4_inode_ref *inode_ref, + ext4_lblk_t iblock, ext4_fsblk_t fblock) +{ + struct ext4_fs *fs = inode_ref->fs; + +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle inode using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Not reachable */ + return ENOTSUP; + } +#endif + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + ext4_inode_set_direct_block(inode_ref->inode, (uint32_t)iblock, + (uint32_t)fblock); + inode_ref->dirty = true; + + return EOK; + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = (uint32_t)(iblock - fs->inode_block_limits[l - 1]); + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode_ref->inode, l - 1); + uint32_t off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + + ext4_fsblk_t new_blk; + + struct ext4_block block; + struct ext4_block new_block; + + /* Is needed to allocate indirect block on the i-node level */ + if (current_block == 0) { + /* Allocate new indirect block */ + ext4_fsblk_t goal; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) + return rc; + + /* Update i-node */ + ext4_inode_set_indirect_block(inode_ref->inode, l - 1, + (uint32_t)new_blk); + inode_ref->dirty = true; + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, new_blk); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, new_blk); + return rc; + } + + /* Initialize new block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + /* Put back the allocated block */ + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) + return rc; + + current_block = new_blk; + } + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = to_le32(((uint32_t *)block.data)[off_in_blk]); + if ((l > 1) && (current_block == 0)) { + ext4_fsblk_t goal; + rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Allocate new block */ + rc = + ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, + new_blk); + + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Initialize allocated block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Write block address to the parent */ + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)new_blk); + ext4_trans_set_block_dirty(block.buf); + current_block = new_blk; + } + + /* Will be finished, write the fblock address */ + if (l == 1) { + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)fblock); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + l--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = (uint32_t)(blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]); + } + + return EOK; +} + + +int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *fblock, ext4_lblk_t *iblock) +{ +#if CONFIG_EXTENT_ENABLE && CONFIG_EXTENTS_ENABLE + /* Handle extents separately */ + if ((ext4_sb_feature_incom(&inode_ref->fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + int rc; + ext4_fsblk_t current_fsblk; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + *iblock = (uint32_t)((inode_size + block_size - 1) / block_size); + + rc = ext4_extent_get_blocks(inode_ref, *iblock, 1, + ¤t_fsblk, true, NULL); + if (rc != EOK) + return rc; + + *fblock = current_fsblk; + ext4_assert(*fblock); + + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + + return rc; + } +#endif + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute next block index and allocate data block */ + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Align size i-node size */ + if ((inode_size % block_size) != 0) + inode_size += block_size - (inode_size % block_size); + + /* Logical blocks are numbered from 0 */ + uint32_t new_block_idx = (uint32_t)(inode_size / block_size); + + /* Allocate new physical block */ + ext4_fsblk_t goal, phys_block; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &phys_block); + if (rc != EOK) + return rc; + + /* Add physical block address to the i-node */ + rc = ext4_fs_set_inode_data_block_index(inode_ref, new_block_idx, + phys_block); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, phys_block); + return rc; + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + *fblock = phys_block; + *iblock = new_block_idx; + + return EOK; +} + +void ext4_fs_inode_links_count_inc(struct ext4_inode_ref *inode_ref) +{ + uint16_t link; + bool is_dx; + link = ext4_inode_get_links_cnt(inode_ref->inode); + link++; + ext4_inode_set_links_cnt(inode_ref->inode, link); + + is_dx = ext4_sb_feature_com(&inode_ref->fs->sb, EXT4_FCOM_DIR_INDEX) && + ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_INDEX); + + if (is_dx && link > 1) { + if (link >= EXT4_LINK_MAX || link == 2) { + ext4_inode_set_links_cnt(inode_ref->inode, 1); + + uint32_t v; + v = ext4_get32(&inode_ref->fs->sb, features_read_only); + v |= EXT4_FRO_COM_DIR_NLINK; + ext4_set32(&inode_ref->fs->sb, features_read_only, v); + } + } +} + +void ext4_fs_inode_links_count_dec(struct ext4_inode_ref *inode_ref) +{ + uint16_t links = ext4_inode_get_links_cnt(inode_ref->inode); + if (!ext4_inode_is_type(&inode_ref->fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + if (links > 0) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); + return; + } + + if (links > 2) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_hash.c b/lib/lwext4_rust/c/lwext4/src/ext4_hash.c new file mode 100644 index 0000000..ff6d031 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_hash.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * FreeBSD: + * Copyright (c) 2010, 2013 Zheng Liu + * Copyright (c) 2012, Vyacheslav Matyushin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * The following notice applies to the code in ext2_half_md4(): + * + * Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD4 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD4 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_hash.c + * @brief Directory indexing hash functions. + */ + +#include +#include +#include +#include +#include + +#include + +/* F, G, and H are MD4 functions */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* + * FF, GG, and HH are transformations for rounds 1, 2, and 3. + * Rotation is separated from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s) \ + { \ + (a) += F((b), (c), (d)) + (x); \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define GG(a, b, c, d, x, s) \ + { \ + (a) += G((b), (c), (d)) + (x) + (uint32_t)0x5A827999; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define HH(a, b, c, d, x, s) \ + { \ + (a) += H((b), (c), (d)) + (x) + (uint32_t)0x6ED9EBA1; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +/* + * MD4 basic transformation. It transforms state based on block. + * + * This is a half md4 algorithm since Linux uses this algorithm for dir + * index. This function is derived from the RSA Data Security, Inc. MD4 + * Message-Digest Algorithm and was modified as necessary. + * + * The return value of this function is uint32_t in Linux, but actually we don't + * need to check this value, so in our version this function doesn't return any + * value. + */ +static void ext2_half_md4(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t a = hash[0], b = hash[1], c = hash[2], d = hash[3]; + + /* Round 1 */ + FF(a, b, c, d, data[0], 3); + FF(d, a, b, c, data[1], 7); + FF(c, d, a, b, data[2], 11); + FF(b, c, d, a, data[3], 19); + FF(a, b, c, d, data[4], 3); + FF(d, a, b, c, data[5], 7); + FF(c, d, a, b, data[6], 11); + FF(b, c, d, a, data[7], 19); + + /* Round 2 */ + GG(a, b, c, d, data[1], 3); + GG(d, a, b, c, data[3], 5); + GG(c, d, a, b, data[5], 9); + GG(b, c, d, a, data[7], 13); + GG(a, b, c, d, data[0], 3); + GG(d, a, b, c, data[2], 5); + GG(c, d, a, b, data[4], 9); + GG(b, c, d, a, data[6], 13); + + /* Round 3 */ + HH(a, b, c, d, data[3], 3); + HH(d, a, b, c, data[7], 9); + HH(c, d, a, b, data[2], 11); + HH(b, c, d, a, data[6], 15); + HH(a, b, c, d, data[1], 3); + HH(d, a, b, c, data[5], 9); + HH(c, d, a, b, data[0], 11); + HH(b, c, d, a, data[4], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +/* + * Tiny Encryption Algorithm. + */ +static void ext2_tea(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t tea_delta = 0x9E3779B9; + uint32_t sum; + uint32_t x = hash[0], y = hash[1]; + int n = 16; + int i = 1; + + while (n-- > 0) { + sum = i * tea_delta; + x += ((y << 4) + data[0]) ^ (y + sum) ^ ((y >> 5) + data[1]); + y += ((x << 4) + data[2]) ^ (x + sum) ^ ((x >> 5) + data[3]); + i++; + } + + hash[0] += x; + hash[1] += y; +} + +static uint32_t ext2_legacy_hash(const char *name, int len, int unsigned_char) +{ + uint32_t h0, h1 = 0x12A3FE2D, h2 = 0x37ABE8F9; + uint32_t multi = 0x6D22F5; + const unsigned char *uname = (const unsigned char *)name; + const signed char *sname = (const signed char *)name; + int val, i; + + for (i = 0; i < len; i++) { + if (unsigned_char) + val = (unsigned int)*uname++; + else + val = (int)*sname++; + + h0 = h2 + (h1 ^ (val * multi)); + if (h0 & 0x80000000) + h0 -= 0x7FFFFFFF; + h2 = h1; + h1 = h0; + } + + return (h1 << 1); +} + +static void ext2_prep_hashbuf(const char *src, uint32_t slen, uint32_t *dst, + int dlen, int unsigned_char) +{ + uint32_t padding = slen | (slen << 8) | (slen << 16) | (slen << 24); + uint32_t buf_val; + int len, i; + int buf_byte; + const unsigned char *ubuf = (const unsigned char *)src; + const signed char *sbuf = (const signed char *)src; + + if (slen > (uint32_t)dlen) + len = dlen; + else + len = slen; + + buf_val = padding; + + for (i = 0; i < len; i++) { + if (unsigned_char) + buf_byte = (unsigned int)ubuf[i]; + else + buf_byte = (int)sbuf[i]; + + if ((i % 4) == 0) + buf_val = padding; + + buf_val <<= 8; + buf_val += buf_byte; + + if ((i % 4) == 3) { + *dst++ = buf_val; + dlen -= sizeof(uint32_t); + buf_val = padding; + } + } + + dlen -= sizeof(uint32_t); + if (dlen >= 0) + *dst++ = buf_val; + + dlen -= sizeof(uint32_t); + while (dlen >= 0) { + *dst++ = padding; + dlen -= sizeof(uint32_t); + } +} + +int ext2_htree_hash(const char *name, int len, const uint32_t *hash_seed, + int hash_version, uint32_t *hash_major, + uint32_t *hash_minor) +{ + uint32_t hash[4]; + uint32_t data[8]; + uint32_t major = 0, minor = 0; + int unsigned_char = 0; + + if (!name || !hash_major) + return (-1); + + if (len < 1 || len > 255) + goto error; + + hash[0] = 0x67452301; + hash[1] = 0xEFCDAB89; + hash[2] = 0x98BADCFE; + hash[3] = 0x10325476; + + if (hash_seed) + memcpy(hash, hash_seed, sizeof(hash)); + + switch (hash_version) { + case EXT2_HTREE_TEA_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_TEA: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 16, unsigned_char); + ext2_tea(hash, data); + len -= 16; + name += 16; + } + major = hash[0]; + minor = hash[1]; + break; + case EXT2_HTREE_LEGACY_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_LEGACY: + major = ext2_legacy_hash(name, len, unsigned_char); + break; + case EXT2_HTREE_HALF_MD4_UNSIGNED: + unsigned_char = 1; + /* FALLTHRU */ + case EXT2_HTREE_HALF_MD4: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 32, unsigned_char); + ext2_half_md4(hash, data); + len -= 32; + name += 32; + } + major = hash[1]; + minor = hash[2]; + break; + default: + goto error; + } + + major &= ~1; + if (major == (EXT2_HTREE_EOF << 1)) + major = (EXT2_HTREE_EOF - 1) << 1; + *hash_major = major; + if (hash_minor) + *hash_minor = minor; + + return EOK; + +error: + *hash_major = 0; + if (hash_minor) + *hash_minor = 0; + return ENOTSUP; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_ialloc.c b/lib/lwext4_rust/c/lwext4/src/ext4_ialloc.c new file mode 100644 index 0000000..f2c796f --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_ialloc.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_ialloc.c + * @brief Inode allocation procedures. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**@brief Convert i-node number to relative index in block group. + * @param sb Superblock + * @param inode I-node number to be converted + * @return Index of the i-node in the block group + */ +static uint32_t ext4_ialloc_inode_to_bgidx(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) % inodes_per_group; +} + +/**@brief Convert relative index of i-node to absolute i-node number. + * @param sb Superblock + * @param index Index to be converted + * @return Absolute number of the i-node + * + */ +static uint32_t ext4_ialloc_bgidx_to_inode(struct ext4_sblock *sb, + uint32_t index, uint32_t bgid) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return bgid * inodes_per_group + (index + 1); +} + +/**@brief Compute block group number from the i-node number. + * @param sb Superblock + * @param inode I-node number to be found the block group for + * @return Block group number computed from i-node number + */ +static uint32_t ext4_ialloc_get_bgid_of_inode(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) / inodes_per_group; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_ialloc_bitmap_csum(struct ext4_sblock *sb, void *bitmap) +{ + uint32_t csum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t inodes_per_group = + ext4_get32(sb, inodes_per_group); + + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode bitmap */ + csum = ext4_crc32c(csum, bitmap, (inodes_per_group + 7) / 8); + } + return csum; +} +#else +#define ext4_ialloc_bitmap_csum(...) 0 +#endif + +void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->inode_bitmap_csum_lo = lo_csum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_bitmap_csum_hi = hi_csum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_ialloc_verify_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->inode_bitmap_csum_lo != lo_csum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->inode_bitmap_csum_hi != hi_csum) + return false; + + return true; +} +#else +#define ext4_ialloc_verify_bitmap_csum(...) true +#endif + +int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + /* Compute index of block group and load it */ + uint32_t block_group = ext4_ialloc_get_bgid_of_inode(sb, index); + + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load i-node bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Free i-node in the bitmap */ + uint32_t index_in_group = ext4_ialloc_inode_to_bgidx(sb, index); + ext4_bmap_bit_clr(b.data, index_in_group); + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + + /* Put back the block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If released i-node is a directory, decrement used directories count + */ + if (is_dir) { + uint32_t bg_used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + bg_used_dirs--; + ext4_bg_set_used_dirs_count(bg, sb, bg_used_dirs); + } + + /* Update block group free inodes count */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + free_inodes++; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + bg_ref.dirty = true; + + /* Put back the modified block group */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock free inodes count */ + ext4_set32(sb, free_inodes_count, + ext4_get32(sb, free_inodes_count) + 1); + + return EOK; +} + +int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + uint32_t bgid = fs->last_inode_bg_id; + uint32_t bg_count = ext4_block_group_cnt(sb); + uint32_t sb_free_inodes = ext4_get32(sb, free_inodes_count); + bool rewind = false; + + /* Try to find free i-node in all block groups */ + while (bgid <= bg_count) { + + if (bgid == bg_count) { + if (rewind) + break; + bg_count = fs->last_inode_bg_id; + bgid = 0; + rewind = true; + continue; + } + + /* Load block group to check */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bgid, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Read necessary values for algorithm */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + uint32_t used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + + /* Check if this block group is good candidate for allocation */ + if (free_inodes > 0) { + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_add = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_add); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Try to allocate i-node in the bitmap */ + uint32_t inodes_in_bg; + uint32_t idx_in_bg; + + inodes_in_bg = ext4_inodes_in_group_cnt(sb, bgid); + rc = ext4_bmap_bit_find_clr(b.data, 0, inodes_in_bg, + &idx_in_bg); + /* Block group has not any free i-node */ + if (rc == ENOSPC) { + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + continue; + } + + ext4_bmap_bit_set(b.data, idx_in_bg); + + /* Free i-node found, save the bitmap */ + ext4_ialloc_set_bitmap_csum(sb,bg, + b.data); + ext4_trans_set_block_dirty(b.buf); + + ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* Modify filesystem counters */ + free_inodes--; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + /* Increment used directories counter */ + if (is_dir) { + used_dirs++; + ext4_bg_set_used_dirs_count(bg, sb, used_dirs); + } + + /* Decrease unused inodes count */ + uint32_t unused = + ext4_bg_get_itable_unused(bg, sb); + + uint32_t free = inodes_in_bg - unused; + + if (idx_in_bg >= free) { + unused = inodes_in_bg - (idx_in_bg + 1); + ext4_bg_set_itable_unused(bg, sb, unused); + } + + /* Save modified block group */ + bg_ref.dirty = true; + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock */ + sb_free_inodes--; + ext4_set32(sb, free_inodes_count, sb_free_inodes); + + /* Compute the absolute i-nodex number */ + *idx = ext4_ialloc_bgidx_to_inode(sb, idx_in_bg, bgid); + + fs->last_inode_bg_id = bgid; + + return EOK; + } + + /* Block group not modified, put it and jump to the next block + * group */ + ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + ++bgid; + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_inode.c b/lib/lwext4_rust/c/lwext4/src/ext4_inode.c new file mode 100644 index 0000000..ff3c234 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_inode.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_inode.c + * @brief Inode handle functions + */ + +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Compute number of bits for block count. + * @param block_size Filesystem block_size + * @return Number of bits + */ +static uint32_t ext4_inode_block_bits_count(uint32_t block_size) +{ + uint32_t bits = 8; + uint32_t size = block_size; + + do { + bits++; + size = size >> 1; + } while (size > 256); + + return bits; +} + +uint32_t ext4_inode_get_mode(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint32_t v = to_le16(inode->mode); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) { + v |= ((uint32_t)to_le16(inode->osd2.hurd2.mode_high)) << 16; + } + + return v; +} + +void ext4_inode_set_mode(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t mode) +{ + inode->mode = to_le16((mode << 16) >> 16); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) + inode->osd2.hurd2.mode_high = to_le16(mode >> 16); +} + +uint32_t ext4_inode_get_uid(struct ext4_inode *inode) +{ + return to_le32(inode->uid); +} + +void ext4_inode_set_uid(struct ext4_inode *inode, uint32_t uid) +{ + inode->uid = to_le32(uid); +} + +uint64_t ext4_inode_get_size(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint64_t v = to_le32(inode->size_lo); + + if ((ext4_get32(sb, rev_level) > 0) && + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE))) + v |= ((uint64_t)to_le32(inode->size_hi)) << 32; + + return v; +} + +void ext4_inode_set_size(struct ext4_inode *inode, uint64_t size) +{ + inode->size_lo = to_le32((size << 32) >> 32); + inode->size_hi = to_le32(size >> 32); +} + +uint32_t ext4_inode_get_csum(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t v = to_le16(inode->osd2.linux2.checksum_lo); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + v |= ((uint32_t)to_le16(inode->checksum_hi)) << 16; + + return v; +} + +void ext4_inode_set_csum(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t checksum) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + inode->osd2.linux2.checksum_lo = + to_le16((checksum << 16) >> 16); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + inode->checksum_hi = to_le16(checksum >> 16); + +} + +uint32_t ext4_inode_get_access_time(struct ext4_inode *inode) +{ + return to_le32(inode->access_time); +} +void ext4_inode_set_access_time(struct ext4_inode *inode, uint32_t time) +{ + inode->access_time = to_le32(time); +} + +uint32_t ext4_inode_get_change_inode_time(struct ext4_inode *inode) +{ + return to_le32(inode->change_inode_time); +} +void ext4_inode_set_change_inode_time(struct ext4_inode *inode, uint32_t time) +{ + inode->change_inode_time = to_le32(time); +} + +uint32_t ext4_inode_get_modif_time(struct ext4_inode *inode) +{ + return to_le32(inode->modification_time); +} + +void ext4_inode_set_modif_time(struct ext4_inode *inode, uint32_t time) +{ + inode->modification_time = to_le32(time); +} + +uint32_t ext4_inode_get_del_time(struct ext4_inode *inode) +{ + return to_le32(inode->deletion_time); +} + +void ext4_inode_set_del_time(struct ext4_inode *inode, uint32_t time) +{ + inode->deletion_time = to_le32(time); +} + +uint32_t ext4_inode_get_gid(struct ext4_inode *inode) +{ + return to_le32(inode->gid); +} +void ext4_inode_set_gid(struct ext4_inode *inode, uint32_t gid) +{ + inode->gid = to_le32(gid); +} + +uint16_t ext4_inode_get_links_cnt(struct ext4_inode *inode) +{ + return to_le16(inode->links_count); +} +void ext4_inode_set_links_cnt(struct ext4_inode *inode, uint16_t cnt) +{ + inode->links_count = to_le16(cnt); +} + +uint64_t ext4_inode_get_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode) +{ + uint64_t cnt = to_le32(inode->blocks_count_lo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) { + + /* 48-bit field */ + cnt |= (uint64_t)to_le16(inode->osd2.linux2.blocks_high) << 32; + + if (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_HUGE_FILE)) { + + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t b = ext4_inode_block_bits_count(block_count); + return cnt << (b - 9); + } + } + + return cnt; +} + +int ext4_inode_set_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode, uint64_t count) +{ + /* 32-bit maximum */ + uint64_t max = 0; + max = ~max >> 32; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = 0; + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + + return EOK; + } + + /* Check if there can be used huge files (many blocks) */ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) + return EINVAL; + + /* 48-bit maximum */ + max = 0; + max = ~max >> 16; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + } else { + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t block_bits =ext4_inode_block_bits_count(block_count); + + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + count = count >> (block_bits - 9); + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + } + + return EOK; +} + +uint32_t ext4_inode_get_flags(struct ext4_inode *inode) +{ + return to_le32(inode->flags); +} +void ext4_inode_set_flags(struct ext4_inode *inode, uint32_t flags) +{ + inode->flags = to_le32(flags); +} + +uint32_t ext4_inode_get_generation(struct ext4_inode *inode) +{ + return to_le32(inode->generation); +} +void ext4_inode_set_generation(struct ext4_inode *inode, uint32_t gen) +{ + inode->generation = to_le32(gen); +} + +uint16_t ext4_inode_get_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + return to_le16(inode->extra_isize); + else + return 0; +} + +void ext4_inode_set_extra_isize(struct ext4_sblock *sb, + struct ext4_inode *inode, + uint16_t size) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + inode->extra_isize = to_le16(size); +} + +uint64_t ext4_inode_get_file_acl(struct ext4_inode *inode, + struct ext4_sblock *sb) +{ + uint64_t v = to_le32(inode->file_acl_lo); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + v |= (uint32_t)to_le16(inode->osd2.linux2.file_acl_high) << 16; + + return v; +} + +void ext4_inode_set_file_acl(struct ext4_inode *inode, struct ext4_sblock *sb, + uint64_t acl) +{ + inode->file_acl_lo = to_le32((acl << 32) >> 32); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + inode->osd2.linux2.file_acl_high = to_le16((uint16_t)(acl >> 32)); +} + +uint32_t ext4_inode_get_direct_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx]); +} +void ext4_inode_set_direct_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx] = to_le32(block); +} + +uint32_t ext4_inode_get_indirect_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK]); +} + +void ext4_inode_set_indirect_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK] = to_le32(block); +} + +uint32_t ext4_inode_get_dev(struct ext4_inode *inode) +{ + uint32_t dev_0, dev_1; + dev_0 = ext4_inode_get_direct_block(inode, 0); + dev_1 = ext4_inode_get_direct_block(inode, 1); + + if (dev_0) + return dev_0; + else + return dev_1; +} + +void ext4_inode_set_dev(struct ext4_inode *inode, uint32_t dev) +{ + if (dev & ~0xFFFF) + ext4_inode_set_direct_block(inode, 1, dev); + else + ext4_inode_set_direct_block(inode, 0, dev); +} + +uint32_t ext4_inode_type(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + return (ext4_inode_get_mode(sb, inode) & EXT4_INODE_MODE_TYPE_MASK); +} + +bool ext4_inode_is_type(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t type) +{ + return ext4_inode_type(sb, inode) == type; +} + +bool ext4_inode_has_flag(struct ext4_inode *inode, uint32_t f) +{ + return ext4_inode_get_flags(inode) & f; +} + +void ext4_inode_clear_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags & (~f); + ext4_inode_set_flags(inode, flags); +} + +void ext4_inode_set_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags | f; + ext4_inode_set_flags(inode, flags); +} + +bool ext4_inode_can_truncate(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + if ((ext4_inode_has_flag(inode, EXT4_INODE_FLAG_APPEND)) || + (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_IMMUTABLE))) + return false; + + if ((ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_DIRECTORY)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_SOFTLINK))) + return true; + + return false; +} + +struct ext4_extent_header * +ext4_inode_get_extent_header(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_journal.c b/lib/lwext4_rust/c/lwext4/src/ext4_journal.c new file mode 100644 index 0000000..7874f58 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_journal.c @@ -0,0 +1,2291 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_journal.c + * @brief Journal handle functions + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/**@brief Revoke entry during journal replay.*/ +struct revoke_entry { + /**@brief Block number not to be replayed.*/ + ext4_fsblk_t block; + + /**@brief For any transaction id smaller + * than trans_id, records of @block + * in those transactions should not + * be replayed.*/ + uint32_t trans_id; + + /**@brief Revoke tree node.*/ + RB_ENTRY(revoke_entry) revoke_node; +}; + +/**@brief Valid journal replay information.*/ +struct recover_info { + /**@brief Starting transaction id.*/ + uint32_t start_trans_id; + + /**@brief Ending transaction id.*/ + uint32_t last_trans_id; + + /**@brief Used as internal argument.*/ + uint32_t this_trans_id; + + /**@brief No of transactions went through.*/ + uint32_t trans_cnt; + + /**@brief RB-Tree storing revoke entries.*/ + RB_HEAD(jbd_revoke, revoke_entry) revoke_root; +}; + +/**@brief Journal replay internal arguments.*/ +struct replay_arg { + /**@brief Journal replay information.*/ + struct recover_info *info; + + /**@brief Current block we are on.*/ + uint32_t *this_block; + + /**@brief Current trans_id we are on.*/ + uint32_t this_trans_id; +}; + +/* Make sure we wrap around the log correctly! */ +#define wrap(sb, var) \ +do { \ + if (var >= jbd_get32((sb), maxlen)) \ + var -= (jbd_get32((sb), maxlen) - jbd_get32((sb), first)); \ +} while (0) + +static inline int32_t +trans_id_diff(uint32_t x, uint32_t y) +{ + int32_t diff = x - y; + return diff; +} + +static int +jbd_revoke_entry_cmp(struct revoke_entry *a, struct revoke_entry *b) +{ + if (a->block > b->block) + return 1; + else if (a->block < b->block) + return -1; + return 0; +} + +static int +jbd_block_rec_cmp(struct jbd_block_rec *a, struct jbd_block_rec *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +static int +jbd_revoke_rec_cmp(struct jbd_revoke_rec *a, struct jbd_revoke_rec *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(jbd_revoke, revoke_entry, revoke_node, + jbd_revoke_entry_cmp, static inline) +RB_GENERATE_INTERNAL(jbd_block, jbd_block_rec, block_rec_node, + jbd_block_rec_cmp, static inline) +RB_GENERATE_INTERNAL(jbd_revoke_tree, jbd_revoke_rec, revoke_node, + jbd_revoke_rec_cmp, static inline) + +#define jbd_alloc_revoke_entry() ext4_calloc(1, sizeof(struct revoke_entry)) +#define jbd_free_revoke_entry(addr) ext4_free(addr) + +static int jbd_has_csum(struct jbd_sb *jbd_sb) +{ + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V2)) + return 2; + + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V3)) + return 3; + + return 0; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_sb_csum(struct jbd_sb *jbd_sb) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(jbd_sb)) { + uint32_t orig_checksum = jbd_sb->checksum; + jbd_set32(jbd_sb, checksum, 0); + /* Calculate crc32c checksum against tho whole superblock */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_sb, + JBD_SUPERBLOCK_SIZE); + jbd_sb->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_sb_csum(...) 0 +#endif + +static void jbd_sb_csum_set(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return; + + jbd_set32(jbd_sb, checksum, jbd_sb_csum(jbd_sb)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_sb_csum(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return true; + + return jbd_sb_csum(jbd_sb) == jbd_get32(jbd_sb, checksum); +} +#else +#define jbd_verify_sb_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = + (struct jbd_block_tail *)((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + uint32_t orig_checksum = tail->checksum; + tail->checksum = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, bhdr, + block_size); + tail->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_meta_csum(...) 0 +#endif + +static void jbd_meta_csum_set(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + tail->checksum = to_be32(jbd_meta_csum(jbd_fs, bhdr)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return jbd_meta_csum(jbd_fs, bhdr) == to_be32(tail->checksum); +} +#else +#define jbd_verify_meta_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint8_t orig_checksum_type = header->chksum_type, + orig_checksum_size = header->chksum_size; + uint32_t orig_checksum = header->chksum[0]; + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, header, + block_size); + + header->chksum_type = orig_checksum_type; + header->chksum_size = orig_checksum_size; + header->chksum[0] = orig_checksum; + } + return checksum; +} +#else +#define jbd_commit_csum(...) 0 +#endif + +static void jbd_commit_csum_set(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = jbd_commit_csum(jbd_fs, header); +} + +#if CONFIG_META_CSUM_ENABLE +static bool jbd_verify_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return header->chksum[0] == to_be32(jbd_commit_csum(jbd_fs, + header)); +} +#else +#define jbd_verify_commit_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +/* + * NOTE: We only make use of @csum parameter when + * JBD_FEATURE_COMPAT_CHECKSUM is enabled. + */ +static uint32_t jbd_block_csum(struct jbd_fs *jbd_fs, const void *buf, + uint32_t csum, + uint32_t sequence) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Then calculate crc32c checksum against sequence no. */ + checksum = ext4_crc32c(checksum, &sequence, + sizeof(uint32_t)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, buf, + block_size); + } else if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32(csum, buf, + block_size); + } + return checksum; +} +#else +#define jbd_block_csum(...) 0 +#endif + +static void jbd_block_tag_csum_set(struct jbd_fs *jbd_fs, void *__tag, + uint32_t checksum) +{ + int ver = jbd_has_csum(&jbd_fs->sb); + if (!ver) + return; + + if (ver == 2) { + struct jbd_block_tag *tag = __tag; + tag->checksum = (uint16_t)to_be32(checksum); + } else { + struct jbd_block_tag3 *tag = __tag; + tag->checksum = to_be32(checksum); + } +} + +/**@brief Write jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_write(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + jbd_sb_csum_set(s); + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_writebytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Read jbd superblock from disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_read(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_readbytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Verify jbd superblock. + * @param sb jbd superblock + * @return true if jbd superblock is valid */ +static bool jbd_verify_sb(struct jbd_sb *sb) +{ + struct jbd_bhdr *header = &sb->header; + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) + return false; + + if (jbd_get32(header, blocktype) != JBD_SUPERBLOCK && + jbd_get32(header, blocktype) != JBD_SUPERBLOCK_V2) + return false; + + return jbd_verify_sb_csum(sb); +} + +/**@brief Write back dirty jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +static int jbd_write_sb(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + if (jbd_fs->dirty) { + rc = jbd_sb_write(jbd_fs, &jbd_fs->sb); + if (rc != EOK) + return rc; + + jbd_fs->dirty = false; + } + return rc; +} + +/**@brief Get reference to jbd filesystem. + * @param fs Filesystem to load journal of + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_get_fs(struct ext4_fs *fs, + struct jbd_fs *jbd_fs) +{ + int rc; + uint32_t journal_ino; + + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + /* See if there is journal inode on this filesystem.*/ + /* FIXME: detection on existance ofbkejournal bdev is + * missing.*/ + journal_ino = ext4_get32(&fs->sb, journal_inode_number); + + rc = ext4_fs_get_inode_ref(fs, + journal_ino, + &jbd_fs->inode_ref); + if (rc != EOK) + return rc; + + rc = jbd_sb_read(jbd_fs, &jbd_fs->sb); + if (rc != EOK) + goto Error; + + if (!jbd_verify_sb(&jbd_fs->sb)) { + rc = EIO; + goto Error; + } + + if (rc == EOK) + jbd_fs->bdev = fs->bdev; + + return rc; +Error: + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + + return rc; +} + +/**@brief Put reference of jbd filesystem. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_put_fs(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + rc = jbd_write_sb(jbd_fs); + + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + return rc; +} + +/**@brief Data block lookup helper. + * @param jbd_fs jbd filesystem + * @param iblock block index + * @param fblock logical block address + * @return standard error code*/ +int jbd_inode_bmap(struct jbd_fs *jbd_fs, + ext4_lblk_t iblock, + ext4_fsblk_t *fblock) +{ + int rc = ext4_fs_get_inode_dblk_idx( + &jbd_fs->inode_ref, + iblock, + fblock, + false); + return rc; +} + +/**@brief jbd block get function (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + struct ext4_blockdev *bdev = jbd_fs->bdev; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + + /* Lookup the logical block address of + * fblock.*/ + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + rc = ext4_block_get(bdev, block, fblock); + + /* If succeeded, mark buffer as BC_FLUSH to indicate + * that data should be written to disk immediately.*/ + if (rc == EOK) { + ext4_bcache_set_flag(block->buf, BC_FLUSH); + /* As we don't want to occupy too much space + * in block cache, we set this buffer BC_TMP.*/ + ext4_bcache_set_flag(block->buf, BC_TMP); + } + + return rc; +} + +/**@brief jbd block get function (through cache, don't read). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get_noread(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + struct ext4_blockdev *bdev = jbd_fs->bdev; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + rc = ext4_block_get_noread(bdev, block, fblock); + if (rc == EOK) + ext4_bcache_set_flag(block->buf, BC_FLUSH); + + return rc; +} + +/**@brief jbd block set procedure (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @return standard error code*/ +static int jbd_block_set(struct jbd_fs *jbd_fs, + struct ext4_block *block) +{ + struct ext4_blockdev *bdev = jbd_fs->bdev; + return ext4_block_set(bdev, block); +} + +/**@brief helper functions to calculate + * block tag size, not including UUID part. + * @param jbd_fs jbd filesystem + * @return tag size in bytes*/ +static int jbd_tag_bytes(struct jbd_fs *jbd_fs) +{ + int size; + + /* It is very easy to deal with the case which + * JBD_FEATURE_INCOMPAT_CSUM_V3 is enabled.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + return sizeof(struct jbd_block_tag3); + + size = sizeof(struct jbd_block_tag); + + /* If JBD_FEATURE_INCOMPAT_CSUM_V2 is enabled, + * add 2 bytes to size.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2)) + size += sizeof(uint16_t); + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + return size; + + /* If block number is 4 bytes in size, + * minus 4 bytes from size */ + return size - sizeof(uint32_t); +} + +/**@brief Tag information. */ +struct tag_info { + /**@brief Tag size in bytes, including UUID part.*/ + int tag_bytes; + + /**@brief block number stored in this tag.*/ + ext4_fsblk_t block; + + /**@brief Is the first 4 bytes of block equals to + * JBD_MAGIC_NUMBER? */ + bool is_escape; + + /**@brief whether UUID part exists or not.*/ + bool uuid_exist; + + /**@brief UUID content if UUID part exists.*/ + uint8_t uuid[UUID_SIZE]; + + /**@brief Is this the last tag? */ + bool last_tag; + + /**@brief crc32c checksum. */ + uint32_t checksum; +}; + +/**@brief Extract information from a block tag. + * @param __tag pointer to the block tag + * @param tag_bytes block tag size of this jbd filesystem + * @param remain_buf_size size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_extract_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int tag_bytes, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + tag_info->tag_bytes = tag_bytes; + tag_info->uuid_exist = false; + tag_info->last_tag = false; + tag_info->is_escape = false; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get32(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->is_escape = true; + + if (!(jbd_get32(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get32(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } else { + struct jbd_block_tag *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get16(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->is_escape = true; + + if (!(jbd_get16(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get16(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } + return EOK; +} + +/**@brief Write information to a block tag. + * @param __tag pointer to the block tag + * @param remain_buf_size size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_write_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + int tag_bytes = jbd_tag_bytes(jbd_fs); + + tag_info->tag_bytes = tag_bytes; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag3)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_LAST_TAG); + + if (tag_info->is_escape) + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_ESCAPE); + + } else { + struct jbd_block_tag *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_LAST_TAG); + + + if (tag_info->is_escape) + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_ESCAPE); + + } + return EOK; +} + +/**@brief Iterate all block tags in a block. + * @param jbd_fs jbd filesystem + * @param __tag_start pointer to the block + * @param tag_tbl_size size of the block + * @param func callback routine to indicate that + * a block tag is found + * @param arg additional argument to be passed to func */ +static void +jbd_iterate_block_table(struct jbd_fs *jbd_fs, + void *__tag_start, + int32_t tag_tbl_size, + void (*func)(struct jbd_fs * jbd_fs, + struct tag_info *tag_info, + void *arg), + void *arg) +{ + char *tag_start, *tag_ptr; + int tag_bytes = jbd_tag_bytes(jbd_fs); + tag_start = __tag_start; + tag_ptr = tag_start; + + /* Cut off the size of block tail storing checksum. */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2) || + JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + while (tag_tbl_size) { + struct tag_info tag_info; + int rc = jbd_extract_block_tag(jbd_fs, + tag_ptr, + tag_bytes, + tag_tbl_size, + &tag_info); + if (rc != EOK) + break; + + if (func) + func(jbd_fs, &tag_info, arg); + + /* Stop the iteration when we reach the last tag. */ + if (tag_info.last_tag) + break; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + } +} + +static void jbd_display_block_tags(struct jbd_fs *jbd_fs, + struct tag_info *tag_info, + void *arg) +{ + uint32_t *iblock = arg; + ext4_dbg(DEBUG_JBD, "Block in block_tag: %" PRIu64 "\n", tag_info->block); + (*iblock)++; + wrap(&jbd_fs->sb, *iblock); + (void)jbd_fs; + return; +} + +static struct revoke_entry * +jbd_revoke_entry_lookup(struct recover_info *info, ext4_fsblk_t block) +{ + struct revoke_entry tmp = { + .block = block + }; + + return RB_FIND(jbd_revoke, &info->revoke_root, &tmp); +} + +/**@brief Replay a block in a transaction. + * @param jbd_fs jbd filesystem + * @param tag_info tag_info of the logged block.*/ +static void jbd_replay_block_tags(struct jbd_fs *jbd_fs, + struct tag_info *tag_info, + void *__arg) +{ + int r; + struct replay_arg *arg = __arg; + struct recover_info *info = arg->info; + uint32_t *this_block = arg->this_block; + struct revoke_entry *revoke_entry; + struct ext4_block journal_block, ext4_block; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + + (*this_block)++; + wrap(&jbd_fs->sb, *this_block); + + /* We replay this block only if the current transaction id + * is equal or greater than that in revoke entry.*/ + revoke_entry = jbd_revoke_entry_lookup(info, tag_info->block); + if (revoke_entry && + trans_id_diff(arg->this_trans_id, revoke_entry->trans_id) <= 0) + return; + + ext4_dbg(DEBUG_JBD, + "Replaying block in block_tag: %" PRIu64 "\n", + tag_info->block); + + r = jbd_block_get(jbd_fs, &journal_block, *this_block); + if (r != EOK) + return; + + /* We need special treatment for ext4 superblock. */ + if (tag_info->block) { + r = ext4_block_get_noread(fs->bdev, &ext4_block, tag_info->block); + if (r != EOK) { + jbd_block_set(jbd_fs, &journal_block); + return; + } + + memcpy(ext4_block.data, + journal_block.data, + jbd_get32(&jbd_fs->sb, blocksize)); + + if (tag_info->is_escape) + ((struct jbd_bhdr *)ext4_block.data)->magic = + to_be32(JBD_MAGIC_NUMBER); + + ext4_bcache_set_dirty(ext4_block.buf); + ext4_block_set(fs->bdev, &ext4_block); + } else { + uint16_t mount_count, state; + mount_count = ext4_get16(&fs->sb, mount_count); + state = ext4_get16(&fs->sb, state); + + memcpy(&fs->sb, + journal_block.data + EXT4_SUPERBLOCK_OFFSET, + EXT4_SUPERBLOCK_SIZE); + + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, state); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, mount_count); + } + + jbd_block_set(jbd_fs, &journal_block); + + return; +} + +/**@brief Add block address to revoke tree, along with + * its transaction id. + * @param info journal replay info + * @param block block address to be replayed.*/ +static void jbd_add_revoke_block_tags(struct recover_info *info, + ext4_fsblk_t block) +{ + struct revoke_entry *revoke_entry; + + ext4_dbg(DEBUG_JBD, "Add block %" PRIu64 " to revoke tree\n", block); + /* If the revoke entry with respect to the block address + * exists already, update its transaction id.*/ + revoke_entry = jbd_revoke_entry_lookup(info, block); + if (revoke_entry) { + revoke_entry->trans_id = info->this_trans_id; + return; + } + + revoke_entry = jbd_alloc_revoke_entry(); + ext4_assert(revoke_entry); + revoke_entry->block = block; + revoke_entry->trans_id = info->this_trans_id; + RB_INSERT(jbd_revoke, &info->revoke_root, revoke_entry); + + return; +} + +static void jbd_destroy_revoke_tree(struct recover_info *info) +{ + while (!RB_EMPTY(&info->revoke_root)) { + struct revoke_entry *revoke_entry = + RB_MIN(jbd_revoke, &info->revoke_root); + ext4_assert(revoke_entry); + RB_REMOVE(jbd_revoke, &info->revoke_root, revoke_entry); + jbd_free_revoke_entry(revoke_entry); + } +} + + +#define ACTION_SCAN 0 +#define ACTION_REVOKE 1 +#define ACTION_RECOVER 2 + +/**@brief Add entries in a revoke block to revoke tree. + * @param jbd_fs jbd filesystem + * @param header revoke block header + * @param info journal replay info*/ +static void jbd_build_revoke_tree(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct recover_info *info) +{ + char *blocks_entry; + struct jbd_revoke_header *revoke_hdr = + (struct jbd_revoke_header *)header; + uint32_t i, nr_entries, record_len = 4; + + /* If we are working on a 64bit jbd filesystem, */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + nr_entries = (jbd_get32(revoke_hdr, count) - + sizeof(struct jbd_revoke_header)) / + record_len; + + blocks_entry = (char *)(revoke_hdr + 1); + + for (i = 0;i < nr_entries;i++) { + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be64(*blocks)); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be32(*blocks)); + } + blocks_entry += record_len; + } +} + +static void jbd_debug_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + uint32_t *iblock) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_display_block_tags, + iblock); +} + +static void jbd_replay_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct replay_arg *arg) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_replay_block_tags, + arg); +} + +/**@brief The core routine of journal replay. + * @param jbd_fs jbd filesystem + * @param info journal replay info + * @param action action needed to be taken + * @return standard error code*/ +static int jbd_iterate_log(struct jbd_fs *jbd_fs, + struct recover_info *info, + int action) +{ + int r = EOK; + bool log_end = false; + struct jbd_sb *sb = &jbd_fs->sb; + uint32_t start_trans_id, this_trans_id; + uint32_t start_block, this_block; + + /* We start iterating valid blocks in the whole journal.*/ + start_trans_id = this_trans_id = jbd_get32(sb, sequence); + start_block = this_block = jbd_get32(sb, start); + if (action == ACTION_SCAN) + info->trans_cnt = 0; + else if (!info->trans_cnt) + log_end = true; + + ext4_dbg(DEBUG_JBD, "Start of journal at trans id: %" PRIu32 "\n", + start_trans_id); + + while (!log_end) { + struct ext4_block block; + struct jbd_bhdr *header; + /* If we are not scanning for the last + * valid transaction in the journal, + * we will stop when we reach the end of + * the journal.*/ + if (action != ACTION_SCAN) + if (trans_id_diff(this_trans_id, info->last_trans_id) > 0) { + log_end = true; + continue; + } + + r = jbd_block_get(jbd_fs, &block, this_block); + if (r != EOK) + break; + + header = (struct jbd_bhdr *)block.data; + /* This block does not have a valid magic number, + * so we have reached the end of the journal.*/ + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) { + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + /* If the transaction id we found is not expected, + * we may have reached the end of the journal. + * + * If we are not scanning the journal, something + * bad might have taken place. :-( */ + if (jbd_get32(header, sequence) != this_trans_id) { + if (action != ACTION_SCAN) + r = EIO; + + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + switch (jbd_get32(header, blocktype)) { + case JBD_DESCRIPTOR_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Descriptor block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Descriptor block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_RECOVER) { + struct replay_arg replay_arg; + replay_arg.info = info; + replay_arg.this_block = &this_block; + replay_arg.this_trans_id = this_trans_id; + + jbd_replay_descriptor_block(jbd_fs, + header, &replay_arg); + } else + jbd_debug_descriptor_block(jbd_fs, + header, &this_block); + + break; + case JBD_COMMIT_BLOCK: + if (!jbd_verify_commit_csum(jbd_fs, + (struct jbd_commit_header *)header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Commit block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Commit block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + /* + * This is the end of a transaction, + * we may now proceed to the next transaction. + */ + this_trans_id++; + if (action == ACTION_SCAN) + info->trans_cnt++; + break; + case JBD_REVOKE_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Revoke block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Revoke block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_REVOKE) { + info->this_trans_id = this_trans_id; + jbd_build_revoke_tree(jbd_fs, + header, info); + } + break; + default: + log_end = true; + break; + } + jbd_block_set(jbd_fs, &block); + this_block++; + wrap(sb, this_block); + if (this_block == start_block) + log_end = true; + + } + ext4_dbg(DEBUG_JBD, "End of journal.\n"); + if (r == EOK && action == ACTION_SCAN) { + /* We have finished scanning the journal. */ + info->start_trans_id = start_trans_id; + if (trans_id_diff(this_trans_id, start_trans_id) > 0) + info->last_trans_id = this_trans_id - 1; + else + info->last_trans_id = this_trans_id; + } + + return r; +} + +/**@brief Replay journal. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_recover(struct jbd_fs *jbd_fs) +{ + int r; + struct recover_info info; + struct jbd_sb *sb = &jbd_fs->sb; + if (!sb->start) + return EOK; + + RB_INIT(&info.revoke_root); + + r = jbd_iterate_log(jbd_fs, &info, ACTION_SCAN); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_REVOKE); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_RECOVER); + if (r == EOK) { + /* If we successfully replay the journal, + * clear EXT4_FINCOM_RECOVER flag on the + * ext4 superblock, and set the start of + * journal to 0.*/ + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + jbd_set32(&jbd_fs->sb, start, 0); + jbd_set32(&jbd_fs->sb, sequence, info.last_trans_id); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + jbd_fs->dirty = true; + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + } + jbd_destroy_revoke_tree(&info); + return r; +} + +static void jbd_journal_write_sb(struct jbd_journal *journal) +{ + struct jbd_fs *jbd_fs = journal->jbd_fs; + jbd_set32(&jbd_fs->sb, start, journal->start); + jbd_set32(&jbd_fs->sb, sequence, journal->trans_id); + jbd_fs->dirty = true; +} + +/**@brief Start accessing the journal. + * @param jbd_fs jbd filesystem + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_start(struct jbd_fs *jbd_fs, + struct jbd_journal *journal) +{ + int r; + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + features_incompatible |= EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->first = jbd_get32(&jbd_fs->sb, first); + journal->start = journal->first; + journal->last = journal->first; + /* + * To invalidate any stale records we need to start from + * the checkpoint transaction ID of the previous journalling session + * plus 1. + */ + journal->trans_id = jbd_get32(&jbd_fs->sb, sequence) + 1; + journal->alloc_trans_id = journal->trans_id; + + journal->block_size = jbd_get32(&jbd_fs->sb, blocksize); + + TAILQ_INIT(&journal->cp_queue); + RB_INIT(&journal->block_rec_root); + journal->jbd_fs = jbd_fs; + jbd_journal_write_sb(journal); + r = jbd_write_sb(jbd_fs); + if (r != EOK) + return r; + + jbd_fs->bdev->journal = journal; + return EOK; +} + +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf __unused, + int res, + void *arg); + +/* + * This routine is only suitable to committed transactions. */ +static void jbd_journal_flush_trans(struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_journal *journal = trans->journal; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + void *tmp_data = ext4_malloc(journal->block_size); + ext4_assert(tmp_data); + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_buf *buf; + struct ext4_block block; + /* The buffer is not yet flushed. */ + buf = ext4_bcache_find_get(fs->bdev->bc, &block, + jbd_buf->block_rec->lba); + if (!(buf && ext4_bcache_test_flag(buf, BC_UPTODATE) && + jbd_buf->block_rec->trans == trans)) { + int r; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(); + r = jbd_block_get(journal->jbd_fs, + &jbd_block, + jbd_buf->jbd_lba); + ext4_assert(r == EOK); + memcpy(tmp_data, jbd_block.data, + journal->block_size); + ext4_block_set(fs->bdev, &jbd_block); + r = ext4_blocks_set_direct(fs->bdev, tmp_data, + jbd_buf->block_rec->lba, 1); + jbd_trans_end_write(fs->bdev->bc, buf, r, jbd_buf); + } else + ext4_block_flush_buf(fs->bdev, buf); + + if (buf) + ext4_block_set(fs->bdev, &block); + } + + ext4_free(tmp_data); +} + +static void +jbd_journal_skip_pure_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_free_trans(journal, + trans, false); + jbd_journal_write_sb(journal); +} + +void +jbd_journal_purge_cp_trans(struct jbd_journal *journal, + bool flush, + bool once) +{ + struct jbd_trans *trans; + while ((trans = TAILQ_FIRST(&journal->cp_queue))) { + if (!trans->data_cnt) { + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_skip_pure_revoke(journal, trans); + } else { + if (trans->data_cnt == + trans->written_cnt) { + journal->start = + trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_free_trans(journal, + trans, + false); + jbd_journal_write_sb(journal); + } else if (!flush) { + journal->start = + trans->start_iblock; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id; + jbd_journal_write_sb(journal); + break; + } else + jbd_journal_flush_trans(trans); + } + if (once) + break; + } +} + +/**@brief Stop accessing the journal. + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_stop(struct jbd_journal *journal) +{ + int r; + struct jbd_fs *jbd_fs = journal->jbd_fs; + uint32_t features_incompatible; + + /* Make sure that journalled content have reached + * the disk.*/ + jbd_journal_purge_cp_trans(journal, true, false); + + /* There should be no block record in this journal + * session. */ + if (!RB_EMPTY(&journal->block_rec_root)) + ext4_dbg(DEBUG_JBD, + DBG_WARN "There are still block records " + "in this journal session!\n"); + + features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->start = 0; + journal->trans_id = 0; + jbd_journal_write_sb(journal); + return jbd_write_sb(journal->jbd_fs); +} + +/**@brief Allocate a block in the journal. + * @param journal current journal session + * @param trans transaction + * @return allocated block address*/ +static uint32_t jbd_journal_alloc_block(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + uint32_t start_block; + + start_block = journal->last++; + trans->alloc_blocks++; + wrap(&journal->jbd_fs->sb, journal->last); + + /* If there is no space left, flush just one journalled + * transaction.*/ + if (journal->last == journal->start) { + jbd_journal_purge_cp_trans(journal, true, true); + ext4_assert(journal->last != journal->start); + } + + return start_block; +} + +static struct jbd_block_rec * +jbd_trans_block_rec_lookup(struct jbd_journal *journal, + ext4_fsblk_t lba) +{ + struct jbd_block_rec tmp = { + .lba = lba + }; + + return RB_FIND(jbd_block, + &journal->block_rec_root, + &tmp); +} + +static void +jbd_trans_change_ownership(struct jbd_block_rec *block_rec, + struct jbd_trans *new_trans) +{ + LIST_REMOVE(block_rec, tbrec_node); + if (new_trans) { + /* Now this block record belongs to this transaction. */ + LIST_INSERT_HEAD(&new_trans->tbrec_list, block_rec, tbrec_node); + } + block_rec->trans = new_trans; +} + +static inline struct jbd_block_rec * +jbd_trans_insert_block_rec(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_block_rec *block_rec; + block_rec = jbd_trans_block_rec_lookup(trans->journal, lba); + if (block_rec) { + jbd_trans_change_ownership(block_rec, trans); + return block_rec; + } + block_rec = ext4_calloc(1, sizeof(struct jbd_block_rec)); + if (!block_rec) + return NULL; + + block_rec->lba = lba; + block_rec->trans = trans; + TAILQ_INIT(&block_rec->dirty_buf_queue); + LIST_INSERT_HEAD(&trans->tbrec_list, block_rec, tbrec_node); + RB_INSERT(jbd_block, &trans->journal->block_rec_root, block_rec); + return block_rec; +} + +/* + * This routine will do the dirty works. + */ +static void +jbd_trans_finish_callback(struct jbd_journal *journal, + const struct jbd_trans *trans, + struct jbd_block_rec *block_rec, + bool abort, + bool revoke) +{ + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + if (block_rec->trans != trans) + return; + + if (!abort) { + struct jbd_buf *jbd_buf, *tmp; + TAILQ_FOREACH_SAFE(jbd_buf, + &block_rec->dirty_buf_queue, + dirty_buf_node, + tmp) { + jbd_trans_end_write(fs->bdev->bc, + NULL, + EOK, + jbd_buf); + } + } else { + /* + * We have to roll back data if the block is going to be + * aborted. + */ + struct jbd_buf *jbd_buf; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(), + block = EXT4_BLOCK_ZERO(); + jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + if (jbd_buf) { + if (!revoke) { + int r; + r = ext4_block_get_noread(fs->bdev, + &block, + block_rec->lba); + ext4_assert(r == EOK); + r = jbd_block_get(journal->jbd_fs, + &jbd_block, + jbd_buf->jbd_lba); + ext4_assert(r == EOK); + memcpy(block.data, jbd_block.data, + journal->block_size); + + jbd_trans_change_ownership(block_rec, + jbd_buf->trans); + + block.buf->end_write = jbd_trans_end_write; + block.buf->end_write_arg = jbd_buf; + + ext4_bcache_set_flag(jbd_block.buf, BC_TMP); + ext4_bcache_set_dirty(block.buf); + + ext4_block_set(fs->bdev, &jbd_block); + ext4_block_set(fs->bdev, &block); + return; + } else { + /* The revoked buffer is yet written. */ + jbd_trans_change_ownership(block_rec, + jbd_buf->trans); + } + } + } +} + +static inline void +jbd_trans_remove_block_rec(struct jbd_journal *journal, + struct jbd_block_rec *block_rec, + struct jbd_trans *trans) +{ + /* If this block record doesn't belong to this transaction, + * give up.*/ + if (block_rec->trans == trans) { + LIST_REMOVE(block_rec, tbrec_node); + RB_REMOVE(jbd_block, + &journal->block_rec_root, + block_rec); + ext4_free(block_rec); + } +} + +/**@brief Add block to a transaction and mark it dirty. + * @param trans transaction + * @param block block descriptor + * @return standard error code*/ +int jbd_trans_set_block_dirty(struct jbd_trans *trans, + struct ext4_block *block) +{ + struct jbd_buf *jbd_buf; + struct jbd_revoke_rec *rec, tmp_rec = { + .lba = block->lb_id + }; + struct jbd_block_rec *block_rec; + + if (block->buf->end_write == jbd_trans_end_write) { + jbd_buf = block->buf->end_write_arg; + if (jbd_buf && jbd_buf->trans == trans) + return EOK; + } + jbd_buf = ext4_calloc(1, sizeof(struct jbd_buf)); + if (!jbd_buf) + return ENOMEM; + + if ((block_rec = jbd_trans_insert_block_rec(trans, + block->lb_id)) == NULL) { + ext4_free(jbd_buf); + return ENOMEM; + } + + TAILQ_INSERT_TAIL(&block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block_rec = block_rec; + jbd_buf->trans = trans; + jbd_buf->block = *block; + ext4_bcache_inc_ref(block->buf); + + /* If the content reach the disk, notify us + * so that we may do a checkpoint. */ + block->buf->end_write = jbd_trans_end_write; + block->buf->end_write_arg = jbd_buf; + + trans->data_cnt++; + TAILQ_INSERT_HEAD(&trans->buf_queue, jbd_buf, buf_node); + + ext4_bcache_set_dirty(block->buf); + rec = RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec); + if (rec) { + RB_REMOVE(jbd_revoke_tree, &trans->revoke_root, + rec); + ext4_free(rec); + } + + return EOK; +} + +/**@brief Add block to be revoked to a transaction + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_revoke_rec tmp_rec = { + .lba = lba + }, *rec; + rec = RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec); + if (rec) + return EOK; + + rec = ext4_calloc(1, sizeof(struct jbd_revoke_rec)); + if (!rec) + return ENOMEM; + + rec->lba = lba; + RB_INSERT(jbd_revoke_tree, &trans->revoke_root, rec); + return EOK; +} + +/**@brief Try to add block to be revoked to a transaction. + * If @lba still remains in an transaction on checkpoint + * queue, add @lba as a revoked block to the transaction. + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_try_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_journal *journal = trans->journal; + struct jbd_block_rec *block_rec = + jbd_trans_block_rec_lookup(journal, lba); + + if (block_rec) { + if (block_rec->trans == trans) { + struct jbd_buf *jbd_buf = + TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + /* If there are still unwritten buffers. */ + if (TAILQ_FIRST(&block_rec->dirty_buf_queue) != + jbd_buf) + jbd_trans_revoke_block(trans, lba); + + } else + jbd_trans_revoke_block(trans, lba); + } + + return EOK; +} + +/**@brief Free a transaction + * @param journal current journal session + * @param trans transaction + * @param abort discard all the modifications on the block?*/ +void jbd_journal_free_trans(struct jbd_journal *journal, + struct jbd_trans *trans, + bool abort) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_revoke_rec *rec, *tmp2; + struct jbd_block_rec *block_rec, *tmp3; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + block_rec = jbd_buf->block_rec; + if (abort) { + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + ext4_bcache_clear_dirty(jbd_buf->block.buf); + ext4_block_set(fs->bdev, &jbd_buf->block); + } + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + jbd_trans_finish_callback(journal, + trans, + block_rec, + abort, + false); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + } + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp2) { + RB_REMOVE(jbd_revoke_tree, &trans->revoke_root, rec); + ext4_free(rec); + } + LIST_FOREACH_SAFE(block_rec, &trans->tbrec_list, tbrec_node, + tmp3) { + jbd_trans_remove_block_rec(journal, block_rec, trans); + } + + ext4_free(trans); +} + +/**@brief Write commit block for a transaction + * @param trans transaction + * @return standard error code*/ +static int jbd_trans_write_commit_block(struct jbd_trans *trans) +{ + int rc; + struct ext4_block block; + struct jbd_commit_header *header; + uint32_t commit_iblock; + struct jbd_journal *journal = trans->journal; + + commit_iblock = jbd_journal_alloc_block(journal, trans); + + rc = jbd_block_get_noread(journal->jbd_fs, &block, commit_iblock); + if (rc != EOK) + return rc; + + header = (struct jbd_commit_header *)block.data; + jbd_set32(&header->header, magic, JBD_MAGIC_NUMBER); + jbd_set32(&header->header, blocktype, JBD_COMMIT_BLOCK); + jbd_set32(&header->header, sequence, trans->trans_id); + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + header->chksum_type = JBD_CRC32_CHKSUM; + header->chksum_size = JBD_CRC32_CHKSUM_SIZE; + jbd_set32(header, chksum[0], trans->data_csum); + } + jbd_commit_csum_set(journal->jbd_fs, header); + ext4_bcache_set_dirty(block.buf); + ext4_bcache_set_flag(block.buf, BC_TMP); + rc = jbd_block_set(journal->jbd_fs, &block); + return rc; +} + +/**@brief Write descriptor block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int jbd_journal_prepare(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + struct ext4_block desc_block = EXT4_BLOCK_ZERO(), + data_block = EXT4_BLOCK_ZERO(); + int32_t tag_tbl_size = 0; + uint32_t desc_iblock = 0; + uint32_t data_iblock = 0; + char *tag_start = NULL, *tag_ptr = NULL; + struct jbd_buf *jbd_buf, *tmp; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + uint32_t checksum = EXT4_CRC32_INIT; + struct jbd_bhdr *bhdr = NULL; + void *data; + + /* Try to remove any non-dirty buffers from the tail of + * buf_queue. */ + TAILQ_FOREACH_REVERSE_SAFE(jbd_buf, &trans->buf_queue, + jbd_trans_buf, buf_node, tmp) { + struct jbd_revoke_rec tmp_rec = { + .lba = jbd_buf->block_rec->lba + }; + /* We stop the iteration when we find a dirty buffer. */ + if (ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) + break; + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true, + RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec)); + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + } + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, tmp) { + struct tag_info tag_info; + bool uuid_exist = false; + bool is_escape = false; + struct jbd_revoke_rec tmp_rec = { + .lba = jbd_buf->block_rec->lba + }; + if (!ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) { + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + + /* The buffer has not been modified, just release + * that jbd_buf. */ + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true, + RB_FIND(jbd_revoke_tree, + &trans->revoke_root, + &tmp_rec)); + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + ext4_free(jbd_buf); + continue; + } + checksum = jbd_block_csum(journal->jbd_fs, + jbd_buf->block.data, + checksum, + trans->trans_id); + if (((struct jbd_bhdr *)jbd_buf->block.data)->magic == + to_be32(JBD_MAGIC_NUMBER)) + is_escape = true; + +again: + if (!desc_iblock) { + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &desc_block, desc_iblock); + if (rc != EOK) + break; + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_DESCRIPTOR_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + tag_start = (char *)(bhdr + 1); + tag_ptr = tag_start; + uuid_exist = true; + tag_tbl_size = journal->block_size - + sizeof(struct jbd_bhdr); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + ext4_bcache_set_dirty(desc_block.buf); + ext4_bcache_set_flag(desc_block.buf, BC_TMP); + } + tag_info.block = jbd_buf->block.lb_id; + tag_info.uuid_exist = uuid_exist; + tag_info.is_escape = is_escape; + if (i == trans->data_cnt - 1) + tag_info.last_tag = true; + else + tag_info.last_tag = false; + + tag_info.checksum = checksum; + + if (uuid_exist) + memcpy(tag_info.uuid, journal->jbd_fs->sb.uuid, + UUID_SIZE); + + rc = jbd_write_block_tag(journal->jbd_fs, + tag_ptr, + tag_tbl_size, + &tag_info); + if (rc != EOK) { + jbd_meta_csum_set(journal->jbd_fs, bhdr); + desc_iblock = 0; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + if (rc != EOK) + break; + + goto again; + } + + data_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &data_block, data_iblock); + if (rc != EOK) { + desc_iblock = 0; + ext4_bcache_clear_dirty(desc_block.buf); + jbd_block_set(journal->jbd_fs, &desc_block); + break; + } + + data = data_block.data; + memcpy(data, jbd_buf->block.data, + journal->block_size); + if (is_escape) + ((struct jbd_bhdr *)data)->magic = 0; + + ext4_bcache_set_dirty(data_block.buf); + ext4_bcache_set_flag(data_block.buf, BC_TMP); + rc = jbd_block_set(journal->jbd_fs, &data_block); + if (rc != EOK) { + desc_iblock = 0; + ext4_bcache_clear_dirty(desc_block.buf); + jbd_block_set(journal->jbd_fs, &desc_block); + break; + } + jbd_buf->jbd_lba = data_iblock; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + + i++; + } + if (rc == EOK && desc_iblock) { + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)bhdr); + trans->data_csum = checksum; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Write revoke block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int +jbd_journal_prepare_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + struct ext4_block desc_block = EXT4_BLOCK_ZERO(); + int32_t tag_tbl_size = 0; + uint32_t desc_iblock = 0; + char *blocks_entry = NULL; + struct jbd_revoke_rec *rec, *tmp; + struct jbd_revoke_header *header = NULL; + int32_t record_len = 4; + struct jbd_bhdr *bhdr = NULL; + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp) { +again: + if (!desc_iblock) { + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, &desc_block, + desc_iblock); + if (rc != EOK) + break; + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_REVOKE_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + header = (struct jbd_revoke_header *)bhdr; + blocks_entry = (char *)(header + 1); + tag_tbl_size = journal->block_size - + sizeof(struct jbd_revoke_header); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + ext4_bcache_set_dirty(desc_block.buf); + ext4_bcache_set_flag(desc_block.buf, BC_TMP); + } + + if (tag_tbl_size < record_len) { + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + jbd_meta_csum_set(journal->jbd_fs, bhdr); + bhdr = NULL; + desc_iblock = 0; + header = NULL; + rc = jbd_block_set(journal->jbd_fs, &desc_block); + if (rc != EOK) + break; + + goto again; + } + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + *blocks = to_be64(rec->lba); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + *blocks = to_be32((uint32_t)rec->lba); + } + blocks_entry += record_len; + tag_tbl_size -= record_len; + + i++; + } + if (rc == EOK && desc_iblock) { + if (header != NULL) + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + + jbd_meta_csum_set(journal->jbd_fs, bhdr); + rc = jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Put references of block descriptors in a transaction. + * @param journal current journal session + * @param trans transaction*/ +void jbd_journal_cp_trans(struct jbd_journal *journal, struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_block block = jbd_buf->block; + ext4_block_set(fs->bdev, &block); + } +} + +/**@brief Update the start block of the journal when + * all the contents in a transaction reach the disk.*/ +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf, + int res, + void *arg) +{ + struct jbd_buf *jbd_buf = arg; + struct jbd_trans *trans = jbd_buf->trans; + struct jbd_block_rec *block_rec = jbd_buf->block_rec; + struct jbd_journal *journal = trans->journal; + bool first_in_queue = + trans == TAILQ_FIRST(&journal->cp_queue); + if (res != EOK) + trans->error = res; + + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + TAILQ_REMOVE(&block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + false, + false); + if (block_rec->trans == trans && buf) { + /* Clear the end_write and end_write_arg fields. */ + buf->end_write = NULL; + buf->end_write_arg = NULL; + } + + ext4_free(jbd_buf); + + trans->written_cnt++; + if (trans->written_cnt == trans->data_cnt) { + /* If it is the first transaction on checkpoint queue, + * we will shift the start of the journal to the next + * transaction, and remove subsequent written + * transactions from checkpoint queue until we find + * an unwritten one. */ + if (first_in_queue) { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, trans, trans_node); + jbd_journal_free_trans(journal, trans, false); + + jbd_journal_purge_cp_trans(journal, false, false); + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + } + } +} + +/**@brief Commit a transaction to the journal immediately. + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int __jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK; + uint32_t last = journal->last; + struct jbd_revoke_rec *rec, *tmp; + + trans->trans_id = journal->alloc_trans_id; + rc = jbd_journal_prepare(journal, trans); + if (rc != EOK) + goto Finish; + + rc = jbd_journal_prepare_revoke(journal, trans); + if (rc != EOK) + goto Finish; + + if (TAILQ_EMPTY(&trans->buf_queue) && + RB_EMPTY(&trans->revoke_root)) { + /* Since there are no entries in both buffer list + * and revoke entry list, we do not consider trans as + * complete transaction and just return EOK.*/ + jbd_journal_free_trans(journal, trans, false); + goto Finish; + } + + rc = jbd_trans_write_commit_block(trans); + if (rc != EOK) + goto Finish; + + journal->alloc_trans_id++; + + /* Complete the checkpoint of buffers which are revoked. */ + RB_FOREACH_SAFE(rec, jbd_revoke_tree, &trans->revoke_root, + tmp) { + struct jbd_block_rec *block_rec = + jbd_trans_block_rec_lookup(journal, rec->lba); + struct jbd_buf *jbd_buf = NULL; + if (block_rec) + jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + if (jbd_buf) { + struct ext4_buf *buf; + struct ext4_block block = EXT4_BLOCK_ZERO(); + /* + * We do this to reset the ext4_buf::end_write and + * ext4_buf::end_write_arg fields so that the checkpoint + * callback won't be triggered again. + */ + buf = ext4_bcache_find_get(journal->jbd_fs->bdev->bc, + &block, + jbd_buf->block_rec->lba); + jbd_trans_end_write(journal->jbd_fs->bdev->bc, + buf, + EOK, + jbd_buf); + if (buf) + ext4_block_set(journal->jbd_fs->bdev, &block); + } + } + + if (TAILQ_EMPTY(&journal->cp_queue)) { + /* + * This transaction is going to be the first object in the + * checkpoint queue. + * When the first transaction in checkpoint queue is completely + * written to disk, we shift the tail of the log to right. + */ + if (trans->data_cnt) { + journal->start = trans->start_iblock; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id; + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + jbd_journal_cp_trans(journal, trans); + } else { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_write_sb(journal); + jbd_journal_free_trans(journal, trans, false); + } + } else { + /* No need to do anything to the JBD superblock. */ + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + if (trans->data_cnt) + jbd_journal_cp_trans(journal, trans); + } +Finish: + if (rc != EOK && rc != ENOSPC) { + journal->last = last; + jbd_journal_free_trans(journal, trans, true); + } + return rc; +} + +/**@brief Allocate a new transaction + * @param journal current journal session + * @return transaction allocated*/ +struct jbd_trans * +jbd_journal_new_trans(struct jbd_journal *journal) +{ + struct jbd_trans *trans = NULL; + trans = ext4_calloc(1, sizeof(struct jbd_trans)); + if (!trans) + return NULL; + + /* We will assign a trans_id to this transaction, + * once it has been committed.*/ + trans->journal = journal; + trans->data_csum = EXT4_CRC32_INIT; + trans->error = EOK; + TAILQ_INIT(&trans->buf_queue); + return trans; +} + +/**@brief Commit a transaction to the journal immediately. + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +int jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int r = EOK; + r = __jbd_journal_commit_trans(journal, trans); + return r; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_mbr.c b/lib/lwext4_rust/c/lwext4/src/ext4_mbr.c new file mode 100644 index 0000000..0376545 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_mbr.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mbr.c + * @brief Master boot record parser + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define MBR_SIGNATURE 0xAA55 + +#pragma pack(push, 1) + +struct ext4_part_entry { + uint8_t status; + uint8_t chs1[3]; + uint8_t type; + uint8_t chs2[3]; + uint32_t first_lba; + uint32_t sectors; +}; + +struct ext4_mbr { + uint8_t bootstrap[442]; + uint32_t disk_id; + struct ext4_part_entry part_entry[4]; + uint16_t signature; +}; + +#pragma pack(pop) + +int ext4_mbr_scan(struct ext4_blockdev *parent, struct ext4_mbr_bdevs *bdevs) +{ + int r; + size_t i; + + ext4_dbg(DEBUG_MBR, DBG_INFO "ext4_mbr_scan\n"); + memset(bdevs, 0, sizeof(struct ext4_mbr_bdevs)); + r = ext4_block_init(parent); + if (r != EOK) + return r; + + r = ext4_block_readbytes(parent, 0, parent->bdif->ph_bbuf, 512); + if (r != EOK) { + goto blockdev_fini; + } + + const struct ext4_mbr *mbr = (void *)parent->bdif->ph_bbuf; + + if (to_le16(mbr->signature) != MBR_SIGNATURE) { + ext4_dbg(DEBUG_MBR, DBG_ERROR "ext4_mbr_scan: unknown " + "signature: 0x%x\n", to_le16(mbr->signature)); + r = ENOENT; + goto blockdev_fini; + } + + /*Show bootstrap code*/ + ext4_dbg(DEBUG_MBR, "mbr_part: bootstrap:"); + for (i = 0; i < sizeof(mbr->bootstrap); ++i) { + if (!(i & 0xF)) + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n"); + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "%02x, ", mbr->bootstrap[i]); + } + + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n\n"); + for (i = 0; i < 4; ++i) { + const struct ext4_part_entry *pe = &mbr->part_entry[i]; + ext4_dbg(DEBUG_MBR, "mbr_part: %d\n", (int)i); + ext4_dbg(DEBUG_MBR, "\tstatus: 0x%x\n", pe->status); + ext4_dbg(DEBUG_MBR, "\ttype 0x%x:\n", pe->type); + ext4_dbg(DEBUG_MBR, "\tfirst_lba: 0x%"PRIx32"\n", pe->first_lba); + ext4_dbg(DEBUG_MBR, "\tsectors: 0x%"PRIx32"\n", pe->sectors); + + if (!pe->sectors) + continue; /*Empty entry*/ + + if (pe->type != 0x83) + continue; /*Unsupported entry. 0x83 - linux native*/ + + bdevs->partitions[i].bdif = parent->bdif; + bdevs->partitions[i].part_offset = + (uint64_t)pe->first_lba * parent->bdif->ph_bsize; + bdevs->partitions[i].part_size = + (uint64_t)pe->sectors * parent->bdif->ph_bsize; + } + + blockdev_fini: + ext4_block_fini(parent); + return r; +} + +int ext4_mbr_write(struct ext4_blockdev *parent, struct ext4_mbr_parts *parts, uint32_t disk_id) +{ + int r; + uint64_t disk_size; + uint32_t division_sum = parts->division[0] + parts->division[1] + + parts->division[2] + parts->division[3]; + + if (division_sum > 100) + return EINVAL; + + ext4_dbg(DEBUG_MBR, DBG_INFO "ext4_mbr_write\n"); + r = ext4_block_init(parent); + if (r != EOK) + return r; + + disk_size = parent->part_size; + + /*Calculate CHS*/ + uint32_t k = 16; + while ((k < 256) && ((disk_size / k / 63) > 1024)) + k *= 2; + + if (k == 256) + --k; + + const uint32_t cyl_size = 63 * k; + const uint32_t cyl_count = disk_size / cyl_size; + + struct ext4_mbr *mbr = (void *)parent->bdif->ph_bbuf; + memset(mbr, 0, sizeof(struct ext4_mbr)); + + mbr->disk_id = disk_id; + + uint32_t cyl_it = 0; + for (int i = 0; i < 4; ++i) { + uint32_t cyl_part = cyl_count * parts->division[i] / 100; + if (!cyl_part) + continue; + + uint32_t part_start = cyl_it * cyl_size; + uint32_t part_size = cyl_part * cyl_size; + + if (i == 0) { + part_start += 63; + part_size -= 63 * parent->bdif->ph_bsize; + } + + uint32_t cyl_end = cyl_part + cyl_it - 1; + + mbr->part_entry[i].status = 0; + mbr->part_entry[i].chs1[0] = i ? 0 : 1;; + mbr->part_entry[i].chs1[1] = (cyl_it >> 2) + 1; + mbr->part_entry[i].chs1[2] = cyl_it; + mbr->part_entry[i].type = 0x83; + mbr->part_entry[i].chs2[0] = k - 1; + mbr->part_entry[i].chs2[1] = (cyl_end >> 2) + 63; + mbr->part_entry[i].chs2[2] = cyl_end; + + mbr->part_entry[i].first_lba = part_start; + mbr->part_entry[i].sectors = part_size / parent->bdif->ph_bsize; + + cyl_it += cyl_part; + } + + mbr->signature = MBR_SIGNATURE; + r = ext4_block_writebytes(parent, 0, parent->bdif->ph_bbuf, 512); + if (r != EOK) + goto blockdev_fini; + + + blockdev_fini: + ext4_block_fini(parent); + return r; +} + +/** + * @} + */ + diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_mkfs.c b/lib/lwext4_rust/c/lwext4/src/ext4_mkfs.c new file mode 100644 index 0000000..0dfc91f --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_mkfs.c @@ -0,0 +1,865 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mkfs.c + * @brief + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct fs_aux_info { + struct ext4_sblock *sb; + uint8_t *bg_desc_blk; + struct xattr_list_element *xattrs; + uint32_t first_data_block; + uint64_t len_blocks; + uint32_t inode_table_blocks; + uint32_t groups; + uint32_t bg_desc_blocks; + uint32_t default_i_flags; + uint32_t blocks_per_ind; + uint32_t blocks_per_dind; + uint32_t blocks_per_tind; +}; + +static inline int log_2(int j) +{ + int i; + + for (i = 0; j > 0; i++) + j >>= 1; + + return i - 1; +} + +static int sb2info(struct ext4_sblock *sb, struct ext4_mkfs_info *info) +{ + if (to_le16(sb->magic) != EXT4_SUPERBLOCK_MAGIC) + return EINVAL; + + info->block_size = 1024 << to_le32(sb->log_block_size); + info->blocks_per_group = to_le32(sb->blocks_per_group); + info->inodes_per_group = to_le32(sb->inodes_per_group); + info->inode_size = to_le16(sb->inode_size); + info->inodes = to_le32(sb->inodes_count); + info->feat_ro_compat = to_le32(sb->features_read_only); + info->feat_compat = to_le32(sb->features_compatible); + info->feat_incompat = to_le32(sb->features_incompatible); + info->bg_desc_reserve_blocks = to_le16(sb->s_reserved_gdt_blocks); + info->label = sb->volume_name; + info->len = (uint64_t)info->block_size * ext4_sb_get_blocks_cnt(sb); + info->dsc_size = to_le16(sb->desc_size); + memcpy(info->uuid, sb->uuid, UUID_SIZE); + + return EOK; +} + +static uint32_t compute_blocks_per_group(struct ext4_mkfs_info *info) +{ + return info->block_size * 8; +} + +static uint32_t compute_inodes(struct ext4_mkfs_info *info) +{ + return (uint32_t)EXT4_DIV_ROUND_UP(info->len, info->block_size) / 4; +} + +static uint32_t compute_inodes_per_group(struct ext4_mkfs_info *info) +{ + uint32_t blocks = (uint32_t)EXT4_DIV_ROUND_UP(info->len, info->block_size); + uint32_t block_groups = EXT4_DIV_ROUND_UP(blocks, info->blocks_per_group); + uint32_t inodes = EXT4_DIV_ROUND_UP(info->inodes, block_groups); + inodes = EXT4_ALIGN(inodes, (info->block_size / info->inode_size)); + + /* After properly rounding up the number of inodes/group, + * make sure to update the total inodes field in the info struct. + */ + info->inodes = inodes * block_groups; + + return inodes; +} + + +static uint32_t compute_journal_blocks(struct ext4_mkfs_info *info) +{ + uint32_t journal_blocks = (uint32_t)EXT4_DIV_ROUND_UP(info->len, + info->block_size) / 64; + if (journal_blocks < 1024) + journal_blocks = 1024; + if (journal_blocks > 32768) + journal_blocks = 32768; + return journal_blocks; +} + +static bool has_superblock(struct ext4_mkfs_info *info, uint32_t bgid) +{ + if (!(info->feat_ro_compat & EXT4_FRO_COM_SPARSE_SUPER)) + return true; + + return ext4_sb_sparse(bgid); +} + +static int create_fs_aux_info(struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + aux_info->first_data_block = (info->block_size > 1024) ? 0 : 1; + aux_info->len_blocks = info->len / info->block_size; + aux_info->inode_table_blocks = EXT4_DIV_ROUND_UP(info->inodes_per_group * + info->inode_size, info->block_size); + aux_info->groups = (uint32_t)EXT4_DIV_ROUND_UP(aux_info->len_blocks - + aux_info->first_data_block, info->blocks_per_group); + aux_info->blocks_per_ind = info->block_size / sizeof(uint32_t); + aux_info->blocks_per_dind = + aux_info->blocks_per_ind * aux_info->blocks_per_ind; + aux_info->blocks_per_tind = + aux_info->blocks_per_dind * aux_info->blocks_per_dind; + + aux_info->bg_desc_blocks = + EXT4_DIV_ROUND_UP(aux_info->groups * info->dsc_size, + info->block_size); + + aux_info->default_i_flags = EXT4_INODE_FLAG_NOATIME; + + uint32_t last_group_size = aux_info->len_blocks % info->blocks_per_group; + uint32_t last_header_size = 2 + aux_info->inode_table_blocks; + if (has_superblock(info, aux_info->groups - 1)) + last_header_size += 1 + aux_info->bg_desc_blocks + + info->bg_desc_reserve_blocks; + + if (last_group_size > 0 && last_group_size < last_header_size) { + aux_info->groups--; + aux_info->len_blocks -= last_group_size; + } + + aux_info->sb = ext4_calloc(1, EXT4_SUPERBLOCK_SIZE); + if (!aux_info->sb) + return ENOMEM; + + aux_info->bg_desc_blk = ext4_calloc(1, info->block_size); + if (!aux_info->bg_desc_blk) + return ENOMEM; + + aux_info->xattrs = NULL; + + + ext4_dbg(DEBUG_MKFS, DBG_INFO "create_fs_aux_info\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "first_data_block: %"PRIu32"\n", + aux_info->first_data_block); + ext4_dbg(DEBUG_MKFS, DBG_NONE "len_blocks: %"PRIu64"\n", + aux_info->len_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "inode_table_blocks: %"PRIu32"\n", + aux_info->inode_table_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "groups: %"PRIu32"\n", + aux_info->groups); + ext4_dbg(DEBUG_MKFS, DBG_NONE "bg_desc_blocks: %"PRIu32"\n", + aux_info->bg_desc_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "default_i_flags: %"PRIu32"\n", + aux_info->default_i_flags); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_ind: %"PRIu32"\n", + aux_info->blocks_per_ind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_dind: %"PRIu32"\n", + aux_info->blocks_per_dind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_tind: %"PRIu32"\n", + aux_info->blocks_per_tind); + + return EOK; +} + +static void release_fs_aux_info(struct fs_aux_info *aux_info) +{ + if (aux_info->sb) + ext4_free(aux_info->sb); + if (aux_info->bg_desc_blk) + ext4_free(aux_info->bg_desc_blk); +} + + +/* Fill in the superblock memory buffer based on the filesystem parameters */ +static void fill_sb(struct fs_aux_info *aux_info, struct ext4_mkfs_info *info) +{ + struct ext4_sblock *sb = aux_info->sb; + + sb->inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + ext4_sb_set_blocks_cnt(sb, aux_info->len_blocks); + ext4_sb_set_free_blocks_cnt(sb, aux_info->len_blocks); + sb->free_inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + sb->reserved_blocks_count_lo = to_le32(0); + sb->first_data_block = to_le32(aux_info->first_data_block); + sb->log_block_size = to_le32(log_2(info->block_size / 1024)); + sb->log_cluster_size = to_le32(log_2(info->block_size / 1024)); + sb->blocks_per_group = to_le32(info->blocks_per_group); + sb->frags_per_group = to_le32(info->blocks_per_group); + sb->inodes_per_group = to_le32(info->inodes_per_group); + sb->mount_time = to_le32(0); + sb->write_time = to_le32(0); + sb->mount_count = to_le16(0); + sb->max_mount_count = to_le16(0xFFFF); + sb->magic = to_le16(EXT4_SUPERBLOCK_MAGIC); + sb->state = to_le16(EXT4_SUPERBLOCK_STATE_VALID_FS); + sb->errors = to_le16(EXT4_SUPERBLOCK_ERRORS_RO); + sb->minor_rev_level = to_le16(0); + sb->last_check_time = to_le32(0); + sb->check_interval = to_le32(0); + sb->creator_os = to_le32(EXT4_SUPERBLOCK_OS_LINUX); + sb->rev_level = to_le32(1); + sb->def_resuid = to_le16(0); + sb->def_resgid = to_le16(0); + + sb->first_inode = to_le32(EXT4_GOOD_OLD_FIRST_INO); + sb->inode_size = to_le16(info->inode_size); + sb->block_group_index = to_le16(0); + + sb->features_compatible = to_le32(info->feat_compat); + sb->features_incompatible = to_le32(info->feat_incompat); + sb->features_read_only = to_le32(info->feat_ro_compat); + + memcpy(sb->uuid, info->uuid, UUID_SIZE); + + memset(sb->volume_name, 0, sizeof(sb->volume_name)); + strncpy(sb->volume_name, info->label, sizeof(sb->volume_name)); + memset(sb->last_mounted, 0, sizeof(sb->last_mounted)); + + sb->algorithm_usage_bitmap = to_le32(0); + sb->s_prealloc_blocks = 0; + sb->s_prealloc_dir_blocks = 0; + sb->s_reserved_gdt_blocks = to_le16(info->bg_desc_reserve_blocks); + + if (info->feat_compat & EXT4_FCOM_HAS_JOURNAL) + sb->journal_inode_number = to_le32(EXT4_JOURNAL_INO); + + sb->journal_backup_type = 1; + sb->journal_dev = to_le32(0); + sb->last_orphan = to_le32(0); + sb->hash_seed[0] = to_le32(0x11111111); + sb->hash_seed[1] = to_le32(0x22222222); + sb->hash_seed[2] = to_le32(0x33333333); + sb->hash_seed[3] = to_le32(0x44444444); + sb->default_hash_version = EXT2_HTREE_HALF_MD4; + sb->checksum_type = 1; + sb->desc_size = to_le16(info->dsc_size); + sb->default_mount_opts = to_le32(0); + sb->first_meta_bg = to_le32(0); + sb->mkfs_time = to_le32(0); + + sb->reserved_blocks_count_hi = to_le32(0); + sb->min_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->want_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->flags = to_le32(EXT4_SUPERBLOCK_FLAGS_SIGNED_HASH); +} + + +static int write_bgroup_block(struct ext4_blockdev *bd, + struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info, + uint32_t blk) +{ + int r = EOK; + uint32_t j; + struct ext4_block b; + + uint32_t block_size = ext4_sb_get_block_size(aux_info->sb); + + for (j = 0; j < aux_info->groups; j++) { + uint64_t bg_start_block = aux_info->first_data_block + + j * info->blocks_per_group; + uint32_t blk_off = 0; + + blk_off += aux_info->bg_desc_blocks; + if (has_superblock(info, j)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + } + + uint64_t dsc_blk = bg_start_block + blk; + + r = ext4_block_get_noread(bd, &b, dsc_blk); + if (r != EOK) + return r; + + memcpy(b.data, aux_info->bg_desc_blk, block_size); + + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + } + + return r; +} + +static int write_bgroups(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + int r = EOK; + + struct ext4_block b; + struct ext4_bgroup *bg_desc; + + uint32_t i; + uint32_t bg_free_blk = 0; + uint64_t sb_free_blk = 0; + uint32_t block_size = ext4_sb_get_block_size(aux_info->sb); + uint32_t dsc_size = ext4_sb_get_desc_size(aux_info->sb); + uint32_t dsc_per_block = block_size / dsc_size; + uint32_t k = 0; + + for (i = 0; i < aux_info->groups; i++) { + uint64_t bg_start_block = aux_info->first_data_block + + aux_info->first_data_block + i * info->blocks_per_group; + uint32_t blk_off = 0; + + bg_desc = (void *)(aux_info->bg_desc_blk + k * dsc_size); + bg_free_blk = info->blocks_per_group - + aux_info->inode_table_blocks; + + bg_free_blk -= 2; + blk_off += aux_info->bg_desc_blocks; + + if (i == (aux_info->groups - 1)) + bg_free_blk -= aux_info->first_data_block; + + if (has_superblock(info, i)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + bg_free_blk -= info->bg_desc_reserve_blocks + 1; + bg_free_blk -= aux_info->bg_desc_blocks; + } + + ext4_bg_set_block_bitmap(bg_desc, aux_info->sb, + bg_start_block + blk_off + 1); + + ext4_bg_set_inode_bitmap(bg_desc, aux_info->sb, + bg_start_block + blk_off + 2); + + ext4_bg_set_inode_table_first_block(bg_desc, + aux_info->sb, + bg_start_block + blk_off + 3); + + ext4_bg_set_free_blocks_count(bg_desc, aux_info->sb, + bg_free_blk); + + ext4_bg_set_free_inodes_count(bg_desc, + aux_info->sb, to_le32(aux_info->sb->inodes_per_group)); + + ext4_bg_set_used_dirs_count(bg_desc, aux_info->sb, 0); + + ext4_bg_set_flag(bg_desc, + EXT4_BLOCK_GROUP_BLOCK_UNINIT | + EXT4_BLOCK_GROUP_INODE_UNINIT); + + sb_free_blk += bg_free_blk; + + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 1); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 2); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + + if (++k != dsc_per_block) + continue; + + k = 0; + r = write_bgroup_block(bd, aux_info, info, i / dsc_per_block); + if (r != EOK) + return r; + + } + + r = write_bgroup_block(bd, aux_info, info, i / dsc_per_block); + if (r != EOK) + return r; + + ext4_sb_set_free_blocks_cnt(aux_info->sb, sb_free_blk); + return r; +} + +static int write_sblocks(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + uint64_t offset; + uint32_t i; + int r; + + /* write out the backup superblocks */ + for (i = 1; i < aux_info->groups; i++) { + if (has_superblock(info, i)) { + offset = info->block_size * (aux_info->first_data_block + + i * info->blocks_per_group); + + aux_info->sb->block_group_index = to_le16(i); + r = ext4_block_writebytes(bd, offset, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); + if (r != EOK) + return r; + } + } + + /* write out the primary superblock */ + aux_info->sb->block_group_index = to_le16(0); + return ext4_block_writebytes(bd, 1024, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); +} + + +int ext4_mkfs_read_info(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct ext4_sblock *sb = NULL; + r = ext4_block_init(bd); + if (r != EOK) + return r; + + sb = ext4_malloc(EXT4_SUPERBLOCK_SIZE); + if (!sb) + goto Finish; + + + r = ext4_sb_read(bd, sb); + if (r != EOK) + goto Finish; + + r = sb2info(sb, info); + +Finish: + if (sb) + ext4_free(sb); + ext4_block_fini(bd); + return r; +} + +static int mkfs_init(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct fs_aux_info aux_info; + memset(&aux_info, 0, sizeof(struct fs_aux_info)); + + r = create_fs_aux_info(&aux_info, info); + if (r != EOK) + goto Finish; + + fill_sb(&aux_info, info); + + r = write_bgroups(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + r = write_sblocks(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + Finish: + release_fs_aux_info(&aux_info); + return r; +} + +static int init_bgs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_block_group_ref ref; + uint32_t i; + uint32_t bg_count = ext4_block_group_cnt(&fs->sb); + for (i = 0; i < bg_count; ++i) { + r = ext4_fs_get_block_group_ref(fs, i, &ref); + if (r != EOK) + break; + + r = ext4_fs_put_block_group_ref(&ref); + if (r != EOK) + break; + } + return r; +} + +static int alloc_inodes(struct ext4_fs *fs) +{ + int r = EOK; + int i; + struct ext4_inode_ref inode_ref; + for (i = 1; i < 12; ++i) { + int filetype = EXT4_DE_REG_FILE; + + switch (i) { + case EXT4_ROOT_INO: + case EXT4_GOOD_OLD_FIRST_INO: + filetype = EXT4_DE_DIR; + break; + default: + break; + } + + r = ext4_fs_alloc_inode(fs, &inode_ref, filetype); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, inode_ref.inode, 0); + + switch (i) { + case EXT4_ROOT_INO: + case EXT4_JOURNAL_INO: + ext4_fs_inode_blocks_init(fs, &inode_ref); + break; + } + + ext4_fs_put_inode_ref(&inode_ref); + } + + return r; +} + +static int create_dirs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_inode_ref root; + struct ext4_inode_ref child; + + r = ext4_fs_get_inode_ref(fs, EXT4_ROOT_INO, &root); + if (r != EOK) + return r; + + r = ext4_fs_get_inode_ref(fs, EXT4_GOOD_OLD_FIRST_INO, &child); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, child.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + + ext4_inode_set_mode(&fs->sb, root.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&fs->sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(&root, &root); + if (r != EOK) + return r; + + r = ext4_dir_dx_init(&child, &root); + if (r != EOK) + return r; + + ext4_inode_set_flag(root.inode, EXT4_INODE_FLAG_INDEX); + ext4_inode_set_flag(child.inode, EXT4_INODE_FLAG_INDEX); + } else +#endif + { + r = ext4_dir_add_entry(&root, ".", strlen("."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&root, "..", strlen(".."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, ".", strlen("."), &child); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, "..", strlen(".."), &root); + if (r != EOK) + return r; + } + + r = ext4_dir_add_entry(&root, "lost+found", strlen("lost+found"), &child); + if (r != EOK) + return r; + + ext4_inode_set_links_cnt(root.inode, 3); + ext4_inode_set_links_cnt(child.inode, 2); + + child.dirty = true; + root.dirty = true; + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&root); + return r; +} + +static int create_journal_inode(struct ext4_fs *fs, + struct ext4_mkfs_info *info) +{ + int ret; + struct ext4_inode_ref inode_ref; + uint64_t blocks_count; + + if (!info->journal) + return EOK; + + ret = ext4_fs_get_inode_ref(fs, EXT4_JOURNAL_INO, &inode_ref); + if (ret != EOK) + return ret; + + struct ext4_inode *inode = inode_ref.inode; + + ext4_inode_set_mode(&fs->sb, inode, EXT4_INODE_MODE_FILE | 0600); + ext4_inode_set_links_cnt(inode, 1); + + blocks_count = ext4_inode_get_blocks_count(&fs->sb, inode); + + while (blocks_count++ < info->journal_blocks) + { + ext4_fsblk_t fblock; + ext4_lblk_t iblock; + struct ext4_block blk; + + ret = ext4_fs_append_inode_dblk(&inode_ref, &fblock, &iblock); + if (ret != EOK) + goto Finish; + + if (iblock != 0) + continue; + + ret = ext4_block_get(fs->bdev, &blk, fblock); + if (ret != EOK) + goto Finish; + + + struct jbd_sb * jbd_sb = (struct jbd_sb * )blk.data; + memset(jbd_sb, 0, sizeof(struct jbd_sb)); + + jbd_sb->header.magic = to_be32(JBD_MAGIC_NUMBER); + jbd_sb->header.blocktype = to_be32(JBD_SUPERBLOCK_V2); + jbd_sb->blocksize = to_be32(info->block_size); + jbd_sb->maxlen = to_be32(info->journal_blocks); + jbd_sb->nr_users = to_be32(1); + jbd_sb->first = to_be32(1); + jbd_sb->sequence = to_be32(1); + + ext4_bcache_set_dirty(blk.buf); + ret = ext4_block_set(fs->bdev, &blk); + if (ret != EOK) + goto Finish; + } + + memcpy(fs->sb.journal_blocks, inode->blocks, sizeof(inode->blocks)); + + Finish: + ext4_fs_put_inode_ref(&inode_ref); + + return ret; +} + +int ext4_mkfs(struct ext4_fs *fs, struct ext4_blockdev *bd, + struct ext4_mkfs_info *info, int fs_type) +{ + int r; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + bd->fs = fs; + + if (info->len == 0) + info->len = bd->part_size; + + if (info->block_size == 0) + info->block_size = 4096; /*Set block size to default value*/ + + /* Round down the filesystem length to be a multiple of the block size */ + info->len &= ~((uint64_t)info->block_size - 1); + + if (info->journal_blocks == 0) + info->journal_blocks = compute_journal_blocks(info); + + if (info->blocks_per_group == 0) + info->blocks_per_group = compute_blocks_per_group(info); + + if (info->inodes == 0) + info->inodes = compute_inodes(info); + + if (info->inode_size == 0) + info->inode_size = 256; + + if (info->label == NULL) + info->label = ""; + + info->inodes_per_group = compute_inodes_per_group(info); + + switch (fs_type) { + case F_SET_EXT2: + info->feat_compat = EXT2_SUPPORTED_FCOM; + info->feat_ro_compat = EXT2_SUPPORTED_FRO_COM; + info->feat_incompat = EXT2_SUPPORTED_FINCOM; + break; + case F_SET_EXT3: + info->feat_compat = EXT3_SUPPORTED_FCOM; + info->feat_ro_compat = EXT3_SUPPORTED_FRO_COM; + info->feat_incompat = EXT3_SUPPORTED_FINCOM; + break; + case F_SET_EXT4: + info->feat_compat = EXT4_SUPPORTED_FCOM; + info->feat_ro_compat = EXT4_SUPPORTED_FRO_COM; + info->feat_incompat = EXT4_SUPPORTED_FINCOM; + break; + } + + /*TODO: handle this features some day...*/ + info->feat_incompat &= ~EXT4_FINCOM_META_BG; + info->feat_incompat &= ~EXT4_FINCOM_FLEX_BG; + info->feat_incompat &= ~EXT4_FINCOM_64BIT; + + info->feat_ro_compat &= ~EXT4_FRO_COM_METADATA_CSUM; + info->feat_ro_compat &= ~EXT4_FRO_COM_GDT_CSUM; + info->feat_ro_compat &= ~EXT4_FRO_COM_DIR_NLINK; + info->feat_ro_compat &= ~EXT4_FRO_COM_EXTRA_ISIZE; + info->feat_ro_compat &= ~EXT4_FRO_COM_HUGE_FILE; + + if (info->journal) + info->feat_compat |= EXT4_FCOM_HAS_JOURNAL; + + if (info->dsc_size == 0) { + + if (info->feat_incompat & EXT4_FINCOM_64BIT) + info->dsc_size = EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE; + else + info->dsc_size = EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE; + } + + info->bg_desc_reserve_blocks = 0; + + ext4_dbg(DEBUG_MKFS, DBG_INFO "Creating filesystem with parameters:\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Size: %"PRIu64"\n", info->len); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Block size: %"PRIu32"\n", + info->block_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Blocks per group: %"PRIu32"\n", + info->blocks_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes per group: %"PRIu32"\n", + info->inodes_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inode size: %"PRIu32"\n", + info->inode_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes: %"PRIu32"\n", info->inodes); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Journal blocks: %"PRIu32"\n", + info->journal_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features ro_compat: 0x%x\n", + info->feat_ro_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features compat: 0x%x\n", + info->feat_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features incompat: 0x%x\n", + info->feat_incompat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "BG desc reserve: %"PRIu32"\n", + info->bg_desc_reserve_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Descriptor size: %"PRIu16"\n", + info->dsc_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "journal: %s\n", + info->journal ? "yes" : "no"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Label: %s\n", info->label); + + struct ext4_bcache bc; + + memset(&bc, 0, sizeof(struct ext4_bcache)); + ext4_block_set_lb_size(bd, info->block_size); + + r = ext4_bcache_init_dynamic(&bc, CONFIG_BLOCK_DEV_CACHE_SIZE, + info->block_size); + if (r != EOK) + goto block_fini; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, &bc); + if (r != EOK) + goto cache_fini; + + r = ext4_block_cache_write_back(bd, 1); + if (r != EOK) + goto cache_fini; + + r = mkfs_init(bd, info); + if (r != EOK) + goto cache_fini; + + r = ext4_fs_init(fs, bd, false); + if (r != EOK) + goto cache_fini; + + r = init_bgs(fs); + if (r != EOK) + goto fs_fini; + + r = alloc_inodes(fs); + if (r != EOK) + goto fs_fini; + + r = create_dirs(fs); + if (r != EOK) + goto fs_fini; + + r = create_journal_inode(fs, info); + if (r != EOK) + goto fs_fini; + + fs_fini: + ext4_fs_fini(fs); + + cache_fini: + ext4_block_cache_write_back(bd, 0); + ext4_bcache_fini_dynamic(&bc); + + block_fini: + ext4_block_fini(bd); + + return r; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_super.c b/lib/lwext4_rust/c/lwext4/src/ext4_super.c new file mode 100644 index 0000000..092c38b --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_super.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_super.h + * @brief Superblock operations. + */ + +#include +#include +#include +#include +#include + +#include +#include + +uint32_t ext4_block_group_cnt(struct ext4_sblock *s) +{ + uint64_t blocks_count = ext4_sb_get_blocks_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + + uint32_t block_groups_count = (uint32_t)(blocks_count / blocks_per_group); + + if (blocks_count % blocks_per_group) + block_groups_count++; + + return block_groups_count; +} + +uint32_t ext4_blocks_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + uint64_t total_blocks = ext4_sb_get_blocks_cnt(s); + + if (bgid < block_group_count - 1) + return blocks_per_group; + + return (uint32_t)(total_blocks - ((block_group_count - 1) * blocks_per_group)); +} + +uint32_t ext4_inodes_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t inodes_per_group = ext4_get32(s, inodes_per_group); + uint32_t total_inodes = ext4_get32(s, inodes_count); + + if (bgid < block_group_count - 1) + return inodes_per_group; + + return (total_inodes - ((block_group_count - 1) * inodes_per_group)); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_sb_csum(struct ext4_sblock *s) +{ + + return ext4_crc32c(EXT4_CRC32_INIT, s, + offsetof(struct ext4_sblock, checksum)); +} +#else +#define ext4_sb_csum(...) 0 +#endif + +static bool ext4_sb_verify_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (s->checksum_type != to_le32(EXT4_CHECKSUM_CRC32C)) + return false; + + return s->checksum == to_le32(ext4_sb_csum(s)); +} + +static void ext4_sb_set_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return; + + s->checksum = to_le32(ext4_sb_csum(s)); +} + +int ext4_sb_write(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + ext4_sb_set_csum(s); + return ext4_block_writebytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +int ext4_sb_read(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + return ext4_block_readbytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +bool ext4_sb_check(struct ext4_sblock *s) +{ + if (ext4_get16(s, magic) != EXT4_SUPERBLOCK_MAGIC) + return false; + + if (ext4_get32(s, inodes_count) == 0) + return false; + + if (ext4_sb_get_blocks_cnt(s) == 0) + return false; + + if (ext4_get32(s, blocks_per_group) == 0) + return false; + + if (ext4_get32(s, inodes_per_group) == 0) + return false; + + if (ext4_get16(s, inode_size) < 128) + return false; + + if (ext4_get32(s, first_inode) < 11) + return false; + + if (ext4_sb_get_desc_size(s) < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (ext4_sb_get_desc_size(s) > EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (!ext4_sb_verify_csum(s)) + return false; + + return true; +} + +static inline int is_power_of(uint32_t a, uint32_t b) +{ + while (1) { + if (a < b) + return 0; + if (a == b) + return 1; + if ((a % b) != 0) + return 0; + a = a / b; + } +} + +bool ext4_sb_sparse(uint32_t group) +{ + if (group <= 1) + return 1; + + if (!(group & 1)) + return 0; + + return (is_power_of(group, 7) || is_power_of(group, 5) || + is_power_of(group, 3)); +} + +bool ext4_sb_is_super_in_bg(struct ext4_sblock *s, uint32_t group) +{ + if (ext4_sb_feature_ro_com(s, EXT4_FRO_COM_SPARSE_SUPER) && + !ext4_sb_sparse(group)) + return false; + return true; +} + +static uint32_t ext4_bg_num_gdb_meta(struct ext4_sblock *s, uint32_t group) +{ + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + uint32_t metagroup = group / dsc_per_block; + uint32_t first = metagroup * dsc_per_block; + uint32_t last = first + dsc_per_block - 1; + + if (group == first || group == first + 1 || group == last) + return 1; + return 0; +} + +static uint32_t ext4_bg_num_gdb_nometa(struct ext4_sblock *s, uint32_t group) +{ + if (!ext4_sb_is_super_in_bg(s, group)) + return 0; + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + uint32_t db_count = + (ext4_block_group_cnt(s) + dsc_per_block - 1) / dsc_per_block; + + if (ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG)) + return ext4_sb_first_meta_bg(s); + + return db_count; +} + +uint32_t ext4_bg_num_gdb(struct ext4_sblock *s, uint32_t group) +{ + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + uint32_t first_meta_bg = ext4_sb_first_meta_bg(s); + uint32_t metagroup = group / dsc_per_block; + + if (!ext4_sb_feature_incom(s,EXT4_FINCOM_META_BG) || + metagroup < first_meta_bg) + return ext4_bg_num_gdb_nometa(s, group); + + return ext4_bg_num_gdb_meta(s, group); +} + +uint32_t ext4_num_base_meta_clusters(struct ext4_sblock *s, + uint32_t block_group) +{ + uint32_t num; + uint32_t dsc_per_block = + ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); + + num = ext4_sb_is_super_in_bg(s, block_group); + + if (!ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG) || + block_group < ext4_sb_first_meta_bg(s) * dsc_per_block) { + if (num) { + num += ext4_bg_num_gdb(s, block_group); + num += ext4_get16(s, s_reserved_gdt_blocks); + } + } else { + num += ext4_bg_num_gdb(s, block_group); + } + + uint32_t clustersize = 1024 << ext4_get32(s, log_cluster_size); + uint32_t cluster_ratio = clustersize / ext4_sb_get_block_size(s); + uint32_t v = + (num + cluster_ratio - 1) >> ext4_get32(s, log_cluster_size); + + return v; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_trans.c b/lib/lwext4_rust/c/lwext4/src/ext4_trans.c new file mode 100644 index 0000000..f228751 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_trans.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_trans.c + * @brief Ext4 transaction buffer operations. + */ + +#include +#include +#include +#include +#include + +#include +#include + +int ext4_trans_set_block_dirty(struct ext4_buf *buf) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = buf->bc->bdev->fs; + struct ext4_block block = { + .lb_id = buf->lba, + .data = buf->data, + .buf = buf + }; + + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + return jbd_trans_set_block_dirty(trans, &block); + } +#endif + ext4_bcache_set_dirty(buf); + return r; +} + +int ext4_trans_block_get_noread(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + return r; +} + +int ext4_trans_block_get(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get(bdev, b, lba); + if (r != EOK) + return r; + + return r; +} + +int ext4_trans_try_revoke_block(struct ext4_blockdev *bdev __unused, + uint64_t lba __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = bdev->fs; + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + r = jbd_trans_try_revoke_block(trans, lba); + } else if (fs->jbd_journal) { + r = ext4_block_flush_lba(fs->bdev, lba); + } +#endif + return r; +} + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ext4_xattr.c b/lib/lwext4_rust/c/lwext4/src/ext4_xattr.c new file mode 100644 index 0000000..f8a5778 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ext4_xattr.c @@ -0,0 +1,1564 @@ +/* + * Copyright (c) 2017 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2017 Kaho Ng (ngkaho1234@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.c + * @brief Extended Attribute manipulation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if CONFIG_XATTR_ENABLE + +/** + * @file ext4_xattr.c + * @brief Extended Attribute Manipulation + */ + +/* Extended Attribute(EA) */ + +/* Magic value in attribute blocks */ +#define EXT4_XATTR_MAGIC 0xEA020000 + +/* Maximum number of references to one attribute block */ +#define EXT4_XATTR_REFCOUNT_MAX 1024 + +/* Name indexes */ +#define EXT4_XATTR_INDEX_USER 1 +#define EXT4_XATTR_INDEX_POSIX_ACL_ACCESS 2 +#define EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT 3 +#define EXT4_XATTR_INDEX_TRUSTED 4 +#define EXT4_XATTR_INDEX_LUSTRE 5 +#define EXT4_XATTR_INDEX_SECURITY 6 +#define EXT4_XATTR_INDEX_SYSTEM 7 +#define EXT4_XATTR_INDEX_RICHACL 8 +#define EXT4_XATTR_INDEX_ENCRYPTION 9 + +#define EXT4_XATTR_PAD_BITS 2 +#define EXT4_XATTR_PAD (1 << EXT4_XATTR_PAD_BITS) +#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD - 1) +#define EXT4_XATTR_LEN(name_len) \ + (((name_len) + EXT4_XATTR_ROUND + sizeof(struct ext4_xattr_entry)) & \ + ~EXT4_XATTR_ROUND) +#define EXT4_XATTR_NEXT(entry) \ + ((struct ext4_xattr_entry *)((char *)(entry) + \ + EXT4_XATTR_LEN((entry)->e_name_len))) +#define EXT4_XATTR_SIZE(size) (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND) +#define EXT4_XATTR_NAME(entry) ((char *)((entry) + 1)) + +#define EXT4_XATTR_IHDR(sb, raw_inode) \ + ((struct ext4_xattr_ibody_header *)((char *)raw_inode + \ + EXT4_GOOD_OLD_INODE_SIZE + \ + ext4_inode_get_extra_isize( \ + sb, raw_inode))) +#define EXT4_XATTR_IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr) + 1)) + +#define EXT4_XATTR_BHDR(block) ((struct ext4_xattr_header *)((block)->data)) +#define EXT4_XATTR_ENTRY(ptr) ((struct ext4_xattr_entry *)(ptr)) +#define EXT4_XATTR_BFIRST(block) EXT4_XATTR_ENTRY(EXT4_XATTR_BHDR(block) + 1) +#define EXT4_XATTR_IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0) + +#define EXT4_ZERO_XATTR_VALUE ((void *)-1) + +#pragma pack(push, 1) + +struct ext4_xattr_header { + uint32_t h_magic; /* magic number for identification */ + uint32_t h_refcount; /* reference count */ + uint32_t h_blocks; /* number of disk blocks used */ + uint32_t h_hash; /* hash value of all attributes */ + uint32_t h_checksum; /* crc32c(uuid+id+xattrblock) */ + /* id = inum if refcount=1, blknum otherwise */ + uint32_t h_reserved[3]; /* zero right now */ +}; + +struct ext4_xattr_ibody_header { + uint32_t h_magic; /* magic number for identification */ +}; + +struct ext4_xattr_entry { + uint8_t e_name_len; /* length of name */ + uint8_t e_name_index; /* attribute name index */ + uint16_t e_value_offs; /* offset in disk block of value */ + uint32_t e_value_block; /* disk block attribute is stored on (n/i) */ + uint32_t e_value_size; /* size of attribute value */ + uint32_t e_hash; /* hash value of name and value */ +}; + +#pragma pack(pop) + + +#define NAME_HASH_SHIFT 5 +#define VALUE_HASH_SHIFT 16 + +static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + uint32_t hash = 0; + char *name = EXT4_XATTR_NAME(entry); + int n; + + for (n = 0; n < entry->e_name_len; n++) { + hash = (hash << NAME_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - NAME_HASH_SHIFT)) ^ *name++; + } + + if (entry->e_value_block == 0 && entry->e_value_size != 0) { + uint32_t *value = + (uint32_t *)((char *)header + to_le16(entry->e_value_offs)); + for (n = (to_le32(entry->e_value_size) + EXT4_XATTR_ROUND) >> + EXT4_XATTR_PAD_BITS; + n; n--) { + hash = (hash << VALUE_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - VALUE_HASH_SHIFT)) ^ + to_le32(*value++); + } + } + entry->e_hash = to_le32(hash); +} + +#define BLOCK_HASH_SHIFT 16 + +/* + * ext4_xattr_rehash() + * + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext4_xattr_rehash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + struct ext4_xattr_entry *here; + uint32_t hash = 0; + + ext4_xattr_compute_hash(header, entry); + here = EXT4_XATTR_ENTRY(header + 1); + while (!EXT4_XATTR_IS_LAST_ENTRY(here)) { + if (!here->e_hash) { + /* Block is not shared if an entry's hash value == 0 */ + hash = 0; + break; + } + hash = (hash << BLOCK_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - BLOCK_HASH_SHIFT)) ^ + to_le32(here->e_hash); + here = EXT4_XATTR_NEXT(here); + } + header->h_hash = to_le32(hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr, + struct ext4_xattr_header *header) +{ + uint32_t checksum = 0; + uint64_t le64_blocknr = blocknr; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = header->h_checksum; + header->h_checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + checksum = + ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum block number */ + checksum = + ext4_crc32c(checksum, &le64_blocknr, sizeof(le64_blocknr)); + /* Finally calculate crc32 checksum against + * the entire xattr block */ + checksum = + ext4_crc32c(checksum, header, ext4_sb_get_block_size(sb)); + header->h_checksum = orig_checksum; + } + return checksum; +} +#else +#define ext4_xattr_block_checksum(...) 0 +#endif + +static void ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr __unused, + struct ext4_xattr_header *header) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + header->h_checksum = + ext4_xattr_block_checksum(inode_ref, blocknr, header); +} + +struct xattr_prefix { + const char *prefix; + uint8_t name_index; +}; + +static const struct xattr_prefix prefix_tbl[] = { + {"user.", EXT4_XATTR_INDEX_USER}, + {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS}, + {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT}, + {"trusted.", EXT4_XATTR_INDEX_TRUSTED}, + {"security.", EXT4_XATTR_INDEX_SECURITY}, + {"system.", EXT4_XATTR_INDEX_SYSTEM}, + {"system.richacl", EXT4_XATTR_INDEX_RICHACL}, + {NULL, 0}, +}; + +const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len, + bool *found) +{ + int i; + ext4_assert(name_index); + ext4_assert(found); + + *found = false; + + if (!full_name_len) { + if (name_len) + *name_len = 0; + + return NULL; + } + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (full_name_len >= prefix_len && + !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) { + bool require_name = + prefix_tbl[i].prefix[prefix_len - 1] == '.'; + *name_index = prefix_tbl[i].name_index; + if (name_len) + *name_len = full_name_len - prefix_len; + + if (!(full_name_len - prefix_len) && require_name) + return NULL; + + *found = true; + if (require_name) + return full_name + prefix_len; + + return NULL; + } + } + if (name_len) + *name_len = 0; + + return NULL; +} + +const char *ext4_get_xattr_name_prefix(uint8_t name_index, + size_t *ret_prefix_len) +{ + int i; + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (prefix_tbl[i].name_index == name_index) { + if (ret_prefix_len) + *ret_prefix_len = prefix_len; + + return prefix_tbl[i].prefix; + } + } + if (ret_prefix_len) + *ret_prefix_len = 0; + + return NULL; +} + +static const char ext4_xattr_empty_value; + +/** + * @brief Insert/Remove/Modify the given entry + * + * @param i The information of the given EA entry + * @param s Search context block + * @param dry_run Do not modify the content of the buffer + * + * @return Return EOK when finished, ENOSPC when there is no enough space + */ +static int ext4_xattr_set_entry(struct ext4_xattr_info *i, + struct ext4_xattr_search *s, bool dry_run) +{ + struct ext4_xattr_entry *last; + size_t free, min_offs = (char *)s->end - (char *)s->base, + name_len = i->name_len; + + /* + * If the entry is going to be removed but not found, return 0 to + * indicate success. + */ + if (!i->value && s->not_found) + return EOK; + + /* Compute min_offs and last. */ + last = s->first; + for (; !EXT4_XATTR_IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) { + if (last->e_value_size) { + size_t offs = to_le16(last->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + + /* Calculate free space in the block. */ + free = min_offs - ((char *)last - (char *)s->base) - sizeof(uint32_t); + if (!s->not_found) + free += EXT4_XATTR_SIZE(s->here->e_value_size) + + EXT4_XATTR_LEN(s->here->e_name_len); + + if (i->value) { + /* See whether there is enough space to hold new entry */ + if (free < + EXT4_XATTR_SIZE(i->value_len) + EXT4_XATTR_LEN(name_len)) + return ENOSPC; + } + + /* Return EOK now if we do not intend to modify the content. */ + if (dry_run) + return EOK; + + /* First remove the old entry's data part */ + if (!s->not_found) { + size_t value_offs = to_le16(s->here->e_value_offs); + void *value = (char *)s->base + value_offs; + void *first_value = (char *)s->base + min_offs; + size_t value_size = + EXT4_XATTR_SIZE(to_le32(s->here->e_value_size)); + + if (value_offs) { + /* Remove the data part. */ + memmove((char *)first_value + value_size, first_value, + (char *)value - (char *)first_value); + + /* Zero the gap created */ + memset(first_value, 0, value_size); + + /* + * Calculate the new min_offs after removal of the old + * entry's data part + */ + min_offs += value_size; + } + + /* + * Adjust the value offset of entries which has value offset + * prior to the s->here. The offset of these entries won't be + * shifted if the size of the entry we removed is zero. + */ + for (last = s->first; !EXT4_XATTR_IS_LAST_ENTRY(last); + last = EXT4_XATTR_NEXT(last)) { + size_t offs = to_le16(last->e_value_offs); + + /* For zero-value-length entry, offs will be zero. */ + if (offs < value_offs) + last->e_value_offs = to_le16(offs + value_size); + } + } + + /* If caller wants us to insert... */ + if (i->value) { + size_t value_offs; + if (i->value_len) + value_offs = min_offs - EXT4_XATTR_SIZE(i->value_len); + else + value_offs = 0; + + if (!s->not_found) { + struct ext4_xattr_entry *here = s->here; + + /* Reuse the current entry we have got */ + here->e_value_offs = to_le16(value_offs); + here->e_value_size = to_le32(i->value_len); + } else { + /* Insert a new entry */ + last->e_name_len = (uint8_t)name_len; + last->e_name_index = i->name_index; + last->e_value_offs = to_le16(value_offs); + last->e_value_block = 0; + last->e_value_size = to_le32(i->value_len); + memcpy(EXT4_XATTR_NAME(last), i->name, name_len); + + /* Set valid last entry indicator */ + *(uint32_t *)EXT4_XATTR_NEXT(last) = 0; + + s->here = last; + } + + /* Insert the value's part */ + if (value_offs) { + memcpy((char *)s->base + value_offs, i->value, + i->value_len); + + /* Clear the padding bytes if there is */ + if (EXT4_XATTR_SIZE(i->value_len) != i->value_len) + memset((char *)s->base + value_offs + + i->value_len, + 0, EXT4_XATTR_SIZE(i->value_len) - + i->value_len); + } + } else { + size_t shift_offs; + + /* Remove the whole entry */ + shift_offs = (char *)EXT4_XATTR_NEXT(s->here) - (char *)s->here; + memmove(s->here, EXT4_XATTR_NEXT(s->here), + (char *)last + sizeof(uint32_t) - + (char *)EXT4_XATTR_NEXT(s->here)); + + /* Zero the gap created */ + memset((char *)last - shift_offs + sizeof(uint32_t), 0, + shift_offs); + + s->here = NULL; + } + + return EOK; +} + +static inline bool ext4_xattr_is_empty(struct ext4_xattr_search *s) +{ + if (!EXT4_XATTR_IS_LAST_ENTRY(s->first)) + return false; + + return true; +} + +/** + * @brief Find the entry according to given information + * + * @param i The information of the EA entry to be found, + * including name_index, name and the length of name + * @param s Search context block + */ +static void ext4_xattr_find_entry(struct ext4_xattr_info *i, + struct ext4_xattr_search *s) +{ + struct ext4_xattr_entry *entry = NULL; + + s->not_found = true; + s->here = NULL; + + /* + * Find the wanted EA entry by simply comparing the namespace, + * name and the length of name. + */ + for (entry = s->first; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + const char *name = EXT4_XATTR_NAME(entry); + if (name_len == i->name_len && + entry->e_name_index == i->name_index && + !memcmp(name, i->name, name_len)) { + s->here = entry; + s->not_found = false; + i->value_len = to_le32(entry->e_value_size); + if (i->value_len) + i->value = (char *)s->base + + to_le16(entry->e_value_offs); + else + i->value = NULL; + + return; + } + } +} + +/** + * @brief Check whether the xattr block's content is valid + * + * @param inode_ref Inode reference + * @param block The block buffer to be validated + * + * @return true if @block is valid, false otherwise. + */ +static bool ext4_xattr_is_block_valid(struct ext4_inode_ref *inode_ref, + struct ext4_block *block) +{ + + void *base = block->data, + *end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb); + size_t min_offs = (char *)end - (char *)base; + struct ext4_xattr_header *header = EXT4_XATTR_BHDR(block); + struct ext4_xattr_entry *entry = EXT4_XATTR_BFIRST(block); + + /* + * Check whether the magic number in the header is correct. + */ + if (header->h_magic != to_le32(EXT4_XATTR_MAGIC)) + return false; + + /* + * The in-kernel filesystem driver only supports 1 block currently. + */ + if (header->h_blocks != to_le32(1)) + return false; + + /* + * Check if those entries are maliciously corrupted to inflict harm + * upon us. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + if (!to_le32(entry->e_value_size) && + to_le16(entry->e_value_offs)) + return false; + + if ((char *)base + to_le16(entry->e_value_offs) + + to_le32(entry->e_value_size) > + (char *)end) + return false; + + /* + * The name length field should also be correct, + * also there should be an 4-byte zero entry at the + * end. + */ + if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > + (char *)end) + return false; + + if (to_le32(entry->e_value_size)) { + size_t offs = to_le16(entry->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + /* + * Entry field and data field do not override each other. + */ + if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) + return false; + + return true; +} + +/** + * @brief Check whether the inode buffer's content is valid + * + * @param inode_ref Inode reference + * + * @return true if the inode buffer is valid, false otherwise. + */ +static bool ext4_xattr_is_ibody_valid(struct ext4_inode_ref *inode_ref) +{ + size_t min_offs; + void *base, *end; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + struct ext4_xattr_entry *entry; + size_t inode_size = ext4_get16(&fs->sb, inode_size); + + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + entry = EXT4_XATTR_IFIRST(iheader); + base = iheader; + end = (char *)inode_ref->inode + inode_size; + min_offs = (char *)end - (char *)base; + + /* + * Check whether the magic number in the header is correct. + */ + if (iheader->h_magic != to_le32(EXT4_XATTR_MAGIC)) + return false; + + /* + * Check if those entries are maliciously corrupted to inflict harm + * upon us. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + if (!to_le32(entry->e_value_size) && + to_le16(entry->e_value_offs)) + return false; + + if ((char *)base + to_le16(entry->e_value_offs) + + to_le32(entry->e_value_size) > + (char *)end) + return false; + + /* + * The name length field should also be correct, + * also there should be an 4-byte zero entry at the + * end. + */ + if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) > + (char *)end) + return false; + + if (to_le32(entry->e_value_size)) { + size_t offs = to_le16(entry->e_value_offs); + if (offs < min_offs) + min_offs = offs; + } + } + /* + * Entry field and data field do not override each other. + */ + if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t)) + return false; + + return true; +} + +/** + * @brief An EA entry finder for inode buffer + */ +struct ext4_xattr_finder { + /** + * @brief The information of the EA entry to be find + */ + struct ext4_xattr_info i; + + /** + * @brief Search context block of the current search + */ + struct ext4_xattr_search s; + + /** + * @brief Inode reference to the corresponding inode + */ + struct ext4_inode_ref *inode_ref; +}; + +static void ext4_xattr_ibody_initialize(struct ext4_inode_ref *inode_ref) +{ + struct ext4_xattr_ibody_header *header; + struct ext4_fs *fs = inode_ref->fs; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + size_t inode_size = ext4_get16(&fs->sb, inode_size); + if (!extra_isize) + return; + + header = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + memset(header, 0, inode_size - EXT4_GOOD_OLD_INODE_SIZE - extra_isize); + header->h_magic = to_le32(EXT4_XATTR_MAGIC); + inode_ref->dirty = true; +} + +/** + * @brief Initialize a given xattr block + * + * @param inode_ref Inode reference + * @param block xattr block buffer + */ +static void ext4_xattr_block_initialize(struct ext4_inode_ref *inode_ref, + struct ext4_block *block) +{ + struct ext4_xattr_header *header; + struct ext4_fs *fs = inode_ref->fs; + + memset(block->data, 0, ext4_sb_get_block_size(&fs->sb)); + + header = EXT4_XATTR_BHDR(block); + header->h_magic = to_le32(EXT4_XATTR_MAGIC); + header->h_refcount = to_le32(1); + header->h_blocks = to_le32(1); + + ext4_trans_set_block_dirty(block->buf); +} + +static void ext4_xattr_block_init_search(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_search *s, + struct ext4_block *block) +{ + s->base = block->data; + s->end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb); + s->first = EXT4_XATTR_BFIRST(block); + s->here = NULL; + s->not_found = true; +} + +/** + * @brief Find an EA entry inside a xattr block + * + * @param inode_ref Inode reference + * @param finder The caller-provided finder block with + * information filled + * @param block The block buffer to be looked into + * + * @return Return EOK no matter the entry is found or not. + * If the IO operation or the buffer validation failed, + * return other value. + */ +static int ext4_xattr_block_find_entry(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_finder *finder, + struct ext4_block *block) +{ + int ret = EOK; + + /* Initialize the caller-given finder */ + finder->inode_ref = inode_ref; + memset(&finder->s, 0, sizeof(finder->s)); + + if (ret != EOK) + return ret; + + /* Check the validity of the buffer */ + if (!ext4_xattr_is_block_valid(inode_ref, block)) + return EIO; + + ext4_xattr_block_init_search(inode_ref, &finder->s, block); + ext4_xattr_find_entry(&finder->i, &finder->s); + return EOK; +} + +/** + * @brief Find an EA entry inside an inode's extra space + * + * @param inode_ref Inode reference + * @param finder The caller-provided finder block with + * information filled + * + * @return Return EOK no matter the entry is found or not. + * If the IO operation or the buffer validation failed, + * return other value. + */ +static int ext4_xattr_ibody_find_entry(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_finder *finder) +{ + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + size_t inode_size = ext4_get16(&fs->sb, inode_size); + + /* Initialize the caller-given finder */ + finder->inode_ref = inode_ref; + memset(&finder->s, 0, sizeof(finder->s)); + + /* + * If there is no extra inode space + * set ext4_xattr_ibody_finder::s::not_found to true and return EOK + */ + if (!extra_isize) { + finder->s.not_found = true; + return EOK; + } + + /* Check the validity of the buffer */ + if (!ext4_xattr_is_ibody_valid(inode_ref)) + return EIO; + + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + finder->s.base = EXT4_XATTR_IFIRST(iheader); + finder->s.end = (char *)inode_ref->inode + inode_size; + finder->s.first = EXT4_XATTR_IFIRST(iheader); + ext4_xattr_find_entry(&finder->i, &finder->s); + return EOK; +} + +/** + * @brief Try to allocate a block holding EA entries. + * + * @param inode_ref Inode reference + * + * @return Error code + */ +static int ext4_xattr_try_alloc_block(struct ext4_inode_ref *inode_ref) +{ + int ret = EOK; + + ext4_fsblk_t xattr_block = 0; + xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb); + + /* + * Only allocate a xattr block when there is no xattr block + * used by the inode. + */ + if (!xattr_block) { + ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); + + ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block); + if (ret != EOK) + goto Finish; + + ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb, + xattr_block); + } + +Finish: + return ret; +} + +/** + * @brief Try to free a block holding EA entries. + * + * @param inode_ref Inode reference + * + * @return Error code + */ +static void ext4_xattr_try_free_block(struct ext4_inode_ref *inode_ref) +{ + ext4_fsblk_t xattr_block; + xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb); + /* + * Free the xattr block used by the inode when there is one. + */ + if (xattr_block) { + ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb, + 0); + ext4_balloc_free_block(inode_ref, xattr_block); + inode_ref->dirty = true; + } +} + +/** + * @brief Put a list of EA entries into a caller-provided buffer + * In order to make sure that @list buffer can fit in the data, + * the routine should be called twice. + * + * @param inode_ref Inode reference + * @param list A caller-provided buffer to hold a list of EA entries. + * If list == NULL, list_len will contain the size of + * the buffer required to hold these entries + * @param list_len The length of the data written to @list + * @return Error code + */ +int ext4_xattr_list(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_list_entry *list, size_t *list_len) +{ + int ret = EOK; + size_t buf_len = 0; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_ibody_header *iheader; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + struct ext4_block block; + bool block_loaded = false; + ext4_fsblk_t xattr_block = 0; + struct ext4_xattr_entry *entry; + struct ext4_xattr_list_entry *list_prev = NULL; + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + /* + * If there is extra inode space and the xattr buffer in the + * inode is valid. + */ + if (extra_isize && ext4_xattr_is_ibody_valid(inode_ref)) { + iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode); + entry = EXT4_XATTR_IFIRST(iheader); + + /* + * The format of the list should be like this: + * + * name_len indicates the length in bytes of the name + * of the EA entry. The string is null-terminated. + * + * list->name => (char *)(list + 1); + * list->next => (void *)((char *)(list + 1) + name_len + 1); + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + if (list) { + list->name_index = entry->e_name_index; + list->name_len = name_len; + list->name = (char *)(list + 1); + memcpy(list->name, EXT4_XATTR_NAME(entry), + list->name_len); + + if (list_prev) + list_prev->next = list; + + list_prev = list; + list = (struct ext4_xattr_list_entry + *)(list->name + name_len + 1); + } + + /* + * Size calculation by pointer arithmetics. + */ + buf_len += + (char *)((struct ext4_xattr_list_entry *)0 + 1) + + name_len + 1 - + (char *)(struct ext4_xattr_list_entry *)0; + } + } + + /* + * If there is a xattr block used by the inode + */ + if (xattr_block) { + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + block_loaded = true; + + /* + * As we don't allow the content in the block being invalid, + * bail out. + */ + if (!ext4_xattr_is_block_valid(inode_ref, &block)) { + ret = EIO; + goto out; + } + + entry = EXT4_XATTR_BFIRST(&block); + + /* + * The format of the list should be like this: + * + * name_len indicates the length in bytes of the name + * of the EA entry. The string is null-terminated. + * + * list->name => (char *)(list + 1); + * list->next => (void *)((char *)(list + 1) + name_len + 1); + * + * Same as above actually. + */ + for (; !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry)) { + size_t name_len = entry->e_name_len; + if (list) { + list->name_index = entry->e_name_index; + list->name_len = name_len; + list->name = (char *)(list + 1); + memcpy(list->name, EXT4_XATTR_NAME(entry), + list->name_len); + + if (list_prev) + list_prev->next = list; + + list_prev = list; + list = (struct ext4_xattr_list_entry + *)(list->name + name_len + 1); + } + + /* + * Size calculation by pointer arithmetics. + */ + buf_len += + (char *)((struct ext4_xattr_list_entry *)0 + 1) + + name_len + 1 - + (char *)(struct ext4_xattr_list_entry *)0; + } + } + if (list_prev) + list_prev->next = NULL; +out: + if (ret == EOK && list_len) + *list_len = buf_len; + + if (block_loaded) + ext4_block_set(fs->bdev, &block); + + return ret; +} + +/** + * @brief Query EA entry's value with given name-index and name + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be queried + * @param name_len Length of name in bytes + * @param buf Output buffer to hold content + * @param buf_len Output buffer's length + * @param data_len The length of data of the EA entry found + * + * @return Error code + */ +int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, void *buf, size_t buf_len, + size_t *data_len) +{ + int ret = EOK; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_finder block_finder; + struct ext4_xattr_info i; + size_t value_len = 0; + size_t value_offs = 0; + struct ext4_fs *fs = inode_ref->fs; + ext4_fsblk_t xattr_block; + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = 0; + i.value_len = 0; + if (data_len) + *data_len = 0; + + ibody_finder.i = i; + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) + goto out; + + if (!ibody_finder.s.not_found) { + value_len = to_le32(ibody_finder.s.here->e_value_size); + value_offs = to_le32(ibody_finder.s.here->e_value_offs); + if (buf_len && buf) { + void *data_loc = + (char *)ibody_finder.s.base + value_offs; + memcpy(buf, data_loc, + (buf_len < value_len) ? buf_len : value_len); + } + } else { + struct ext4_block block; + + /* Return ENODATA if there is no EA block */ + if (!xattr_block) { + ret = ENODATA; + goto out; + } + + block_finder.i = i; + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + /* Return ENODATA if entry is not found */ + if (block_finder.s.not_found) { + ext4_block_set(fs->bdev, &block); + ret = ENODATA; + goto out; + } + + value_len = to_le32(block_finder.s.here->e_value_size); + value_offs = to_le32(block_finder.s.here->e_value_offs); + if (buf_len && buf) { + void *data_loc = + (char *)block_finder.s.base + value_offs; + memcpy(buf, data_loc, + (buf_len < value_len) ? buf_len : value_len); + } + + /* + * Free the xattr block buffer returned by + * ext4_xattr_block_find_entry. + */ + ext4_block_set(fs->bdev, &block); + } + +out: + if (ret == EOK && data_len) + *data_len = value_len; + + return ret; +} + +/** + * @brief Try to copy the content of an xattr block to a newly-allocated + * block. If the operation fails, the block buffer provided by + * caller will be freed + * + * @param inode_ref Inode reference + * @param block The block buffer reference + * @param new_block The newly-allocated block buffer reference + * @param orig_block The block number of @block + * @param allocated a new block is allocated + * + * @return Error code + */ +static int ext4_xattr_copy_new_block(struct ext4_inode_ref *inode_ref, + struct ext4_block *block, + struct ext4_block *new_block, + ext4_fsblk_t *orig_block, bool *allocated) +{ + int ret = EOK; + ext4_fsblk_t xattr_block = 0; + struct ext4_xattr_header *header; + struct ext4_fs *fs = inode_ref->fs; + header = EXT4_XATTR_BHDR(block); + + if (orig_block) + *orig_block = block->lb_id; + + if (allocated) + *allocated = false; + + /* Only do copy when a block is referenced by more than one inode. */ + if (to_le32(header->h_refcount) > 1) { + ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref); + + /* Allocate a new block to be used by this inode */ + ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block); + if (ret != EOK) + goto out; + + ret = ext4_trans_block_get(fs->bdev, new_block, xattr_block); + if (ret != EOK) + goto out; + + /* Copy the content of the whole block */ + memcpy(new_block->data, block->data, + ext4_sb_get_block_size(&inode_ref->fs->sb)); + + /* + * Decrement the reference count of the original xattr block + * by one + */ + header->h_refcount = to_le32(to_le32(header->h_refcount) - 1); + ext4_trans_set_block_dirty(block->buf); + ext4_trans_set_block_dirty(new_block->buf); + + header = EXT4_XATTR_BHDR(new_block); + header->h_refcount = to_le32(1); + + if (allocated) + *allocated = true; + } +out: + if (xattr_block) { + if (ret != EOK) + ext4_balloc_free_block(inode_ref, xattr_block); + else { + /* + * Modify the in-inode pointer to point to the new xattr block + */ + ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, xattr_block); + inode_ref->dirty = true; + } + } + + return ret; +} + +/** + * @brief Given an EA entry's name, remove the EA entry + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be removed + * @param name_len Length of name in bytes + * + * @return Error code + */ +int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len) +{ + int ret = EOK; + struct ext4_block block; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_finder block_finder; + bool use_block = false; + bool block_loaded = false; + struct ext4_xattr_info i; + struct ext4_fs *fs = inode_ref->fs; + ext4_fsblk_t xattr_block; + + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = NULL; + i.value_len = 0; + + ibody_finder.i = i; + block_finder.i = i; + + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) + goto out; + + if (ibody_finder.s.not_found && xattr_block) { + ret = ext4_trans_block_get(fs->bdev, &block, xattr_block); + if (ret != EOK) + goto out; + + block_loaded = true; + block_finder.i = i; + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &block); + if (ret != EOK) + goto out; + + /* Return ENODATA if entry is not found */ + if (block_finder.s.not_found) { + ret = ENODATA; + goto out; + } + use_block = true; + } + + if (use_block) { + bool allocated = false; + struct ext4_block new_block; + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &xattr_block, &allocated); + if (ret != EOK) + goto out; + + if (!allocated) { + /* Prevent double-freeing */ + block_loaded = false; + new_block = block; + } + + ret = ext4_xattr_block_find_entry(inode_ref, &block_finder, + &new_block); + if (ret != EOK) + goto out; + + /* Now remove the entry */ + ext4_xattr_set_entry(&i, &block_finder.s, false); + + if (ext4_xattr_is_empty(&block_finder.s)) { + ext4_block_set(fs->bdev, &new_block); + ext4_xattr_try_free_block(inode_ref); + } else { + struct ext4_xattr_header *header = + EXT4_XATTR_BHDR(&new_block); + header = EXT4_XATTR_BHDR(&new_block); + ext4_assert(block_finder.s.first); + ext4_xattr_rehash(header, block_finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + + ext4_trans_set_block_dirty(new_block.buf); + ext4_block_set(fs->bdev, &new_block); + } + + } else { + /* Now remove the entry */ + ext4_xattr_set_entry(&i, &block_finder.s, false); + inode_ref->dirty = true; + } +out: + if (block_loaded) + ext4_block_set(fs->bdev, &block); + + return ret; +} + +/** + * @brief Insert/overwrite an EA entry into/in a xattr block + * + * @param inode_ref Inode reference + * @param i The information of the given EA entry + * + * @return Error code + */ +static int ext4_xattr_block_set(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_info *i, + bool no_insert) +{ + int ret = EOK; + bool allocated = false; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_block block, new_block; + ext4_fsblk_t orig_xattr_block; + + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + ext4_assert(i->value); + if (!orig_xattr_block) { + struct ext4_xattr_search s; + struct ext4_xattr_header *header; + + /* If insertion of new entry is not allowed... */ + if (no_insert) { + ret = ENODATA; + goto out; + } + + ret = ext4_xattr_try_alloc_block(inode_ref); + if (ret != EOK) + goto out; + + orig_xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) { + ext4_xattr_try_free_block(inode_ref); + goto out; + } + + ext4_xattr_block_initialize(inode_ref, &block); + ext4_xattr_block_init_search(inode_ref, &s, &block); + + ret = ext4_xattr_set_entry(i, &s, false); + if (ret == EOK) { + header = EXT4_XATTR_BHDR(&block); + + ext4_assert(s.here); + ext4_assert(s.first); + ext4_xattr_compute_hash(header, s.here); + ext4_xattr_rehash(header, s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + ext4_block_set(fs->bdev, &block); + if (ret != EOK) + ext4_xattr_try_free_block(inode_ref); + + } else { + struct ext4_xattr_finder finder; + struct ext4_xattr_header *header; + finder.i = *i; + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) + goto out; + + header = EXT4_XATTR_BHDR(&block); + + /* + * Consider the following case when insertion of new + * entry is not allowed + */ + if (to_le32(header->h_refcount) > 1 && no_insert) { + /* + * There are other people referencing the + * same xattr block + */ + ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + if (finder.s.not_found) { + ext4_block_set(fs->bdev, &block); + ret = ENODATA; + goto out; + } + } + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &orig_xattr_block, &allocated); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + if (allocated) { + ext4_block_set(fs->bdev, &block); + new_block = block; + } + + ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + ret = ext4_xattr_set_entry(i, &finder.s, false); + if (ret == EOK) { + header = EXT4_XATTR_BHDR(&block); + + ext4_assert(finder.s.here); + ext4_assert(finder.s.first); + ext4_xattr_compute_hash(header, finder.s.here); + ext4_xattr_rehash(header, finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + ext4_block_set(fs->bdev, &block); + } +out: + return ret; +} + +/** + * @brief Remove an EA entry from a xattr block + * + * @param inode_ref Inode reference + * @param i The information of the given EA entry + * + * @return Error code + */ +static int ext4_xattr_block_remove(struct ext4_inode_ref *inode_ref, + struct ext4_xattr_info *i) +{ + int ret = EOK; + bool allocated = false; + const void *value = i->value; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_finder finder; + struct ext4_block block, new_block; + struct ext4_xattr_header *header; + ext4_fsblk_t orig_xattr_block; + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + ext4_assert(orig_xattr_block); + ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block); + if (ret != EOK) + goto out; + + /* + * There will be no effect when the xattr block is only referenced + * once. + */ + ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block, + &orig_xattr_block, &allocated); + if (ret != EOK) { + ext4_block_set(fs->bdev, &block); + goto out; + } + + if (allocated) { + ext4_block_set(fs->bdev, &block); + block = new_block; + } + + ext4_xattr_block_find_entry(inode_ref, &finder, &block); + + if (!finder.s.not_found) { + i->value = NULL; + ret = ext4_xattr_set_entry(i, &finder.s, false); + i->value = value; + + header = EXT4_XATTR_BHDR(&block); + ext4_assert(finder.s.first); + ext4_xattr_rehash(header, finder.s.first); + ext4_xattr_set_block_checksum(inode_ref, + block.lb_id, + header); + ext4_trans_set_block_dirty(block.buf); + } + + ext4_block_set(fs->bdev, &block); +out: + return ret; +} + +/** + * @brief Insert an EA entry into a given inode reference + * + * @param inode_ref Inode reference + * @param name_index Name-index + * @param name Name of the EA entry to be inserted + * @param name_len Length of name in bytes + * @param value Input buffer to hold content + * @param value_len Length of input content + * + * @return Error code + */ +int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index, + const char *name, size_t name_len, const void *value, + size_t value_len) +{ + int ret = EOK; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_xattr_finder ibody_finder; + struct ext4_xattr_info i; + bool block_found = false; + ext4_fsblk_t orig_xattr_block; + size_t extra_isize = + ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode); + + i.name_index = name_index; + i.name = name; + i.name_len = name_len; + i.value = (value_len) ? value : &ext4_xattr_empty_value; + i.value_len = value_len; + + ibody_finder.i = i; + + orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + + /* + * Even if entry is not found, search context block inside the + * finder is still valid and can be used to insert entry. + */ + ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + if (ret != EOK) { + ext4_xattr_ibody_initialize(inode_ref); + ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder); + } + + if (ibody_finder.s.not_found) { + if (orig_xattr_block) { + block_found = true; + ret = ext4_xattr_block_set(inode_ref, &i, true); + if (ret == ENOSPC) + goto try_insert; + else if (ret == ENODATA) + goto try_insert; + else if (ret != EOK) + goto out; + + } else + goto try_insert; + + } else { + try_insert: + /* Only try to set entry in ibody if inode is sufficiently large */ + if (extra_isize) + ret = ext4_xattr_set_entry(&i, &ibody_finder.s, false); + else + ret = ENOSPC; + + if (ret == ENOSPC) { + if (!block_found) { + ret = ext4_xattr_block_set(inode_ref, &i, false); + ibody_finder.i.value = NULL; + ext4_xattr_set_entry(&ibody_finder.i, + &ibody_finder.s, false); + inode_ref->dirty = true; + } + + } else if (ret == EOK) { + if (block_found) + ret = ext4_xattr_block_remove(inode_ref, &i); + + inode_ref->dirty = true; + } + } + +out: + return ret; +} + +#endif + +/** + * @} + */ diff --git a/lib/lwext4_rust/c/lwext4/src/ulibc.c b/lib/lwext4_rust/c/lwext4/src/ulibc.c new file mode 100644 index 0000000..41df52a --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/src/ulibc.c @@ -0,0 +1,162 @@ +#include +// #include +// #include + +// #include "ext4_debug.h" + +// // +++++++++ musl +++++++++ + +// #ifdef LWEXT4_ULIBC + +// #define ALIGN (sizeof(size_t)) +// #define ONES ((size_t) - 1 / UCHAR_MAX) +// #define HIGHS (ONES * (UCHAR_MAX / 2 + 1)) +// #define HASZERO(x) (((x) - ONES) & ~(x) & HIGHS) + +// __attribute__((weak)) +// char *__stpcpy(char *restrict d, const char *restrict s) +// { +// #ifdef __GNUC__ +// typedef size_t __attribute__((__may_alias__)) word; +// word *wd; +// const word *ws; +// if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) +// { +// for (; (uintptr_t)s % ALIGN; s++, d++) +// if (!(*d = *s)) +// return d; +// wd = (void *)d; +// ws = (const void *)s; +// for (; !HASZERO(*ws); *wd++ = *ws++) +// ; +// d = (void *)wd; +// s = (const void *)ws; +// } +// #endif +// for (; (*d = *s); s++, d++) +// ; + +// return d; +// } + +// __attribute__((weak)) +// char *strcpy(char *restrict dest, const char *restrict src) +// { +// __stpcpy(dest, src); +// return dest; +// } + +// __attribute__((weak)) +// int strcmp(const char *l, const char *r) +// { +// for (; *l == *r && *l; l++, r++) +// ; +// return *(unsigned char *)l - *(unsigned char *)r; +// } + +// __attribute__((weak)) +// int strncmp(const char *_l, const char *_r, size_t n) +// { +// const unsigned char *l = (void *)_l, *r = (void *)_r; +// if (!n--) +// return 0; +// for (; *l && *r && n && *l == *r; l++, r++, n--) +// ; +// return *l - *r; +// } + +// // fix me +// __attribute__((weak)) +// FILE *const stdout = NULL; + +// __attribute__((weak)) +// int fflush(FILE *f) +// { +// // printf("fflush() is not implemented !\n"); +// return 0; +// } + +// // +++++++++ uClibc +++++++++ + +// __attribute__((weak)) +// void *memset(void *s, int c, size_t n) +// { +// register unsigned char *p = (unsigned char *)s; +// while (n) +// { +// *p++ = (unsigned char)c; +// --n; +// } +// return s; +// } + +// // musl, typedef int (*cmpfun)(const void *, const void *); +// typedef int (*__compar_fn_t)(const void *, const void *); +// typedef int (*__compar_d_fn_t)(const void *, const void *, void *); +// __attribute__((weak)) +// void qsort_r(void *base, +// size_t nel, +// size_t width, +// __compar_d_fn_t comp, +// void *arg) +// { +// size_t wgap, i, j, k; +// char tmp; + +// if ((nel > 1) && (width > 0)) +// { +// // check for overflow +// // assert(nel <= ((size_t)(-1)) / width); +// ext4_assert(nel <= ((size_t)(-1)) / width); +// wgap = 0; +// do +// { +// wgap = 3 * wgap + 1; +// } while (wgap < (nel - 1) / 3); +// /* From the above, we know that either wgap == 1 < nel or */ +// /* ((wgap-1)/3 < (int) ((nel-1)/3) <= (nel-1)/3 ==> wgap < nel. */ +// wgap *= width; /* So this can not overflow if wnel doesn't. */ +// nel *= width; /* Convert nel to 'wnel' */ +// do +// { +// i = wgap; +// do +// { +// j = i; +// do +// { +// register char *a; +// register char *b; + +// j -= wgap; +// a = j + ((char *)base); +// b = a + wgap; +// if ((*comp)(a, b, arg) <= 0) +// { +// break; +// } +// k = width; +// do +// { +// tmp = *a; +// *a++ = *b; +// *b++ = tmp; +// } while (--k); +// } while (j >= wgap); +// i += width; +// } while (i < nel); +// wgap = (wgap - width) / 3; +// } while (wgap); +// } +// } + +// __attribute__((weak)) +// void qsort(void *base, +// size_t nel, +// size_t width, +// __compar_fn_t comp) +// { +// return qsort_r(base, nel, width, (__compar_d_fn_t)comp, NULL); +// } + +// #endif diff --git a/lib/lwext4_rust/c/lwext4/toolchain/arm-sim.cmake b/lib/lwext4_rust/c/lwext4/toolchain/arm-sim.cmake new file mode 100644 index 0000000..0b3c944 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/arm-sim.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR arm-sim) + +set(MCPU_FLAGS "") +set(VFP_FLAGS "") +set(LD_FLAGS "--specs=rdimon.specs -Wl,--start-group -lgcc -lc -lm -lrdimon -Wl,--end-group") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) diff --git a/lib/lwext4_rust/c/lwext4/toolchain/avrxmega7.cmake b/lib/lwext4_rust/c/lwext4/toolchain/avrxmega7.cmake new file mode 100644 index 0000000..3ace0cc --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/avrxmega7.cmake @@ -0,0 +1,7 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR avrxmega7) + +set(MCPU_FLAGS "-mmcu=avrxmega7") + +include(${CMAKE_CURRENT_LIST_DIR}/common/avr-gcc.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/common/arm-none-eabi.cmake b/lib/lwext4_rust/c/lwext4/toolchain/common/arm-none-eabi.cmake new file mode 100644 index 0000000..53af920 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/common/arm-none-eabi.cmake @@ -0,0 +1,23 @@ +# Toolchain settings +set(CMAKE_C_COMPILER arm-none-eabi-gcc) +set(CMAKE_CXX_COMPILER arm-none-eabi-g++) +set(AS arm-none-eabi-as) +set(AR arm-none-eabi-ar) +set(OBJCOPY arm-none-eabi-objcopy) +set(OBJDUMP arm-none-eabi-objdump) +set(SIZE arm-none-eabi-size) + +set(CMAKE_C_FLAGS "${MCPU_FLAGS} ${VFP_FLAGS} -Wall -fno-builtin -std=gnu11 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "${MCPU_FLAGS} ${VFP_FLAGS} -Wall -fno-builtin -fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "${MCPU_FLAGS} -x assembler-with-cpp" CACHE INTERNAL "asm compiler flags") +set(CMAKE_EXE_LINKER_FLAGS "${MCPU_FLAGS} ${LD_FLAGS} -Wl,--gc-sections" CACHE INTERNAL "exe link flags") + + + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/common/avr-gcc.cmake b/lib/lwext4_rust/c/lwext4/toolchain/common/avr-gcc.cmake new file mode 100644 index 0000000..523ce5e --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/common/avr-gcc.cmake @@ -0,0 +1,22 @@ +# Toolchain settings +set(CMAKE_C_COMPILER avr-gcc) +set(CMAKE_CXX_COMPILER avr-g++) +set(AS avr--gcc) +set(AR avr-ar) +set(OBJCOPY avr-objcopy) +set(OBJDUMP avr-objdump) +set(SIZE avr-size) + +set(CMAKE_C_FLAGS "${MCPU_FLAGS} -Wall -std=gnu11 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "${MCPU_FLAGS} -Wall -fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "${MCPU_FLAGS} -x assembler-with-cpp" CACHE INTERNAL "asm compiler flags") +set(CMAKE_EXE_LINKER_FLAGS "${MCPU_FLAGS} -nostartfiles -Wl,--gc-sections" CACHE INTERNAL "exe link flags") + + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/common/bfin-elf.cmake b/lib/lwext4_rust/c/lwext4/toolchain/common/bfin-elf.cmake new file mode 100644 index 0000000..edbe61f --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/common/bfin-elf.cmake @@ -0,0 +1,22 @@ +# Toolchain settings +set(CMAKE_C_COMPILER bfin-elf-gcc) +set(CMAKE_CXX_COMPILER bfin-elf-g++) +set(AS bfin-elf--gcc) +set(AR bfin-elf-ar) +set(OBJCOPY bfin-elf-objcopy) +set(OBJDUMP bfin-elf-objdump) +set(SIZE bfin-elf-size) + +set(CMAKE_C_FLAGS "${MCPU_FLAGS} -Wall -std=gnu99 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "${MCPU_FLAGS} -Wall -fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "${MCPU_FLAGS} -x assembler-with-cpp" CACHE INTERNAL "asm compiler flags") +set(CMAKE_EXE_LINKER_FLAGS "${MCPU_FLAGS} -nostartfiles -Wl,--gc-sections" CACHE INTERNAL "exe link flags") + + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/common/msp430-gcc.cmake b/lib/lwext4_rust/c/lwext4/toolchain/common/msp430-gcc.cmake new file mode 100644 index 0000000..8e903c3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/common/msp430-gcc.cmake @@ -0,0 +1,22 @@ +# Toolchain settings +set(CMAKE_C_COMPILER msp430-gcc) +set(CMAKE_CXX_COMPILER msp430-g++) +set(AS msp430--gcc) +set(AR msp430-ar) +set(OBJCOPY msp430-objcopy) +set(OBJDUMP msp430-objdump) +set(SIZE msp430-size) + +set(CMAKE_C_FLAGS "${MCPU_FLAGS} -Wall -std=gnu99 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "${MCPU_FLAGS} -Wall -fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "${MCPU_FLAGS} -x assembler-with-cpp" CACHE INTERNAL "asm compiler flags") +set(CMAKE_EXE_LINKER_FLAGS "${MCPU_FLAGS} -nostartfiles -Wl,--gc-sections" CACHE INTERNAL "exe link flags") + + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-Os -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0+.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0+.cmake new file mode 100644 index 0000000..56c4dde --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0+.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m0) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m0plus") +set(VFP_FLAGS "") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0.cmake new file mode 100644 index 0000000..0ad3d56 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m0.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m0) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m0") +set(VFP_FLAGS "") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m3.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m3.cmake new file mode 100644 index 0000000..69c514d --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m3.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m3) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m3") +set(VFP_FLAGS "") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4.cmake new file mode 100644 index 0000000..3447233 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m4) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m4") +set(VFP_FLAGS "") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4f.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4f.cmake new file mode 100644 index 0000000..fef16ed --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m4f.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m4) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m4") +set(VFP_FLAGS "-mfloat-abi=hard -mfpu=fpv4-sp-d16") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/cortex-m7.cmake b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m7.cmake new file mode 100644 index 0000000..1f1b093 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/cortex-m7.cmake @@ -0,0 +1,9 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-m4) + +set(MCPU_FLAGS "-mthumb -mcpu=cortex-m7") +set(VFP_FLAGS "-mfloat-abi=hard -mfpu=fpv4-sp-d16") +set(LD_FLAGS "-nostartfiles") + +include(${CMAKE_CURRENT_LIST_DIR}/common/arm-none-eabi.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/generic.cmake b/lib/lwext4_rust/c/lwext4/toolchain/generic.cmake new file mode 100644 index 0000000..4b5e329 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/generic.cmake @@ -0,0 +1,28 @@ +# Name of the target +set(CMAKE_SYSTEM_PROCESSOR generic) + +# Toolchain settings +set(CMAKE_C_COMPILER cc) +set(CMAKE_CXX_COMPILER c++) +set(AS as) +set(AR ar) +set(OBJCOPY objcopy) +set(OBJDUMP objdump) +set(SIZE size) + +set(CMAKE_C_FLAGS "-std=gnu99 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "-fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "" CACHE INTERNAL "asm compiler flags") +if (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-dead_strip" CACHE INTERNAL "exe link flags") +else (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections" CACHE INTERNAL "exe link flags") +endif (APPLE) + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") diff --git a/lib/lwext4_rust/c/lwext4/toolchain/mingw.cmake b/lib/lwext4_rust/c/lwext4/toolchain/mingw.cmake new file mode 100644 index 0000000..b251acb --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/mingw.cmake @@ -0,0 +1,32 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Windows) +set(TOOLCHAIN_PREFIX x86_64-w64-mingw32) +set(CMAKE_SYSTEM_PROCESSOR mingw) + +# Toolchain settings +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) +set(AS ${TOOLCHAIN_PREFIX}-as) +set(AR ${TOOLCHAIN_PREFIX}-as) +set(OBJCOPY objcopy) +set(OBJDUMP objdump) +set(SIZE size) + +set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) + +set(CMAKE_C_FLAGS "-std=gnu99 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "-fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "" CACHE INTERNAL "asm compiler flags") +if (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-dead_strip" CACHE INTERNAL "exe link flags") +else (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections" CACHE INTERNAL "exe link flags") +endif (APPLE) + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") diff --git a/lib/lwext4_rust/c/lwext4/toolchain/msp430.cmake b/lib/lwext4_rust/c/lwext4/toolchain/msp430.cmake new file mode 100644 index 0000000..aaab41e --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/msp430.cmake @@ -0,0 +1,7 @@ +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR msp430g2210) + +set(MCPU_FLAGS "-mmcu=msp430g2210") + +include(${CMAKE_CURRENT_LIST_DIR}/common/msp430-gcc.cmake) \ No newline at end of file diff --git a/lib/lwext4_rust/c/lwext4/toolchain/musl-generic.cmake b/lib/lwext4_rust/c/lwext4/toolchain/musl-generic.cmake new file mode 100644 index 0000000..5940ac3 --- /dev/null +++ b/lib/lwext4_rust/c/lwext4/toolchain/musl-generic.cmake @@ -0,0 +1,57 @@ +if(NOT DEFINED ENV{ARCH}) + set(ARCH "x86_64") +else() + set(ARCH $ENV{ARCH}) +endif() + +# Name of the target +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_PROCESSOR ${ARCH}) + +# Toolchain settings +set(TOOLCHAIN_PREFIX ${ARCH}-linux-gnu) + +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-c++) +set(AS ${TOOLCHAIN_PREFIX}-as) +set(AR ${TOOLCHAIN_PREFIX}-ar) +set(OBJCOPY ${TOOLCHAIN_PREFIX}-objcopy) +set(OBJDUMP ${TOOLCHAIN_PREFIX}-objdump) +set(SIZE ${TOOLCHAIN_PREFIX}-size) + +set(LD_FLAGS "-nolibc -nostdlib -static --gc-sections -nostartfiles") + +set(CMAKE_C_FLAGS "-std=gnu99 -fdata-sections -ffunction-sections -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0" CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS "-fdata-sections -ffunction-sections -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0" CACHE INTERNAL "cxx compiler flags") +set(CMAKE_ASM_FLAGS "" CACHE INTERNAL "asm compiler flags") + +if(ARCH STREQUAL "x86_64") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mno-sse") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-sse") +elseif (ARCH STREQUAL "aarch64") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mgeneral-regs-only") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mgeneral-regs-only") +elseif (ARCH STREQUAL "riscv64") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gc -mabi=lp64d -mcmodel=medany") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gc -mabi=lp64d -mcmodel=medany") +elseif (ARCH STREQUAL "loongarch64") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msoft-float") +endif() + +set(CMAKE_C_FLAGS "-fPIC -fno-builtin -ffreestanding -fno-omit-frame-pointer ${CMAKE_C_FLAGS}") +set(CMAKE_CXX_FLAGS "-fPIC -nostdinc -fno-builtin -ffreestanding -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}") + +if (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-dead_strip" CACHE INTERNAL "exe link flags") +else (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections" CACHE INTERNAL "exe link flags") +endif (APPLE) + +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "c debug compiler flags") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb3" CACHE INTERNAL "cxx debug compiler flags") +SET(CMAKE_ASM_FLAGS_DEBUG "-g -ggdb3" CACHE INTERNAL "asm debug compiler flags") + +SET(CMAKE_C_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "c release compiler flags") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -ggdb3" CACHE INTERNAL "cxx release compiler flags") +SET(CMAKE_ASM_FLAGS_RELEASE "" CACHE INTERNAL "asm release compiler flags") diff --git a/lib/lwext4_rust/c/wrapper.h b/lib/lwext4_rust/c/wrapper.h new file mode 100644 index 0000000..36f6a58 --- /dev/null +++ b/lib/lwext4_rust/c/wrapper.h @@ -0,0 +1,8 @@ +#include "lwext4/include/ext4.h" +#include "lwext4/include/ext4_dir.h" +#include "lwext4/include/ext4_fs.h" +#include "lwext4/include/ext4_inode.h" +#include "lwext4/include/ext4_mbr.h" +#include "lwext4/include/ext4_mkfs.h" + +#include "lwext4/fs_test/common/test_lwext4.h" diff --git a/lib/lwext4_rust/src/blockdev.rs b/lib/lwext4_rust/src/blockdev.rs new file mode 100644 index 0000000..90f33f3 --- /dev/null +++ b/lib/lwext4_rust/src/blockdev.rs @@ -0,0 +1,178 @@ +use core::{ + ffi::{c_int, c_void}, + mem, ptr, slice, +}; + +use crate::{Ext4Result, error::Context, ffi::*}; +use alloc::boxed::Box; + +/// Device block size. +pub const EXT4_DEV_BSIZE: usize = 512; + +pub trait BlockDevice { + /// Writes blocks to the device, starting from the given block ID. + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Ext4Result; + + /// Reads blocks from the device, starting from the given block ID. + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Ext4Result; + + /// Gets the number of blocks on the device. + fn num_blocks(&self) -> Ext4Result; +} + +/// Holds necessary resources for the ext4 block device, and automatically frees +/// them when the instance is dropped. +#[allow(dead_code)] +struct ResourceGuard { + dev: Box, + block_buf: Box<[u8; EXT4_DEV_BSIZE]>, + block_cache_buf: Box, + block_dev_iface: Box, +} + +pub struct Ext4BlockDevice { + pub(crate) inner: Box, + _guard: ResourceGuard, +} + +impl Ext4BlockDevice { + pub fn new(dev: Dev) -> Ext4Result { + let mut dev = Box::new(dev); + + // Block size buffer + let mut block_buf = Box::new([0u8; EXT4_DEV_BSIZE]); + let mut block_dev_iface = Box::new(ext4_blockdev_iface { + open: Some(Self::dev_open), + bread: Some(Self::dev_bread), + bwrite: Some(Self::dev_bwrite), + close: Some(Self::dev_close), + lock: None, + unlock: None, + ph_bsize: EXT4_DEV_BSIZE as u32, + ph_bcnt: 0, + ph_bbuf: block_buf.as_mut_ptr(), + ph_refctr: 0, + bread_ctr: 0, + bwrite_ctr: 0, + p_user: dev.as_mut() as *mut _ as *mut c_void, + }); + + let mut block_cache_buf: Box = Box::new(unsafe { mem::zeroed() }); + let mut blockdev = Box::new(ext4_blockdev { + bdif: block_dev_iface.as_mut(), + part_offset: 0, + part_size: 0, + bc: block_cache_buf.as_mut(), + lg_bsize: 0, + lg_bcnt: 0, + cache_write_back: 0, + fs: ptr::null_mut(), + journal: ptr::null_mut(), + }); + + unsafe { + ext4_block_init(blockdev.as_mut()).context("ext4_block_init")?; + ext4_block_cache_write_back(blockdev.as_mut(), 1) + .context("ext4_block_cache_write_back") + .inspect_err(|_| { + ext4_block_fini(blockdev.as_mut()); + })?; + } + Ok(Self { + inner: blockdev, + _guard: ResourceGuard { + dev, + block_buf, + block_cache_buf, + block_dev_iface, + }, + }) + } + + unsafe fn dev_read_fields<'a>( + bdev: *mut ext4_blockdev, + ) -> ( + &'a mut ext4_blockdev, + &'a mut ext4_blockdev_iface, + &'a mut Dev, + ) { + let bdev = unsafe { &mut *bdev }; + let bdif = unsafe { &mut *bdev.bdif }; + let dev = unsafe { &mut *(bdif.p_user as *mut Dev) }; + (bdev, bdif, dev) + } + unsafe extern "C" fn dev_open(bdev: *mut ext4_blockdev) -> c_int { + debug!("open ext4 block device"); + let (bdev, bdif, dev) = unsafe { Self::dev_read_fields(bdev) }; + + bdif.ph_bcnt = match dev.num_blocks() { + Ok(cur) => cur, + Err(err) => { + error!("num_blocks failed: {err:?}"); + return EIO as _; + } + }; + + bdev.part_offset = 0; + bdev.part_size = bdif.ph_bcnt * bdif.ph_bsize as u64; + EOK as _ + } + unsafe extern "C" fn dev_bread( + bdev: *mut ext4_blockdev, + buf: *mut c_void, + blk_id: u64, + blk_cnt: u32, + ) -> c_int { + trace!("read ext4 block id={blk_id} count={blk_cnt}"); + if blk_cnt == 0 { + return EOK as _; + } + + let (_bdev, bdif, dev) = unsafe { Self::dev_read_fields(bdev) }; + let buf_len = (bdif.ph_bsize * blk_cnt) as usize; + let buffer = unsafe { slice::from_raw_parts_mut(buf as *mut u8, buf_len) }; + if let Err(err) = dev.read_blocks(blk_id, buffer) { + error!("read_blocks failed: {err:?}"); + return EIO as _; + } + + EOK as _ + } + unsafe extern "C" fn dev_bwrite( + bdev: *mut ext4_blockdev, + buf: *const c_void, + blk_id: u64, + blk_cnt: u32, + ) -> c_int { + trace!("write ext4 block id={blk_id} count={blk_cnt}"); + if blk_cnt == 0 { + return EOK as _; + } + + let (_bdev, bdif, dev) = unsafe { Self::dev_read_fields(bdev) }; + let buf_len = (bdif.ph_bsize * blk_cnt) as usize; + let buffer = unsafe { slice::from_raw_parts(buf as *const u8, buf_len) }; + if let Err(err) = dev.write_blocks(blk_id, buffer) { + error!("read_blocks failed: {err:?}"); + return EIO as _; + } + + // drop_cache(); + // sync + + EOK as _ + } + unsafe extern "C" fn dev_close(_bdev: *mut ext4_blockdev) -> c_int { + debug!("close ext4 block device"); + EOK as _ + } +} + +impl Drop for Ext4BlockDevice { + fn drop(&mut self) { + unsafe { + let bdev = self.inner.as_mut(); + ext4_block_fini(bdev); + } + } +} diff --git a/lib/lwext4_rust/src/error.rs b/lib/lwext4_rust/src/error.rs new file mode 100644 index 0000000..fa81dc8 --- /dev/null +++ b/lib/lwext4_rust/src/error.rs @@ -0,0 +1,63 @@ +use core::{ + error::Error, + fmt::{Debug, Display}, +}; + +use crate::ffi::EOK; + +pub type Ext4Result = Result; + +pub struct Ext4Error { + pub code: i32, + pub context: Option<&'static str>, +} +impl Ext4Error { + pub fn new(code: i32, context: impl Into>) -> Self { + Ext4Error { + code, + context: context.into(), + } + } +} + +impl From for Ext4Error { + fn from(code: i32) -> Self { + Ext4Error::new(code, None) + } +} + +impl Display for Ext4Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if let Some(context) = self.context { + write!(f, "ext4 error {}: {context}", self.code) + } else { + write!(f, "ext4 error {}", self.code) + } + } +} + +impl Debug for Ext4Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Display::fmt(self, f) + } +} + +impl Error for Ext4Error {} + +pub(crate) trait Context { + fn context(self, context: &'static str) -> Result; +} +impl Context<()> for i32 { + fn context(self, context: &'static str) -> Result<(), Ext4Error> { + if self != EOK as _ { + Err(Ext4Error::new(self, Some(context))) + } else { + Ok(()) + } + } +} +impl Context for Ext4Result { + fn context(self, context: &'static str) -> Result { + self.map_err(|e| Ext4Error::new(e.code, Some(context))) + } +} diff --git a/lib/lwext4_rust/src/fs.rs b/lib/lwext4_rust/src/fs.rs new file mode 100644 index 0000000..31e357e --- /dev/null +++ b/lib/lwext4_rust/src/fs.rs @@ -0,0 +1,289 @@ +use core::{marker::PhantomData, mem, time::Duration}; + +use alloc::boxed::Box; + +use crate::{ + DirLookupResult, DirReader, Ext4Error, Ext4Result, FileAttr, InodeRef, InodeType, + blockdev::{BlockDevice, Ext4BlockDevice}, + error::Context, + ffi::*, + util::get_block_size, +}; + +pub trait SystemHal { + fn now() -> Option; +} + +pub struct DummyHal; +impl SystemHal for DummyHal { + fn now() -> Option { + None + } +} + +#[derive(Debug, Clone)] +pub struct FsConfig { + pub bcache_size: u32, +} +impl Default for FsConfig { + fn default() -> Self { + Self { + bcache_size: CONFIG_BLOCK_DEV_CACHE_SIZE, + } + } +} + +#[derive(Debug, Clone)] +pub struct StatFs { + pub inodes_count: u32, + pub free_inodes_count: u32, + + pub blocks_count: u64, + pub free_blocks_count: u64, + pub block_size: u32, +} + +pub struct Ext4Filesystem { + inner: Box, + bdev: Ext4BlockDevice, + _phantom: PhantomData, +} + +impl Ext4Filesystem { + pub fn new(dev: Dev, config: FsConfig) -> Ext4Result { + let mut bdev = Ext4BlockDevice::new(dev)?; + let mut fs = Box::new(unsafe { mem::zeroed() }); + unsafe { + let bd = bdev.inner.as_mut(); + ext4_fs_init(&mut *fs, bd, false).context("ext4_fs_init")?; + + let bs = get_block_size(&fs.sb); + ext4_block_set_lb_size(bd, bs); + ext4_bcache_init_dynamic(bd.bc, config.bcache_size, bs) + .context("ext4_bcache_init_dynamic")?; + if bs != (*bd.bc).itemsize { + return Err(Ext4Error::new(ENOTSUP as _, "block size mismatch")); + } + + bd.fs = &mut *fs; + + let mut result = Self { + inner: fs, + bdev, + _phantom: PhantomData, + }; + let bd = result.bdev.inner.as_mut(); + ext4_block_bind_bcache(bd, bd.bc).context("ext4_block_bind_bcache")?; + Ok(result) + } + } + + fn inode_ref(&mut self, ino: u32) -> Ext4Result> { + unsafe { + let mut result = InodeRef::new(mem::zeroed()); + ext4_fs_get_inode_ref(self.inner.as_mut(), ino, result.inner.as_mut()) + .context("ext4_fs_get_inode_ref")?; + Ok(result) + } + } + fn clone_ref(&mut self, inode: &InodeRef) -> InodeRef { + self.inode_ref(inode.ino()).expect("inode ref clone failed") + } + + pub fn with_inode_ref( + &mut self, + ino: u32, + f: impl FnOnce(&mut InodeRef) -> Ext4Result, + ) -> Ext4Result { + let mut inode = self.inode_ref(ino)?; + f(&mut inode) + } + + pub(crate) fn alloc_inode(&mut self, ty: InodeType) -> Ext4Result> { + unsafe { + let ty = match ty { + InodeType::Fifo => EXT4_DE_FIFO, + InodeType::CharacterDevice => EXT4_DE_CHRDEV, + InodeType::Directory => EXT4_DE_DIR, + InodeType::BlockDevice => EXT4_DE_BLKDEV, + InodeType::RegularFile => EXT4_DE_REG_FILE, + InodeType::Symlink => EXT4_DE_SYMLINK, + InodeType::Socket => EXT4_DE_SOCK, + InodeType::Unknown => EXT4_DE_UNKNOWN, + }; + let mut result = InodeRef::new(mem::zeroed()); + ext4_fs_alloc_inode(self.inner.as_mut(), result.inner.as_mut(), ty as _) + .context("ext4_fs_get_inode_ref")?; + ext4_fs_inode_blocks_init(self.inner.as_mut(), result.inner.as_mut()); + Ok(result) + } + } + + pub fn get_attr(&mut self, ino: u32, attr: &mut FileAttr) -> Ext4Result<()> { + self.inode_ref(ino)?.get_attr(attr); + Ok(()) + } + + pub fn read_at(&mut self, ino: u32, buf: &mut [u8], offset: u64) -> Ext4Result { + self.inode_ref(ino)?.read_at(buf, offset) + } + pub fn write_at(&mut self, ino: u32, buf: &[u8], offset: u64) -> Ext4Result { + self.inode_ref(ino)?.write_at(buf, offset) + } + pub fn set_len(&mut self, ino: u32, len: u64) -> Ext4Result<()> { + self.inode_ref(ino)?.set_len(len) + } + pub fn set_symlink(&mut self, ino: u32, buf: &[u8]) -> Ext4Result<()> { + self.inode_ref(ino)?.set_symlink(buf) + } + pub fn lookup(&mut self, parent: u32, name: &str) -> Ext4Result> { + self.inode_ref(parent)?.lookup(name) + } + pub fn read_dir(&mut self, parent: u32, offset: u64) -> Ext4Result> { + self.inode_ref(parent)?.read_dir(offset) + } + + pub fn create(&mut self, parent: u32, name: &str, ty: InodeType, mode: u32) -> Ext4Result { + let mut child = self.alloc_inode(ty)?; + let mut parent = self.inode_ref(parent)?; + parent.add_entry(name, &mut child)?; + if ty == InodeType::Directory { + child.add_entry(".", &mut self.clone_ref(&child))?; + child.add_entry("..", &mut parent)?; + assert_eq!(child.nlink(), 2); + } + child.set_mode((child.mode() & !0o777) | (mode & 0o777)); + + Ok(child.ino()) + } + + pub fn rename( + &mut self, + src_dir: u32, + src_name: &str, + dst_dir: u32, + dst_name: &str, + ) -> Ext4Result { + let mut src_dir_ref = self.inode_ref(src_dir)?; + let mut dst_dir_ref = self.inode_ref(dst_dir)?; + + // TODO: optimize + match self.unlink(dst_dir, dst_name) { + Ok(_) => {} + Err(err) if err.code == ENOENT as i32 => {} + Err(err) => return Err(err), + } + + let src = self.lookup(src_dir, src_name)?.entry().ino(); + + let mut src_ref = self.inode_ref(src)?; + if src_ref.is_dir() { + let mut result = self.clone_ref(&src_ref).lookup("..")?; + result.entry().raw_entry_mut().set_ino(dst_dir); + src_dir_ref.dec_nlink(); + dst_dir_ref.inc_nlink(); + } + src_dir_ref.remove_entry(src_name, &mut src_ref)?; + dst_dir_ref.add_entry(dst_name, &mut src_ref)?; + + Ok(()) + } + + pub fn link(&mut self, dir: u32, name: &str, child: u32) -> Ext4Result { + let mut child_ref = self.inode_ref(child)?; + if child_ref.is_dir() { + return Err(Ext4Error::new(EISDIR as _, "cannot link to directory")); + } + self.inode_ref(dir)?.add_entry(name, &mut child_ref)?; + Ok(()) + } + + pub fn unlink(&mut self, dir: u32, name: &str) -> Ext4Result { + let mut dir_ref = self.inode_ref(dir)?; + let child = self.clone_ref(&dir_ref).lookup(name)?.entry().ino(); + let mut child_ref = self.inode_ref(child)?; + + if self.clone_ref(&child_ref).has_children()? { + return Err(Ext4Error::new(ENOTEMPTY as _, None)); + } + if child_ref.inode_type() == InodeType::Directory { + // According to `ext4_trunc_dir` + let bs = get_block_size(&self.inner.as_mut().sb); + child_ref.truncate(bs as _)?; + } + + dir_ref.remove_entry(name, &mut child_ref)?; + + if child_ref.is_dir() { + dir_ref.dec_nlink(); + child_ref.dec_nlink(); + } + if child_ref.nlink() == 0 { + child_ref.truncate(0)?; + unsafe { + ext4_inode_set_del_time(child_ref.inner.inode, u32::MAX); + child_ref.mark_dirty(); + ext4_fs_free_inode(child_ref.inner.as_mut()); + } + } + Ok(()) + } + + pub fn stat(&mut self) -> Ext4Result { + let sb = &mut self.inner.as_mut().sb; + Ok(StatFs { + inodes_count: u32::from_le(sb.inodes_count), + free_inodes_count: u32::from_le(sb.free_inodes_count), + blocks_count: (u32::from_le(sb.blocks_count_hi) as u64) << 32 + | u32::from_le(sb.blocks_count_lo) as u64, + free_blocks_count: (u32::from_le(sb.free_blocks_count_hi) as u64) << 32 + | u32::from_le(sb.free_blocks_count_lo) as u64, + block_size: get_block_size(sb), + }) + } + + pub fn flush(&mut self) -> Ext4Result<()> { + unsafe { + ext4_block_cache_flush(self.bdev.inner.as_mut()).context("ext4_cache_flush")?; + } + Ok(()) + } +} + +impl Drop for Ext4Filesystem { + fn drop(&mut self) { + unsafe { + let r = ext4_fs_fini(self.inner.as_mut()); + if r != 0 { + log::error!("ext4_fs_fini failed: {}", Ext4Error::new(r, None)); + } + let bdev = self.bdev.inner.as_mut(); + ext4_bcache_cleanup(bdev.bc); + ext4_block_fini(bdev); + ext4_bcache_fini_dynamic(bdev.bc); + } + } +} + +// The underlying C structures contain raw pointers and are not automatically +// `Send`/`Sync`. In this project the filesystem is always synchronized by +// the surrounding kernel locking; mark the type as `Send`/`Sync` so it can +// be stored inside our kernel `Mutex`/`Arc`. This is an unsafe, but +// controlled decision. +unsafe impl Send for Ext4Filesystem {} +unsafe impl Sync for Ext4Filesystem {} + +pub(crate) struct WritebackGuard { + bdev: *mut ext4_blockdev, +} +impl WritebackGuard { + pub fn new(bdev: *mut ext4_blockdev) -> Self { + unsafe { ext4_block_cache_write_back(bdev, 1) }; + Self { bdev } + } +} +impl Drop for WritebackGuard { + fn drop(&mut self) { + unsafe { ext4_block_cache_write_back(self.bdev, 0) }; + } +} diff --git a/lib/lwext4_rust/src/inode/attr.rs b/lib/lwext4_rust/src/inode/attr.rs new file mode 100644 index 0000000..66eb06b --- /dev/null +++ b/lib/lwext4_rust/src/inode/attr.rs @@ -0,0 +1,153 @@ +use core::time::Duration; + +use crate::{SystemHal, ffi::*, util::get_block_size}; + +use super::{InodeRef, InodeType}; + +/// Filesystem node metadata. +#[derive(Clone, Debug, Default)] +pub struct FileAttr { + /// ID of device containing file + pub device: u64, + /// Inode number + pub ino: u32, + /// Number of hard links + pub nlink: u64, + /// Permission mode + pub mode: u32, + /// Type of file + pub node_type: InodeType, + /// User ID of owner + pub uid: u32, + /// Group ID of owner + pub gid: u32, + /// Total size in bytes + pub size: u64, + /// Block size for filesystem I/O + pub block_size: u64, + /// Number of 512B blocks allocated + pub blocks: u64, + + /// Time of last access + pub atime: Duration, + /// Time of last modification + pub mtime: Duration, + /// Time of last status change + pub ctime: Duration, +} + +fn encode_time(dur: &Duration) -> (u32, u32) { + let sec = dur.as_secs(); + let nsec = dur.subsec_nanos(); + let time = u32::to_le(sec as u32); + let extra = u32::to_le((nsec << 2) | (sec >> 32) as u32); + (time, extra) +} +fn decode_time(time: u32, extra: u32) -> Duration { + let sec = u32::from_le(time); + let extra = u32::from_le(extra); + let epoch = extra & 3; + let nsec = extra >> 2; + + Duration::new(sec as u64 + ((epoch as u64) << 32), nsec) +} + +impl InodeRef { + pub fn inode_type(&self) -> InodeType { + ((self.mode() >> 12) as u8).into() + } + + pub fn is_dir(&self) -> bool { + self.inode_type() == InodeType::Directory + } + + pub fn size(&self) -> u64 { + unsafe { ext4_inode_get_size(self.superblock() as *const _ as _, self.inner.inode) } + } + + pub fn mode(&self) -> u32 { + unsafe { ext4_inode_get_mode(self.superblock() as *const _ as _, self.inner.inode) } + } + pub fn set_mode(&mut self, mode: u32) { + unsafe { + ext4_inode_set_mode(self.superblock_mut(), self.inner.inode, mode); + self.mark_dirty(); + } + } + + pub fn nlink(&self) -> u16 { + u16::from_le(self.raw_inode().links_count) + } + + pub fn uid(&self) -> u16 { + u16::from_le(self.raw_inode().uid) + } + pub fn gid(&self) -> u16 { + u16::from_le(self.raw_inode().gid) + } + + pub fn set_owner(&mut self, uid: u16, gid: u16) { + let inode = self.raw_inode_mut(); + inode.uid = u16::to_le(uid); + inode.gid = u16::to_le(gid); + self.mark_dirty(); + } + + pub fn set_atime(&mut self, dur: &Duration) { + let (time, extra) = encode_time(dur); + let inode = self.raw_inode_mut(); + inode.access_time = time; + inode.atime_extra = extra; + self.mark_dirty(); + } + pub fn set_mtime(&mut self, dur: &Duration) { + let (time, extra) = encode_time(dur); + let inode = self.raw_inode_mut(); + inode.modification_time = time; + inode.mtime_extra = extra; + self.mark_dirty(); + } + pub fn set_ctime(&mut self, dur: &Duration) { + let (time, extra) = encode_time(dur); + let inode = self.raw_inode_mut(); + inode.change_inode_time = time; + inode.ctime_extra = extra; + self.mark_dirty(); + } + + pub fn update_atime(&mut self) { + if let Some(dur) = Hal::now() { + self.set_atime(&dur); + } + } + pub fn update_mtime(&mut self) { + if let Some(dur) = Hal::now() { + self.set_mtime(&dur); + } + } + pub fn update_ctime(&mut self) { + if let Some(dur) = Hal::now() { + self.set_ctime(&dur); + } + } + + pub fn get_attr(&self, attr: &mut FileAttr) { + attr.device = 0; + attr.ino = u32::from_le(self.inner.index); + attr.nlink = self.nlink() as _; + attr.mode = self.mode(); + attr.node_type = self.inode_type(); + attr.uid = self.uid() as _; + attr.gid = self.gid() as _; + attr.size = self.size(); + attr.block_size = get_block_size(self.superblock()) as _; + attr.blocks = unsafe { + ext4_inode_get_blocks_count(self.superblock() as *const _ as _, self.inner.inode) + }; + + let inode = self.raw_inode(); + attr.atime = decode_time(inode.access_time, inode.atime_extra); + attr.mtime = decode_time(inode.modification_time, inode.mtime_extra); + attr.ctime = decode_time(inode.change_inode_time, inode.ctime_extra); + } +} diff --git a/lib/lwext4_rust/src/inode/dir.rs b/lib/lwext4_rust/src/inode/dir.rs new file mode 100644 index 0000000..5ded808 --- /dev/null +++ b/lib/lwext4_rust/src/inode/dir.rs @@ -0,0 +1,212 @@ +use core::{mem, slice}; + +use crate::{Ext4Result, SystemHal, error::Context, ffi::*, util::revision_tuple}; + +use super::{InodeRef, InodeType}; + +impl InodeRef { + pub fn read_dir(mut self, offset: u64) -> Ext4Result> { + unsafe { + let mut iter = mem::zeroed(); + ext4_dir_iterator_init(&mut iter, self.inner.as_mut(), offset) + .context("ext4_dir_iterator_init")?; + + Ok(DirReader { + parent: self, + inner: iter, + }) + } + } + + pub fn lookup(mut self, name: &str) -> Ext4Result> { + unsafe { + let mut result = mem::zeroed(); + ext4_dir_find_entry( + &mut result, + self.inner.as_mut(), + name.as_ptr() as *const _, + name.len() as _, + ) + .context("ext4_dir_find_entry")?; + + Ok(DirLookupResult { + parent: self, + inner: result, + }) + } + } + + pub fn has_children(self) -> Ext4Result { + if self.inode_type() != InodeType::Directory { + return Ok(false); + } + let mut reader = self.read_dir(0)?; + while let Some(curr) = reader.current() { + let name = curr.name(); + if name != b"." && name != b".." { + return Ok(true); + } + reader.step()?; + } + Ok(false) + } + + pub(crate) fn add_entry(&mut self, name: &str, entry: &mut InodeRef) -> Ext4Result { + unsafe { + ext4_dir_add_entry( + self.inner.as_mut(), + name.as_ptr() as *const _, + name.len() as _, + entry.inner.as_mut(), + ) + .context("ext4_dir_add_entry")?; + } + entry.inc_nlink(); + Ok(()) + } + pub(crate) fn remove_entry(&mut self, name: &str, entry: &mut InodeRef) -> Ext4Result { + unsafe { + ext4_dir_remove_entry( + self.inner.as_mut(), + name.as_ptr() as *const _, + name.len() as _, + ) + .context("ext4_dir_remove_entry")?; + } + entry.dec_nlink(); + Ok(()) + } +} + +pub struct DirLookupResult { + parent: InodeRef, + inner: ext4_dir_search_result, +} +impl DirLookupResult { + pub fn entry(&mut self) -> DirEntry { + DirEntry { + inner: unsafe { &mut *(self.inner.dentry as *mut _) }, + sb: self.parent.superblock(), + } + } +} +impl Drop for DirLookupResult { + fn drop(&mut self) { + unsafe { + ext4_dir_destroy_result(self.parent.inner.as_mut(), &mut self.inner); + } + } +} + +#[repr(transparent)] +pub struct RawDirEntry { + inner: ext4_dir_en, +} +impl RawDirEntry { + pub fn ino(&self) -> u32 { + u32::from_le(self.inner.inode) + } + pub fn set_ino(&mut self, ino: u32) { + self.inner.inode = u32::to_le(ino); + } + + pub fn len(&self) -> u16 { + u16::from_le(self.inner.entry_len) + } + + pub fn name<'a>(&'a self, sb: &ext4_sblock) -> &'a [u8] { + let mut name_len = self.inner.name_len as u16; + if revision_tuple(sb) < (0, 5) { + let high = unsafe { self.inner.in_.name_length_high }; + name_len |= (high as u16) << 8; + } + unsafe { slice::from_raw_parts(self.inner.name.as_ptr(), name_len as usize) } + } + + pub fn inode_type(&self, sb: &ext4_sblock) -> InodeType { + if revision_tuple(sb) < (0, 5) { + InodeType::Unknown + } else { + match unsafe { self.inner.in_.inode_type } as u32 { + EXT4_DE_DIR => InodeType::Directory, + EXT4_DE_REG_FILE => InodeType::RegularFile, + EXT4_DE_SYMLINK => InodeType::Symlink, + EXT4_DE_CHRDEV => InodeType::CharacterDevice, + EXT4_DE_BLKDEV => InodeType::BlockDevice, + EXT4_DE_FIFO => InodeType::Fifo, + EXT4_DE_SOCK => InodeType::Socket, + _ => InodeType::Unknown, + } + } + } +} + +pub struct DirEntry<'a> { + inner: &'a mut RawDirEntry, + sb: &'a ext4_sblock, +} +impl DirEntry<'_> { + pub fn ino(&self) -> u32 { + self.inner.ino() + } + + pub fn name(&self) -> &[u8] { + self.inner.name(self.sb) + } + + pub fn inode_type(&self) -> InodeType { + self.inner.inode_type(self.sb) + } + + pub fn len(&self) -> u16 { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.len() == 0 + } + + pub fn raw_entry(&self) -> &RawDirEntry { + self.inner + } + pub fn raw_entry_mut(&mut self) -> &mut RawDirEntry { + self.inner + } +} + +/// Reader returned by [`InodeRef::read_dir`]. +pub struct DirReader { + parent: InodeRef, + inner: ext4_dir_iter, +} +impl DirReader { + pub fn current(&self) -> Option { + if self.inner.curr.is_null() { + return None; + } + let curr = unsafe { &mut *(self.inner.curr as *mut _) }; + let sb = self.parent.superblock(); + + Some(DirEntry { inner: curr, sb }) + } + + pub fn step(&mut self) -> Ext4Result { + if !self.inner.curr.is_null() { + unsafe { + ext4_dir_iterator_next(&mut self.inner).context("ext4_dir_iterator_next")?; + } + } + Ok(()) + } + + pub fn offset(&self) -> u64 { + self.inner.curr_off + } +} +impl Drop for DirReader { + fn drop(&mut self) { + unsafe { + ext4_dir_iterator_fini(&mut self.inner); + } + } +} diff --git a/lib/lwext4_rust/src/inode/file.rs b/lib/lwext4_rust/src/inode/file.rs new file mode 100644 index 0000000..12370a5 --- /dev/null +++ b/lib/lwext4_rust/src/inode/file.rs @@ -0,0 +1,303 @@ +use core::{ + mem::{self, offset_of}, + slice, +}; + +use super::InodeRef; + +use crate::{ + Ext4Result, InodeType, SystemHal, WritebackGuard, error::Context, ffi::*, util::get_block_size, +}; + +fn take<'a>(buf: &mut &'a [u8], cnt: usize) -> &'a [u8] { + let (first, rem) = buf.split_at(cnt.min(buf.len())); + *buf = rem; + first +} +fn take_mut<'a>(buf: &mut &'a mut [u8], cnt: usize) -> &'a mut [u8] { + // use mem::take to circumvent lifetime issues + let pos = cnt.min(buf.len()); + let (first, rem) = mem::take(buf).split_at_mut(pos); + *buf = rem; + first +} + +impl InodeRef { + fn get_inode_fblock(&mut self, block: u32) -> Ext4Result { + unsafe { + let mut fblock = 0u64; + ext4_fs_get_inode_dblk_idx(self.inner.as_mut(), block, &mut fblock, true) + .context("ext4_fs_get_inode_dblk_idx")?; + Ok(fblock) + } + } + fn init_inode_fblock(&mut self, block: u32) -> Ext4Result { + unsafe { + let mut fblock = 0u64; + ext4_fs_init_inode_dblk_idx(self.inner.as_mut(), block, &mut fblock) + .context("ext4_fs_init_inode_dblk_idx")?; + Ok(fblock) + } + } + fn append_inode_fblock(&mut self) -> Ext4Result<(u64, u32)> { + unsafe { + let mut fblock = 0u64; + let mut block = 0u32; + ext4_fs_append_inode_dblk(self.inner.as_mut(), &mut fblock, &mut block) + .context("ext4_fs_append_inode_dblk_idx")?; + Ok((fblock, block)) + } + } + + fn read_bytes(&mut self, offset: u64, buf: &mut [u8]) -> Ext4Result<()> { + unsafe { + let bdev = (*self.inner.fs).bdev; + ext4_block_readbytes(bdev, offset, buf.as_mut_ptr() as _, buf.len() as _) + .context("ext4_block_readbytes") + } + } + fn write_bytes(&mut self, offset: u64, buf: &[u8]) -> Ext4Result<()> { + unsafe { + let bdev = (*self.inner.fs).bdev; + ext4_block_writebytes(bdev, offset, buf.as_ptr() as _, buf.len() as _) + .context("ext4_block_writebytes") + } + } + + pub fn read_at(&mut self, mut buf: &mut [u8], pos: u64) -> Ext4Result { + unsafe { + let file_size = self.size(); + let block_size = get_block_size(self.superblock()); + let bdev = (*self.inner.fs).bdev; + + if pos >= file_size || buf.is_empty() { + return Ok(0); + } + let to_be_read = buf.len().min((file_size - pos) as usize); + buf = &mut buf[..to_be_read]; + + let inode = self.raw_inode(); + + // symlink inline data + if self.inode_type() == InodeType::Symlink && file_size < size_of::<[u32; 15]>() as u64 + { + let content = (inode as *const _ as *const u8).add(offset_of!(ext4_inode, blocks)); + let buf = take_mut(&mut buf, (file_size - pos) as usize); + buf.copy_from_slice(slice::from_raw_parts(content.add(pos as usize), buf.len())); + } + + let mut block_start = (pos / block_size as u64) as u32; + // This is inclusive! + let block_end = ((pos + buf.len() as u64).min(file_size) / block_size as u64) as u32; + + let offset = pos % block_size as u64; + if offset > 0 { + let buf = take_mut(&mut buf, block_size as usize - offset as usize); + let fblock = self.get_inode_fblock(block_start)?; + if fblock != 0 { + self.read_bytes(fblock * block_size as u64 + offset, buf)?; + } else { + buf.fill(0); + } + block_start += 1; + } + + let guard = WritebackGuard::new(bdev); + + // Each block corresponds to a fblock, and we can read multiple + // fblocks at once if they are consecutive. + let mut fblock_start = 0; + let mut fblock_count = 0; + + let flush_fblock_segment = |buf: &mut &mut [u8], start: u64, count: u32| { + if count == 0 { + return Ok(()); + } + let buf = take_mut(buf, count as usize * block_size as usize); + ext4_blocks_get_direct(bdev, buf.as_mut_ptr() as _, start, count) + .context("ext4_blocks_get_direct") + }; + for block in block_start..block_end { + let fblock = self.get_inode_fblock(block)?; + if fblock != fblock_start + fblock_count as u64 { + flush_fblock_segment(&mut buf, fblock_start, fblock_count)?; + fblock_start = fblock; + fblock_count = 0; + } + + if fblock == 0 { + take_mut(&mut buf, block_size as usize).fill(0); + } else { + fblock_count += 1; + } + } + flush_fblock_segment(&mut buf, fblock_start, fblock_count)?; + + drop(guard); + + assert!(buf.len() < block_size as usize); + if !buf.is_empty() { + let fblock = self.get_inode_fblock(block_end)?; + if fblock != 0 { + self.read_bytes(fblock * block_size as u64, buf)?; + } else { + buf.fill(0); + } + } + + Ok(to_be_read) + } + } + + pub fn write_at(&mut self, mut buf: &[u8], pos: u64) -> Ext4Result { + unsafe { + let mut file_size = self.size(); + if pos > file_size { + self.set_len(pos)?; + // If we extend the file, we need to update the file size. + file_size = self.size(); + } + + let block_size = get_block_size(self.superblock()); + let block_count = file_size.div_ceil(block_size as u64) as u32; + let bdev = (*self.inner.fs).bdev; + + if buf.is_empty() { + return Ok(0); + } + let to_be_written = buf.len(); + + // TODO: symlink? + + let get_fblock = |this: &mut Self, block: u32| -> Ext4Result { + if block < block_count { + this.init_inode_fblock(block) + } else { + let (fblock, new_block) = this.append_inode_fblock()?; + assert_eq!(block, new_block); + Ok(fblock) + } + }; + + let mut block_start = (pos / block_size as u64) as u32; + // This is inclusive! + let block_end = ((pos + buf.len() as u64) / block_size as u64) as u32; + + let offset = pos % block_size as u64; + if offset > 0 { + let buf = take(&mut buf, block_size as usize - offset as usize); + let fblock = get_fblock(self, block_start)?; + self.write_bytes(fblock * block_size as u64 + offset, buf)?; + block_start += 1; + } + + let mut fblock_start = 0; + let mut fblock_count = 0; + + let flush_fblock_segment = |buf: &mut &[u8], start: u64, count: u32| { + if count == 0 { + return Ok(()); + } + let buf = take(buf, count as usize * block_size as usize); + ext4_blocks_set_direct(bdev, buf.as_ptr() as _, start, count) + .context("ext4_blocks_set_direct") + }; + for block in block_start..block_end { + let fblock = get_fblock(self, block)?; + if fblock != fblock_start + fblock_count as u64 { + flush_fblock_segment(&mut buf, fblock_start, fblock_count)?; + fblock_start = fblock; + fblock_count = 0; + } + fblock_count += 1; + } + flush_fblock_segment(&mut buf, fblock_start, fblock_count)?; + + assert!(buf.len() < block_size as usize); + if !buf.is_empty() { + let fblock = get_fblock(self, block_end)?; + self.write_bytes(fblock * block_size as u64, buf)?; + } + + let end = pos + to_be_written as u64; + if end > file_size { + ext4_inode_set_size(self.inner.inode, end); + self.mark_dirty(); + } + + Ok(to_be_written) + } + } + + pub fn truncate(&mut self, size: u64) -> Ext4Result<()> { + unsafe { + let bdev = (*self.inner.fs).bdev; + let _guard = WritebackGuard::new(bdev); + ext4_fs_truncate_inode(self.inner.as_mut(), size).context("ext4_fs_truncate_inode") + } + } + + pub fn set_symlink(&mut self, target: &[u8]) -> Ext4Result<()> { + let block_size = get_block_size(self.superblock()); + if target.len() > block_size as usize { + // ENAMETOOLONG + return 36.context("symlink too long"); + } + + unsafe { + if target.len() < size_of::() * EXT4_INODE_BLOCKS as usize { + let ptr = (self.inner.inode as *mut u8).add(offset_of!(ext4_inode, blocks)); + slice::from_raw_parts_mut(ptr, target.len()).copy_from_slice(target); + ext4_inode_clear_flag(self.inner.inode, EXT4_INODE_FLAG_EXTENTS); + } else { + ext4_fs_inode_blocks_init(self.inner.fs, self.inner.as_mut()); + let mut fblock: u64 = 0; + let mut sblock: u32 = 0; + ext4_fs_append_inode_dblk(self.inner.as_mut(), &mut fblock, &mut sblock) + .context("ext4_fs_append_inode_dblk")?; + + let off = fblock * block_size as u64; + self.write_bytes(off, target)?; + } + ext4_inode_set_size(self.inner.inode, target.len() as u64); + } + + Ok(()) + } + + pub fn set_len(&mut self, len: u64) -> Ext4Result<()> { + static EMPTY: [u8; 4096] = [0; 4096]; + + let cur_len = self.size(); + if len < cur_len { + self.truncate(len)?; + } else if len > cur_len { + // TODO: correct implementation + let block_size = get_block_size(self.superblock()); + let old_blocks = cur_len.div_ceil(block_size as u64) as u32; + let new_blocks = len.div_ceil(block_size as u64) as u32; + for block in old_blocks..new_blocks { + let (fblock, new_block) = self.append_inode_fblock()?; + assert_eq!(block, new_block); + self.write_bytes(fblock * block_size as u64, &EMPTY[..block_size as usize])?; + } + + // Clear the last block extended part + let old_last_block = (cur_len / block_size as u64) as u32; + let old_block_start = (cur_len - (old_last_block as u64 * block_size as u64)) as usize; + let fblock = self.init_inode_fblock(old_last_block)?; + assert!(fblock != 0, "fblock should not be zero"); + let length = block_size as usize - old_block_start; + self.write_bytes( + fblock * block_size as u64 + old_block_start as u64, + &EMPTY[..length], + )?; + + unsafe { + ext4_inode_set_size(self.inner.inode, len); + } + self.mark_dirty(); + } + Ok(()) + } +} diff --git a/lib/lwext4_rust/src/inode/mod.rs b/lib/lwext4_rust/src/inode/mod.rs new file mode 100644 index 0000000..e435ba3 --- /dev/null +++ b/lib/lwext4_rust/src/inode/mod.rs @@ -0,0 +1,101 @@ +mod attr; +mod dir; +mod file; + +use alloc::boxed::Box; +pub use attr::FileAttr; +pub use dir::{DirEntry, DirLookupResult, DirReader}; + +use core::marker::PhantomData; + +use crate::{SystemHal, ffi::*}; + +/// Inode type. +#[repr(u8)] +#[derive(PartialEq, Default, Eq, Clone, Copy, Debug)] +pub enum InodeType { + #[default] + Unknown = 0, + Fifo = 1, + CharacterDevice = 2, + Directory = 4, + BlockDevice = 6, + RegularFile = 8, + Symlink = 10, + Socket = 12, +} +impl From for InodeType { + fn from(value: u8) -> Self { + match value { + 1 => InodeType::Fifo, + 2 => InodeType::CharacterDevice, + 4 => InodeType::Directory, + 6 => InodeType::BlockDevice, + 8 => InodeType::RegularFile, + 10 => InodeType::Symlink, + 12 => InodeType::Socket, + _ => InodeType::Unknown, + } + } +} + +#[repr(transparent)] +pub struct InodeRef { + pub(crate) inner: Box, + _phantom: PhantomData, +} +impl InodeRef { + pub(crate) fn new(inner: ext4_inode_ref) -> Self { + Self { + inner: Box::new(inner), + _phantom: PhantomData, + } + } + + pub fn ino(&self) -> u32 { + self.inner.index + } + + pub(crate) fn superblock(&self) -> &ext4_sblock { + unsafe { &(*self.inner.fs).sb } + } + pub(crate) fn superblock_mut(&mut self) -> &mut ext4_sblock { + unsafe { &mut (*self.inner.fs).sb } + } + + pub(crate) fn mark_dirty(&mut self) { + self.inner.dirty = true; + } + + pub(crate) fn inc_nlink(&mut self) { + unsafe { + ext4_fs_inode_links_count_inc(self.inner.as_mut()); + } + self.mark_dirty(); + } + pub(crate) fn dec_nlink(&mut self) { + self.set_nlink(self.nlink() - 1); + self.mark_dirty(); + } + + pub(crate) fn set_nlink(&mut self, nlink: u16) { + self.raw_inode_mut().links_count = u16::to_le(nlink); + self.mark_dirty(); + } + + pub(crate) fn raw_inode(&self) -> &ext4_inode { + unsafe { &*self.inner.inode } + } + pub(crate) fn raw_inode_mut(&mut self) -> &mut ext4_inode { + unsafe { &mut *self.inner.inode } + } +} + +impl Drop for InodeRef { + fn drop(&mut self) { + let ret = unsafe { ext4_fs_put_inode_ref(self.inner.as_mut()) }; + if ret != 0 { + panic!("ext4_fs_put_inode_ref failed: {}", ret); + } + } +} diff --git a/lib/lwext4_rust/src/lib.rs b/lib/lwext4_rust/src/lib.rs new file mode 100644 index 0000000..df2b773 --- /dev/null +++ b/lib/lwext4_rust/src/lib.rs @@ -0,0 +1,30 @@ +#![no_std] +#![feature(linkage)] +#![feature(c_variadic, c_size_t)] +#![feature(associated_type_defaults)] + +extern crate alloc; + +#[macro_use] +extern crate log; + +// mod ulibc; + +pub mod ffi { + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + #![allow(non_snake_case)] + + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +mod blockdev; +mod error; +mod fs; +mod inode; +mod util; + +pub use blockdev::{BlockDevice, EXT4_DEV_BSIZE}; +pub use error::{Ext4Error, Ext4Result}; +pub use fs::*; +pub use inode::*; diff --git a/lib/lwext4_rust/src/ulibc.rs b/lib/lwext4_rust/src/ulibc.rs new file mode 100644 index 0000000..fa79307 --- /dev/null +++ b/lib/lwext4_rust/src/ulibc.rs @@ -0,0 +1,111 @@ +mod uprint { + use core::ffi::{c_char, c_int}; + + #[cfg(feature = "print")] + #[linkage = "weak"] + #[unsafe(no_mangle)] + unsafe extern "C" fn printf(str: *const c_char, mut args: ...) -> c_int { + // extern "C" { pub fn printf(arg1: *const c_char, ...) -> c_int; } + use printf_compat::{format, output}; + + let mut s = alloc::string::String::new(); + let bytes_written = + unsafe { format(str as _, args.as_va_list(), output::fmt_write(&mut s)) }; + //println!("{}", s); + info!("{}", s); + + bytes_written + } + + #[cfg(not(feature = "print"))] + #[linkage = "weak"] + #[unsafe(no_mangle)] + unsafe extern "C" fn printf(str: *const c_char, _args: ...) -> c_int { + use core::ffi::CStr; + let c_str = unsafe { CStr::from_ptr(str) }; + //let arg1 = args.arg::(); + + info!("[lwext4] {:?}", c_str); + 0 + } +} + +mod ualloc { + use alloc::alloc::{Layout, alloc, dealloc}; + use alloc::slice::from_raw_parts_mut; + use core::cmp::min; + use core::ffi::{c_int, c_size_t, c_void}; + + #[unsafe(no_mangle)] + pub extern "C" fn ext4_user_calloc(m: c_size_t, n: c_size_t) -> *mut c_void { + let mem = ext4_user_malloc(m * n); + + unsafe extern "C" { + pub fn memset(dest: *mut c_void, c: c_int, n: c_size_t) -> *mut c_void; + } + unsafe { memset(mem, 0, m * n) } + } + + #[unsafe(no_mangle)] + pub extern "C" fn ext4_user_realloc(memblock: *mut c_void, size: c_size_t) -> *mut c_void { + if memblock.is_null() { + warn!("realloc a a null mem pointer"); + return ext4_user_malloc(size); + } + + let ptr = memblock.cast::(); + let old_size = unsafe { ptr.sub(1).read().size }; + info!("realloc from {} to {}", old_size, size); + + let mem = ext4_user_malloc(size); + + unsafe { + let old_size = min(size, old_size); + let mbuf = from_raw_parts_mut(mem as *mut u8, old_size); + mbuf.copy_from_slice(from_raw_parts_mut(memblock as *mut u8, old_size)); + } + ext4_user_free(memblock); + + mem + } + + struct MemoryControlBlock { + size: usize, + } + const CTRL_BLK_SIZE: usize = core::mem::size_of::(); + + /// Allocate size bytes memory and return the memory address. + #[unsafe(no_mangle)] + pub extern "C" fn ext4_user_malloc(size: c_size_t) -> *mut c_void { + // Allocate `(actual length) + 8`. The lowest 8 Bytes are stored in the actual allocated space size. + let layout = Layout::from_size_align(size + CTRL_BLK_SIZE, 8).unwrap(); + unsafe { + let ptr = alloc(layout); + assert!(!ptr.is_null(), "malloc failed"); + //debug!("malloc {}@{:p}", size + CTRL_BLK_SIZE, ptr); + + let ptr = ptr.cast::(); + ptr.write(MemoryControlBlock { size }); + ptr.add(1).cast() + } + } + + /// Deallocate memory at ptr address + #[unsafe(no_mangle)] + pub extern "C" fn ext4_user_free(ptr: *mut c_void) { + if ptr.is_null() { + warn!("free a null pointer !"); + return; + } + //debug!("free pointer {:p}", ptr); + + let ptr = ptr.cast::(); + assert!(ptr as usize > CTRL_BLK_SIZE, "free a null pointer"); // ? + unsafe { + let ptr = ptr.sub(1); + let size = ptr.read().size; + let layout = Layout::from_size_align(size + CTRL_BLK_SIZE, 8).unwrap(); + dealloc(ptr.cast(), layout) + } + } +} diff --git a/lib/lwext4_rust/src/util.rs b/lib/lwext4_rust/src/util.rs new file mode 100644 index 0000000..901e4c8 --- /dev/null +++ b/lib/lwext4_rust/src/util.rs @@ -0,0 +1,9 @@ +use crate::ffi::ext4_sblock; + +pub fn get_block_size(sb: &ext4_sblock) -> u32 { + 1024u32 << u32::from_le(sb.log_block_size) +} + +pub fn revision_tuple(sb: &ext4_sblock) -> (u32, u16) { + (u32::from_le(sb.rev_level), u16::from_le(sb.minor_rev_level)) +} diff --git a/scripts/build.rs b/scripts/build.rs new file mode 100644 index 0000000..2c249e8 --- /dev/null +++ b/scripts/build.rs @@ -0,0 +1,36 @@ +fn main() { + let platform = std::env::var("PLATFORM").unwrap_or_else(|_| "qemu-virt-riscv64".to_string()); + let arch = std::env::var("ARCH").unwrap(); + let arch_bits = std::env::var("ARCH_BITS").unwrap(); + + match platform.as_str() { + "qemu-virt-riscv64" => { + println!("cargo:rustc-cfg=platform_riscv_common"); + } + _ => { + println!("cargo:warning=Unknown platform: {}", platform); + } + } + println!("cargo:rustc-cfg=arch_{}{}", arch, arch_bits); + + // Link C library + println!("cargo:rustc-link-search=native=clib/build/{}{}", arch, arch_bits); + println!("cargo:rustc-link-lib=static=kernelx_clib"); + println!("cargo:rerun-if-changed=clib/build/{}{}/libkernelx_clib.a", arch, arch_bits); + + // Link vdso + let vdso_path = format!("vdso/build/{}{}/vdso.o", arch, arch_bits); + println!("cargo:rustc-link-arg={}", vdso_path); + println!("cargo:rerun-if-changed={}", vdso_path); + + // vDSO symbols + let symbols_src = format!("vdso/build/{}{}/symbols.inc", arch, arch_bits); + println!("cargo:rerun-if-changed={}", symbols_src); + + // Linker script + // let linker = format!("scripts/linker/{}.ld", platform); + let linker = format!("scripts/linker/{}{}.ld", arch, arch_bits); + println!("cargo:rustc-link-arg=-T{}", linker); + println!("cargo:rustc-link-arg=-Map=link.map"); + println!("cargo:rerun-if-changed={}", linker); +} diff --git a/scripts/cmake/env.cmake b/scripts/cmake/env.cmake new file mode 100644 index 0000000..d8d1d53 --- /dev/null +++ b/scripts/cmake/env.cmake @@ -0,0 +1,14 @@ +include(${KERNELX_HOME}/scripts/cmake/parse_config.cmake) + +if(ARCH STREQUAL "riscv") + if (ARCH_BITS STREQUAL "64") + parse_config(${KERNELX_HOME}/scripts/env/riscv64.env) + message("ARCH_COMMON_FLAGS=${ARCH_COMMON_FLAGS}") + else() + message(FATAL_ERROR "Unsupported riscv architecture bits: ${ARCH_BITS}") + endif() +else() + message(FATAL_ERROR "Unsupported architecture: ${ARCH}") +endif() + +string(REPLACE " " ";" ARCH_COMMON_FLAGS_LIST ${ARCH_COMMON_FLAGS}) diff --git a/scripts/cmake/parse_config.cmake b/scripts/cmake/parse_config.cmake new file mode 100644 index 0000000..3af1e08 --- /dev/null +++ b/scripts/cmake/parse_config.cmake @@ -0,0 +1,24 @@ +function(parse_config config_file) + file(STRINGS "${config_file}" lines) + + foreach(line IN LISTS lines) + if(line MATCHES "^$" OR line MATCHES "^#") + continue() + endif() + + if(line MATCHES "^([A-Za-z0-9_]+)=([yYnN]|\".*\"|[0-9]+|[^\"\n]+)$") + set(_var_name "${CMAKE_MATCH_1}") + set(_var_value "${CMAKE_MATCH_2}") + + if(_var_value MATCHES "^(y|Y)$") + set(_var_value ON) + elseif(_var_value MATCHES "^(n|N)$") + set(_var_value OFF) + elseif(_var_value MATCHES "^\".*\"$") + string(REGEX REPLACE "^\"(.*)\"$" "\\1" _var_value "${_var_value}") + endif() + + set(${_var_name} "${_var_value}" PARENT_SCOPE) + endif() + endforeach() +endfunction() \ No newline at end of file diff --git a/scripts/env/common.env b/scripts/env/common.env new file mode 100644 index 0000000..00eea8d --- /dev/null +++ b/scripts/env/common.env @@ -0,0 +1 @@ +CC=clang diff --git a/scripts/env/riscv64.env b/scripts/env/riscv64.env new file mode 100644 index 0000000..5b34b15 --- /dev/null +++ b/scripts/env/riscv64.env @@ -0,0 +1 @@ +ARCH_COMMON_FLAGS=--target=riscv64-linux-gnu -mabi=lp64d -march=rv64gc -mcmodel=medlow -fvisibility=hidden \ No newline at end of file diff --git a/scripts/linker/qemu-virt-riscv64.ld b/scripts/linker/qemu-virt-riscv64.ld new file mode 100644 index 0000000..29f60b9 --- /dev/null +++ b/scripts/linker/qemu-virt-riscv64.ld @@ -0,0 +1,75 @@ +PHYS_BASE = 0x80200000; +VIRT_BASE = 0xffffffc080200000; + +ENTRY(bootloader) + +EXTERN(entry) + +SECTIONS { + . = PHYS_BASE; + .bootloader : { + KEEP(*(.text.bootloader)) + KEEP(*(.rodata.bootloader)) + } + + . = VIRT_BASE + 0x5000; + __kernel_start = .; + lma = PHYS_BASE + 0x5000; + + .text : AT(lma) { + __text_start = .; + KEEP(*(.text.entry)) + *(.text*) + __text_end = .; + . = ALIGN(4K); + __trampoline_start = .; + KEEP(*(.trampoline.text)) + __trampoline_end = .; + } + lma += SIZEOF(.text); + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + .rodata : AT(lma) { + __rodata_start = .; + *(.got*) + *(.rodata*) + *(.srodata*) + __rodata_end = .; + } + lma += SIZEOF(.rodata); + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + .data : AT(lma) { + __data_start = .; + *(.data*) + *(.sdata*) + __data_end = .; + } + lma += SIZEOF(.data); + + .bss : { + __bss_start = .; + *(.bss*) + *(.sbss*) + *(.scommon) + __bss_end = .; + } + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + + __stack_start = .; + . = . + 0x8000; + __stack_end = .; + + . = ALIGN(4K); + __heap_start = .; + . = . + 0x1000000; + __heap_end = .; + + __kernel_end = .; + + /DISCARD/ : { + *(.eh_frame) + *(.eh_frame_hdr) + } +} diff --git a/scripts/linker/riscv64.ld b/scripts/linker/riscv64.ld new file mode 100644 index 0000000..dfeb3f9 --- /dev/null +++ b/scripts/linker/riscv64.ld @@ -0,0 +1,68 @@ +VIRT_BASE = 0xffffffc000000000; + +ENTRY(_entry) +EXTERN(_entry) + +SECTIONS { + . = VIRT_BASE; + + __kernel_start = .; + __init_start = .; + .init : { + KEEP(*(.text.entry)) + KEEP(*(.text.init)) + KEEP(*(.data.init)) + } + . = ALIGN(4K); + __init_end = .; + + .text : { + __text_start = .; + *(.text*) + __text_end = .; + . = ALIGN(4K); + __trampoline_start = .; + KEEP(*(.trampoline.text)) + __trampoline_end = .; + } + + . = ALIGN(4K); + .rodata : { + __rodata_start = .; + *(.got*) + *(.rodata*) + *(.srodata*) + __rodata_end = .; + } + + . = ALIGN(4K); + .data : { + __data_start = .; + *(.data*) + *(.sdata*) + __data_end = .; + } + + .bss : { + __bss_start = .; + *(.bss*) + *(.sbss*) + *(.scommon) + __bss_end = .; + } + + . = ALIGN(4K); + + __stack_start = .; + . = . + 0x8000; + __stack_end = .; + __ktrap_temp_stack_start = .; + . = . + 0x8000; + __ktrap_temp_stack_end = .; + __kernel_end = .; + + /DISCARD/ : { + *(.eh_frame) + *(.eh_frame_hdr) + } +} diff --git a/scripts/linker/starfive-jh7110-riscv64.ld b/scripts/linker/starfive-jh7110-riscv64.ld new file mode 100644 index 0000000..fe11dee --- /dev/null +++ b/scripts/linker/starfive-jh7110-riscv64.ld @@ -0,0 +1,75 @@ +PHYS_BASE = 0x40080000; +VIRT_BASE = 0xffffffc040080000; + +ENTRY(bootloader) + +EXTERN(entry) + +SECTIONS { + . = PHYS_BASE; + .bootloader : { + KEEP(*(.text.bootloader)) + KEEP(*(.rodata.bootloader)) + } + + . = VIRT_BASE + 0x5000; + __kernel_start = .; + lma = PHYS_BASE + 0x5000; + + .text : AT(lma) { + __text_start = .; + KEEP(*(.text.entry)) + *(.text*) + __text_end = .; + . = ALIGN(4K); + __trampoline_start = .; + KEEP(*(.trampoline.text)) + __trampoline_end = .; + } + lma += SIZEOF(.text); + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + .rodata : AT(lma) { + __rodata_start = .; + *(.got*) + *(.rodata*) + *(.srodata*) + __rodata_end = .; + } + lma += SIZEOF(.rodata); + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + .data : AT(lma) { + __data_start = .; + *(.data*) + *(.sdata*) + __data_end = .; + } + lma += SIZEOF(.data); + + .bss : { + __bss_start = .; + *(.bss*) + *(.sbss*) + *(.scommon) + __bss_end = .; + } + + . = ALIGN(4K); lma = ALIGN(lma, 4K); + + __stack_start = .; + . = . + 0x16000; + __stack_end = .; + + . = ALIGN(4K); + __heap_start = .; + . = . + 0x1000000; + __heap_end = .; + + __kernel_end = .; + + /DISCARD/ : { + *(.eh_frame) + *(.eh_frame_hdr) + } +} diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..936511e --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e +mkdir -p temp_image +sudo mount -o loop $IMAGE temp_image + +sudo mkdir -p temp_image/boot +sudo cp $KERNEL_IMAGE temp_image/boot/kernel + +sudo umount temp_image diff --git a/scripts/qemu.mk b/scripts/qemu.mk new file mode 100644 index 0000000..22150be --- /dev/null +++ b/scripts/qemu.mk @@ -0,0 +1,51 @@ +include config/config.mk + +IMAGE = build/$(ARCH)$(ARCH_BITS)/Image +VMKERNELX = build/$(ARCH)$(ARCH_BITS)/vmkernelx + +TMPDISK_SIZE ?= 1G +TMPDISK := $(shell mktemp /tmp/qemu-tmpdisk-XXXXXX) + +QEMU = qemu-system-riscv64 +QEMU_FLAGS += -M $(CONFIG_QEMU_MACHINE) -m $(CONFIG_QEMU_MEMORY) -nographic +QEMU_FLAGS += -kernel $(IMAGE) +QEMU_FLAGS += -drive file=$(CONFIG_DISK_IMAGE),if=none,id=x0,format=raw +QEMU_FLAGS += -drive file=$(TMPDISK),if=none,id=x1,format=raw +QEMU_FLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +QEMU_FLAGS += -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 +QEMU_FLAGS += -smp $(CONFIG_QEMU_CPUS) + +# Set bootargs +ifneq ($(CONFIG_INITPATH),) +BOOTARGS += init=$(CONFIG_INITPATH) +endif + +ifneq ($(CONFIG_INITCWD),) +BOOTARGS += initcwd=$(CONFIG_INITCWD) +endif + +ifneq ($(CONFIG_ROOT_DEVICE),) +BOOTARGS += root=$(CONFIG_ROOT_DEVICE) +endif + +ifneq ($(CONFIG_ROOT_FSTYPE),) +BOOTARGS += rootfstype=$(CONFIG_ROOT_FSTYPE) +endif + +QEMU_FLAGS += -append "$(BOOTARGS)" + +qemu-run: + truncate -s $(TMPDISK_SIZE) $(TMPDISK) + $(QEMU) $(QEMU_FLAGS) + @ rm -f $(TMPDISK) + +qemu-gdb: + @ truncate -s $(TMPDISK_SIZE) $(TMPDISK) + $(QEMU) $(QEMU_FLAGS) -s -S + @ rm -f $(TMPDISK) + +qemu-dts: + $(QEMU) $(QEMU_FLAGS) -machine dumpdtb=qemu-virt-riscv64.dtb + @ dtc -I dtb -O dts qemu-virt-riscv64.dtb -o qemu-virt-riscv64.dts + +.PHONY: qemu-run qemu-gdb qemu-dts diff --git a/src/arch/arch.rs b/src/arch/arch.rs new file mode 100644 index 0000000..01d8709 --- /dev/null +++ b/src/arch/arch.rs @@ -0,0 +1,79 @@ +use crate::kernel::mm::MapPerm; + +use super::{KernelContext, SigContext}; + +#[derive(Debug, Clone, Copy)] +pub struct MappedPage { + pub kaddr: usize, + pub perm: MapPerm, +} + +pub trait PageTableTrait { + fn mmap(&mut self, uaddr: usize, kaddr: usize, perm: MapPerm); + fn mmap_paddr(&mut self, kaddr: usize, paddr: usize, perm: MapPerm); + fn mmap_replace(&mut self, uaddr: usize, kaddr: usize, perm: MapPerm); + fn mmap_replace_kaddr(&mut self, uaddr: usize, kaddr: usize); + fn mmap_replace_perm(&mut self, uaddr: usize, perm: MapPerm); + fn munmap(&mut self, uaddr: usize); + fn munmap_with_check(&mut self, uaddr: usize, expected_kaddr: usize) -> bool; + fn take_access_dirty_bit(&mut self, uaddr: usize) -> Option<(bool, bool)>; + + // fn mapped_page(&self, uaddr: usize) -> Option; + // fn munmap_if_mapped(&mut self, uaddr: usize) -> bool; + // fn is_mapped(&self, uaddr: usize) -> bool; +} + +pub trait ArchTrait { + fn init(); + + /* ----- Per-CPU Data ----- */ + fn set_percpu_data(data: usize); + fn get_percpu_data() -> usize; + + /* ----- Context Switching ----- */ + fn kernel_switch(from: *mut KernelContext, to: *mut KernelContext); + fn get_user_pc() -> usize; + fn return_to_user() -> !; + + /* ----- Interrupt ------ */ + fn wait_for_interrupt(); + fn enable_interrupt(); + fn disable_interrupt(); + fn enable_timer_interrupt(); + + fn get_kernel_stack_top() -> usize; + + fn kaddr_to_paddr(kaddr: usize) -> usize; + fn paddr_to_kaddr(paddr: usize) -> usize; + fn scan_device(); + fn map_kernel_addr(kstart: usize, pstart: usize, size: usize, perm: MapPerm); + unsafe fn unmap_kernel_addr(kstart: usize, size: usize); + + fn get_time_us() -> u64; + fn set_next_time_event_us(interval: u64); +} + +pub trait UserContextTrait: Clone { + fn new() -> Self; + + /// Create a clone of the current context for fork. The returned context + /// will return 0 in the user program. + fn new_clone(&self) -> Self; + + fn get_user_stack_top(&self) -> usize; + fn set_user_stack_top(&mut self, user_stack_top: usize); + fn set_kernel_stack_top(&mut self, kernel_stack_top: usize); + + fn set_addrspace(&mut self, addrspace: &crate::kernel::mm::AddrSpace); + + fn set_sigaction_restorer(&mut self, uptr_restorer: usize) -> &mut Self; + fn restore_from_signal(&mut self, sigcontext: &SigContext) -> &mut Self; + fn set_arg(&mut self, index: usize, arg: usize) -> &mut Self; + + fn set_user_entry(&mut self, entry: usize) -> &mut Self; + fn get_user_entry(&self) -> usize; + fn skip_syscall_instruction(&mut self); + fn set_tls(&mut self, tls: usize); +} + +pub struct Arch; diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 0000000..80ee867 --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,73 @@ +cfg_if::cfg_if! { + if #[cfg(target_arch = "riscv64")] { + mod riscv; + use riscv as arch_impl; + } else { + compile_error!("Unsupported architecture"); + } +} + +pub type UserContext = arch_impl::UserContext; +pub type KernelContext = arch_impl::KernelContext; +pub type SigContext = arch_impl::SigContext; +pub type PageTable = arch_impl::PageTable; +// pub type MappedPage<'a> = arch_impl::MappedPage<'a>; + +pub const PGSIZE: usize = arch_impl::PGSIZE; +pub const PGMASK: usize = arch_impl::PGMASK; +pub const TRAMPOLINE_BASE: usize = arch_impl::TRAMPOLINE_BASE; + +mod arch; +pub use arch::{PageTableTrait, UserContextTrait}; +use arch::{Arch, ArchTrait}; + +macro_rules! arch_export { + ($($func:ident($($arg:ident: $type:ty),*) -> $ret:ty);* $(;)?) => { + $( + pub fn $func($($arg: $type),*) -> $ret { + Arch::$func($($arg),*) + } + )* + }; +} + +use crate::kernel::mm::MapPerm; + +arch_export! { + init() -> (); + + /* ----- Per-CPU Data ----- */ + set_percpu_data(data: usize) -> (); + get_percpu_data() -> usize; + + /* ----- Context Switching ----- */ + kernel_switch(from: *mut KernelContext, to: *mut KernelContext) -> (); + get_user_pc() -> usize; + return_to_user() -> !; + + /* ----- Interrupt ------ */ + wait_for_interrupt() -> (); + enable_interrupt () -> (); + disable_interrupt () -> (); + enable_timer_interrupt() -> (); + + get_kernel_stack_top() -> usize; + + // kaddr_offset() -> usize; + kaddr_to_paddr(kaddr: usize) -> usize; + paddr_to_kaddr(paddr: usize) -> usize; + map_kernel_addr(kstart: usize, pstart: usize, size: usize, perm: MapPerm) -> (); + + get_time_us() -> u64; + set_next_time_event_us(internval: u64) -> (); + + scan_device() -> (); +} + +pub fn page_count(size: usize) -> usize { + (size + PGSIZE - 1) / PGSIZE +} + +pub unsafe fn unmap_kernel_addr(kstart: usize, size: usize) { + unsafe { Arch::unmap_kernel_addr(kstart, size) } +} diff --git a/src/arch/riscv/arch.rs b/src/arch/riscv/arch.rs new file mode 100644 index 0000000..346d460 --- /dev/null +++ b/src/arch/riscv/arch.rs @@ -0,0 +1,114 @@ +use alloc::sync::Arc; + +use crate::arch::riscv::{csr, load_device_tree, process, sbi_driver}; +use crate::arch::riscv::sbi_driver::{SBIConsoleDriver, SBIKPMU}; +use crate::arch::{Arch, ArchTrait, UserContextTrait}; +use crate::kernel::scheduler::current; +use crate::kernel::mm::MapPerm; +use crate::driver::chosen; +use crate::driver; + +use super::KernelContext; +use super::pagetable::kernelpagetable; +use super::csr::{Sstatus, SIE, stvec}; +use super::time_frequency; +use super::kernel_switch; +use super::sbi_driver::SBIKConsole; + +unsafe extern "C" { + static __riscv_copied_fdt: *const u8; + static __riscv_kaddr_offset: usize; +} + +impl ArchTrait for Arch { + fn init() { + unsafe extern "C" { + fn asm_kerneltrap_entry() -> !; + } + stvec::write(asm_kerneltrap_entry as usize); + kernelpagetable::init(); + + chosen::kconsole::register(&SBIKConsole); + chosen::kpmu::register(&SBIKPMU); + + driver::register_matched_driver(Arc::new(SBIConsoleDriver)); + } + + #[inline(always)] + fn set_percpu_data(data: usize) { + unsafe { core::arch::asm!("mv tp, {data}", data = in(reg) data) }; + } + + #[inline(always)] + fn get_percpu_data() -> usize { + let data: usize; + unsafe { core::arch::asm!("mv {data}, tp", data = out(reg) data) }; + data + } + + fn get_user_pc() -> usize { + current::tcb().user_context().get_user_entry() + } + + #[inline(always)] + fn return_to_user() -> ! { + process::traphandle::return_to_user(); + } + + #[inline(always)] + fn kernel_switch(from: *mut KernelContext, to: *mut KernelContext) { + kernel_switch(from, to); + } + + fn wait_for_interrupt() { + unsafe { core::arch::asm!("wfi") }; + } + + fn enable_interrupt() { + Sstatus::read().set_sie(true).write(); + } + + fn disable_interrupt() { + Sstatus::read().set_sie(false).write(); + } + + fn enable_timer_interrupt() { + SIE::read().set_stie(true).write(); + } + + fn get_kernel_stack_top() -> usize { + let sp; + unsafe { + core::arch::asm!("mv {}, sp", out(reg) sp); + } + sp + } + + fn scan_device() { + load_device_tree(unsafe { __riscv_copied_fdt }).unwrap(); + } + + fn kaddr_to_paddr(kaddr: usize) -> usize { + kaddr - unsafe { __riscv_kaddr_offset } + } + + fn paddr_to_kaddr(paddr: usize) -> usize { + paddr + unsafe { __riscv_kaddr_offset } + } + + fn map_kernel_addr(kstart: usize, pstart: usize, size: usize, perm: MapPerm) { + kernelpagetable::map_kernel_addr(kstart, pstart, size, perm); + } + + unsafe fn unmap_kernel_addr(kstart: usize, size: usize) { + unsafe { kernelpagetable::unmap_kernel_addr(kstart, size) }; + } + + fn get_time_us() -> u64 { + csr::time::read() * 1000000 / (time_frequency() as u64) + } + + fn set_next_time_event_us(interval: u64) { + sbi_driver::set_timer(csr::time::read() + interval); + } +} diff --git a/src/arch/riscv/csr/mod.rs b/src/arch/riscv/csr/mod.rs new file mode 100644 index 0000000..78ebf05 --- /dev/null +++ b/src/arch/riscv/csr/mod.rs @@ -0,0 +1,47 @@ +pub mod scause; + +mod sstatus; +mod sie; + +pub use sstatus::Sstatus; +pub use sie::SIE; + +pub mod sepc { + pub fn read() -> usize { + let value: usize; + unsafe { core::arch::asm!("csrr {}, sepc", out(reg) value); } + value + } + + pub fn write(value: usize) { + unsafe { core::arch::asm!("csrw sepc, {}", in(reg) value); } + } +} + +pub mod stvec { + pub fn write(value: usize) { + unsafe { core::arch::asm!("csrw stvec, {}", in(reg) value); } + } +} + +pub mod stval { + pub fn read() -> usize { + let value: usize; + unsafe { core::arch::asm!("csrr {}, stval", out(reg) value); } + value + } +} + +pub mod sscratch { + pub fn write(value: usize) { + unsafe { core::arch::asm!("csrw sscratch, {}", in(reg) value); } + } +} + +pub mod time { + pub fn read() -> u64 { + let value: usize; + unsafe { core::arch::asm!("csrr {}, time", out(reg) value); } + value as u64 + } +} diff --git a/src/arch/riscv/csr/scause.rs b/src/arch/riscv/csr/scause.rs new file mode 100644 index 0000000..66fb55c --- /dev/null +++ b/src/arch/riscv/csr/scause.rs @@ -0,0 +1,76 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Trap { + InstAddrMisaligned = 0, + InstAccessFault = 1, + IllegalInst = 2, + Breakpoint = 3, + LoadAddrMisaligned = 4, + LoadAccessFault = 5, + StoreAddrMisaligned = 6, + StoreAccessFault = 7, + EcallU = 8, + EcallS = 9, + EcallM = 11, + InstPageFault = 12, + LoadPageFault = 13, + StorePageFault = 15, + DoubleTrap = 16, + SoftwareCheck = 18, + HardwareError = 19, +} + +#[derive(Debug)] +pub enum Interrupt { + Software = 0, + Timer = 5, + External = 9, + Counter = 13, +} + +#[derive(Debug)] +pub enum Cause { + Trap(Trap), + Interrupt(Interrupt), +} + +pub fn read() -> usize { + let scause: usize; + unsafe { core::arch::asm!("csrr {}, scause", out(reg) scause); } + scause +} + +pub fn cause() -> Cause { + let scause = read(); + if scause & (1 << 63) == 0 { + Cause::Trap( + match scause & isize::MAX as usize { + 0 => Trap::InstAddrMisaligned, + 1 => Trap::InstAccessFault, + 2 => Trap::IllegalInst, + 3 => Trap::Breakpoint, + 4 => Trap::LoadAddrMisaligned, + 5 => Trap::LoadAccessFault, + 6 => Trap::StoreAddrMisaligned, + 7 => Trap::StoreAccessFault, + 8 => Trap::EcallU, + 9 => Trap::EcallS, + 11 => Trap::EcallM, + 12 => Trap::InstPageFault, + 13 => Trap::LoadPageFault, + 15 => Trap::StorePageFault, + 16 => Trap::DoubleTrap, + 18 => Trap::SoftwareCheck, + 19 => Trap::HardwareError, + _ => panic!("Unknown trap cause: {}", scause), + }) + } else { + match scause & 0x7fffffffffffffff { + 0 => Cause::Interrupt(Interrupt::Software), + 5 => Cause::Interrupt(Interrupt::Timer), + 9 => Cause::Interrupt(Interrupt::External), + 13 => Cause::Interrupt(Interrupt::Counter), + _ => panic!("Unknown interrupt cause: {}", scause), + + } + } +} diff --git a/src/arch/riscv/csr/sie.rs b/src/arch/riscv/csr/sie.rs new file mode 100644 index 0000000..6feab57 --- /dev/null +++ b/src/arch/riscv/csr/sie.rs @@ -0,0 +1,24 @@ +pub struct SIE { + sie: usize +} + +impl SIE { + pub fn read() -> Self { + let sie; + unsafe { core::arch::asm!("csrr {}, sie", out(reg) sie); } + SIE { sie } + } + + pub fn write(&self) { + unsafe { core::arch::asm!("csrw sie, {}", in(reg) self.sie); } + } + + pub fn set_stie(&mut self, stie: bool) -> &mut Self { + if stie { + self.sie |= 1 << 5; + } else { + self.sie &= !(1 << 5); + } + self + } +} diff --git a/src/arch/riscv/csr/sstatus.rs b/src/arch/riscv/csr/sstatus.rs new file mode 100644 index 0000000..2a7e27b --- /dev/null +++ b/src/arch/riscv/csr/sstatus.rs @@ -0,0 +1,44 @@ +pub struct Sstatus { + sstatus: usize, +} + +impl Sstatus { + pub fn read() -> Self { + let sstatus; + unsafe { core::arch::asm!("csrr {}, sstatus", out(reg) sstatus); } + Self { + sstatus + } + } + + pub fn write(&self) { + unsafe { core::arch::asm!("csrw sstatus, {}", in(reg) self.sstatus); } + } + + pub fn set_spie(&mut self, enable: bool) -> &mut Self { + if enable { + self.sstatus |= 1 << 5; + } else { + self.sstatus &= !(1 << 5); + } + self + } + + pub fn set_sie(&mut self, enable: bool) -> &mut Self { + if enable { + self.sstatus |= 1 << 1; + } else { + self.sstatus &= !(1 << 1); + } + self + } + + pub fn set_spp(&mut self, user: bool) -> &mut Self { + if user { + self.sstatus &= !(1 << 8); + } else { + self.sstatus |= 1 << 8; + } + self + } +} diff --git a/src/arch/riscv/fdt.rs b/src/arch/riscv/fdt.rs new file mode 100644 index 0000000..65b6dd0 --- /dev/null +++ b/src/arch/riscv/fdt.rs @@ -0,0 +1,90 @@ +use fdt::node::FdtNode; +use fdt::Fdt; +use alloc::vec::Vec; + +use crate::kernel::parse_boot_args; +use crate::driver::Device; +use crate::driver::found_device; +use crate::klib::initcell::InitedCell; +use crate::{kinfo, kwarn}; + +static TIME_FREQ: InitedCell = InitedCell::uninit(); +static SVADU_EXTENSION_ENABLED: InitedCell = InitedCell::uninit(); + +pub fn load_device_tree(fdt: *const u8) -> Result<(), ()> { + let data = unsafe { core::slice::from_raw_parts(fdt as *const u32, 2) }; + let magic = u32::from_be(data[0]); + if magic != 0xd00dfeed { + return Err(()); + } + + let total_size = u32::from_be(data[1]) as usize; + + let data: &'static [u8] = unsafe { core::slice::from_raw_parts(fdt, total_size) }; + + let fdt = Fdt::new(data).unwrap(); + + let cpu_node = fdt.find_node("/cpus").unwrap(); + let timebase_freq_prop = cpu_node.property("timebase-frequency").ok_or(())?; + timebase_freq_prop.as_usize().map(|freq| { + TIME_FREQ.init(freq as u32); + }); + + kinfo!("Init timebase frequency = {}Hz", *TIME_FREQ); + + let soc_node = fdt.find_node("/soc").unwrap(); + for child in soc_node.children() { + load_soc_node(&child); + } + + let cpu_node = fdt.find_node("/cpus").unwrap(); + load_cpu_node(&cpu_node.children().next().unwrap()); + + let chosen_node = fdt.find_node("/chosen").unwrap(); + if let Some(bootargs_prop) = chosen_node.property("bootargs") { + bootargs_prop.as_str().map(|bootargs| { + parse_boot_args(bootargs); + }); + } else { + kwarn!("No bootargs found in /chosen node"); + } + + kinfo!("Device Tree loaded successfully!"); + + Ok(()) +} + +fn load_soc_node(child: &FdtNode) -> Option<()> { + let reg_prop = child.reg(); + if let Some(mut reg) = reg_prop { + let reg = reg.next()?; + let addr = reg.starting_address as usize; + let size = reg.size? as usize; + + let compatible = child.compatible()?; + + let device = Device::new(addr, size, child.name, compatible.first()); + found_device(&device); + } + Some(()) +} + +fn load_cpu_node(child: &FdtNode) { + let isa_support = child.property("riscv,isa").and_then(|p| p.as_str()).unwrap_or(""); + let extensions: Vec<&str> = isa_support.split('_').collect(); + if extensions.iter().find(|&&ext| ext == "svadu").is_some() { + SVADU_EXTENSION_ENABLED.init(true); + kinfo!("SVADU extension is enabled"); + } else { + SVADU_EXTENSION_ENABLED.init(false); + kinfo!("SVADU extension is disabled"); + }; +} + +pub fn time_frequency() -> u32 { + *TIME_FREQ +} + +pub fn svadu_enable() -> bool { + *SVADU_EXTENSION_ENABLED +} diff --git a/src/arch/riscv/mod.rs b/src/arch/riscv/mod.rs new file mode 100644 index 0000000..229a1c0 --- /dev/null +++ b/src/arch/riscv/mod.rs @@ -0,0 +1,17 @@ +mod pagetable; +mod sbi_driver; +mod process; +mod fdt; +mod csr; +mod arch; + +pub use context::{UserContext, KernelContext, SigContext}; +pub use switch::kernel_switch; +pub use process::*; +pub use pagetable::*; +pub use fdt::{load_device_tree, time_frequency}; + +pub const PGBITS: usize = 12; // 4KB page size +pub const PGSIZE: usize = 1 << PGBITS; // 4096 bytes +pub const PGMASK: usize = PGSIZE - 1; // 0xfff +pub const TRAMPOLINE_BASE: usize = 0xffff_ffff_ffff_f000; diff --git a/src/arch/riscv/pagetable/kernelpagetable.rs b/src/arch/riscv/pagetable/kernelpagetable.rs new file mode 100644 index 0000000..16e57d3 --- /dev/null +++ b/src/arch/riscv/pagetable/kernelpagetable.rs @@ -0,0 +1,71 @@ +use crate::kernel::mm::MapPerm; +use crate::arch::riscv::{PGSIZE, TRAMPOLINE_BASE}; +use crate::arch::PageTableTrait; +use crate::klib::{InitedCell, SpinLock}; +use crate::kinfo; + +use super::pagetable::PageTable; + +unsafe extern "C" { + static __trampoline_start: u8; + static __riscv_kpgtable_root: usize; + static __riscv_kaddr_offset: usize; +} + +static KERNEL_PAGETABLE: InitedCell> = InitedCell::uninit(); +static KERNEL_SATP: InitedCell = InitedCell::uninit(); + +#[unsafe(link_section = ".text.init")] +pub fn init() { + kinfo!("root=0x{:x}, offset=0x{:x}", unsafe { __riscv_kpgtable_root }, core::ptr::addr_of!(__riscv_kaddr_offset) as usize); + let mut pagetable = PageTable::from_root(unsafe { __riscv_kpgtable_root }); + + pagetable.mmap( + TRAMPOLINE_BASE, + core::ptr::addr_of!(__trampoline_start) as usize, + MapPerm::R | MapPerm::X + ); + + KERNEL_SATP.init(pagetable.get_satp()); + KERNEL_PAGETABLE.init(SpinLock::new(pagetable)); +} + +pub fn map_kernel_addr(kstart: usize, pstart: usize, size: usize, perm: MapPerm) { + let mut kaddr = kstart; + let kend = kstart + size; + + let mut pagetable = KERNEL_PAGETABLE.lock(); + let mut paddr = pstart; + while kaddr < kend { + pagetable.mmap_kernel(kaddr, paddr, perm); + kaddr += PGSIZE; + paddr += PGSIZE; + } + + unsafe { + core::arch::asm!( + "sfence.vma zero, zero" + ) + } +} + +pub unsafe fn unmap_kernel_addr(kstart: usize, size: usize) { + let mut kaddr = kstart; + let kend = kstart + size; + + let mut pagetable = KERNEL_PAGETABLE.lock(); + while kaddr < kend { + pagetable.munmap(kaddr); + kaddr += PGSIZE; + } + + unsafe { + core::arch::asm!( + "sfence.vma zero, zero" + ) + } +} + +pub fn get_kernel_satp() -> usize { + *KERNEL_SATP +} \ No newline at end of file diff --git a/src/arch/riscv/pagetable/mod.rs b/src/arch/riscv/pagetable/mod.rs new file mode 100644 index 0000000..8a1ffb9 --- /dev/null +++ b/src/arch/riscv/pagetable/mod.rs @@ -0,0 +1,7 @@ +mod pte; +mod pagetable; +pub mod kernelpagetable; + +// pub use pagetable::{PageTable, MappedPage}; +pub use pagetable::PageTable; +pub use kernelpagetable::get_kernel_satp; \ No newline at end of file diff --git a/src/arch/riscv/pagetable/pagetable.rs b/src/arch/riscv/pagetable/pagetable.rs new file mode 100644 index 0000000..3dddf15 --- /dev/null +++ b/src/arch/riscv/pagetable/pagetable.rs @@ -0,0 +1,296 @@ +use crate::{kernel::mm::MapPerm}; +use crate::kernel::mm; +use crate::arch::PageTableTrait; + +use super::pte::{Addr, PTE, PTEFlags, PTETable}; + +const PAGE_TABLE_LEVELS: usize = 3; +const LEAF_LEVEL: usize = 2; + +pub trait PageAllocator { + fn alloc_zero() -> usize; +} + +// pub struct MappedPage<'a> { +// pte: PTE, +// _marker: core::marker::PhantomData<&'a ()>, +// } + +// impl<'a> MappedPage<'a> { +// pub fn page(&self) -> usize { +// self.pte.ppn().to_addr().kaddr() +// } + +// pub fn perm(&self) -> MapPerm { +// self.pte.flags().into() +// } + +// pub fn set_perm(&mut self, perm: MapPerm) { +// let flags: PTEFlags = perm.into(); +// self.pte.set_flags(flags); +// self.pte.write_back().expect("Failed to write back PTE"); +// } +// } + +pub struct PageTableImpls { + pub root: usize, + _marker: core::marker::PhantomData, +} + +impl PageTableImpls { + pub fn create(&mut self) { + debug_assert!(self.root == 0, "PageTable root should be zero when creating a new PageTable"); + + self.root = mm::page::alloc_zero(); + } + + pub fn from_root(root: usize) -> Self { + debug_assert!(root != 0, "PageTable root cannot be zero"); + Self { + root, + _marker: core::marker::PhantomData, + } + } + + pub fn find_pte(&self, vaddr: usize) -> Option { + self.find_pte_vpn(Addr::from_vaddr(vaddr).vpn()) + } + + fn find_pte_vpn(&self, vpn: [usize; PAGE_TABLE_LEVELS]) -> Option { + debug_assert!(self.root != 0); + let mut ptetable = PTETable::new(self.root as *mut usize); + + for level in 0..PAGE_TABLE_LEVELS { + let pte = ptetable.get(vpn[level]); + if !pte.is_valid() { + return None; + } + + if level == LEAF_LEVEL { + return Some(pte); + } + + ptetable = pte.next_level(); + } + + unreachable!("Page table traversal should always return before this point") + } + + /// find pte or create a new one if it doesn't exist + fn find_pte_or_create(&mut self, vaddr: usize) -> PTE { + self.find_pte_or_create_vpn(Addr::from_vaddr(vaddr).vpn()) + } + + fn find_pte_or_create_vpn(&mut self, vpn: [usize; PAGE_TABLE_LEVELS]) -> PTE { + debug_assert!(self.root != 0); + let mut ptetable = PTETable::new(self.root as *mut usize); + + for level in 0..PAGE_TABLE_LEVELS { + let mut pte = ptetable.get(vpn[level]); + + if level == LEAF_LEVEL { + return pte; + } + + if !pte.is_valid() { + // Create a new page table entry + let page = T::alloc_zero(); + let paddr = Addr::from_kaddr(page); + pte.set_ppn(paddr.ppn()); + pte.set_flags(PTEFlags::V); + ptetable.set(vpn[level], pte); + } + + ptetable = pte.next_level(); + } + + unreachable!("Page table traversal should always return before this point") + } + + fn free_pagetable(&mut self, ptetable: &PTETable, level: usize) { + if level != LEAF_LEVEL { + for i in 0..512 { + let pte = ptetable.get(i); + // debug_assert!(pte.ppn().value() << PGBITS <= 0x88000000, "PPN out of range: 0x{:x}, level={}, i={}", pte.ppn().value() << PGBITS, level, i); + if pte.is_valid() { + self.free_pagetable(&pte.next_level(), level + 1); + } + } + } + + ptetable.free(); + } + + pub fn get_satp(&self) -> usize { + const MODE_SV39: usize = 8; + let ppn = Addr::new(self.root as *const u8).ppn().value(); + (MODE_SV39 << 60) | ppn + } + + #[allow(dead_code)] + pub fn is_mapped(&self, uaddr: usize) -> bool { + self.find_pte(uaddr).is_some() + } + + pub fn mapped_flag(&self, uaddr: usize) -> Option { + self.find_pte(uaddr).map(|pte| pte.flags()) + } + + pub fn mmap_kernel(&mut self, kaddr: usize, paddr: usize, perm: MapPerm) { + let mut flags = perm.into(); + flags = flags | PTEFlags::A | PTEFlags::D; + + let mut pte = self.find_pte_or_create(kaddr); + + pte.set_flags(flags); + pte.set_ppn(Addr::from_paddr(paddr).ppn()); + pte.write_back().expect("Failed to write back PTE"); + } + + // pub fn mapped_page(&self, uaddr: usize) -> Option> { + // if let Some(pte) = self.find_pte(uaddr) { + // Some(MappedPage { pte, _marker: core::marker::PhantomData }) + // } else { + // None + // } + // } + + pub fn mark_page_accessed(&mut self, uaddr: usize) -> bool { + if let Some(mut pte) = self.find_pte(uaddr) { + let flags = pte.flags(); + if !flags.contains(PTEFlags::A) { + pte.set_flags(flags | PTEFlags::A); + pte.write_back().expect("Failed to write back PTE when marking page accessed"); + return true; + } + } + false + } + + pub fn mark_page_dirty(&mut self, uaddr: usize) -> bool { + if let Some(mut pte) = self.find_pte(uaddr) { + let flags = pte.flags(); + if !flags.contains(PTEFlags::D) { + pte.set_flags(flags | PTEFlags::D); + pte.write_back().expect("Failed to write back PTE when marking page dirty"); + return true; + } + } + false + } +} + +impl Drop for PageTableImpls { + fn drop(&mut self) { + self.free_pagetable(&PTETable::new(self.root as *mut usize), 0); + self.root = 0; // Clear the root pointer to avoid double free + } +} + +unsafe impl Send for PageTableImpls {} +unsafe impl Sync for PageTableImpls {} + +impl PageTableTrait for PageTableImpls { + fn mmap(&mut self, uaddr: usize, kaddr: usize, perm: MapPerm) { + let flags = perm.into(); + + let mut pte = self.find_pte_or_create(uaddr); + debug_assert!(!pte.is_valid(), "PTE should NOT be valid before mmap, uaddr= {:#x}, kaddr = {:#x}", uaddr, kaddr); + + pte.set_flags(flags); + pte.set_ppn(Addr::from_kaddr(kaddr).ppn()); + pte.write_back().expect("Failed to write back PTE"); + } + + fn mmap_paddr(&mut self, kaddr: usize, paddr: usize, perm: MapPerm) { + let flags = perm.into(); + + let mut pte = self.find_pte_or_create(kaddr); + pte.set_flags(flags); + pte.set_ppn(Addr::from_paddr(paddr).ppn()); + pte.write_back().expect("Failed to write back PTE"); + } + + fn mmap_replace(&mut self, uaddr: usize, kaddr: usize, perm: MapPerm) { + let flags = perm.into(); + + let mut pte = self.find_pte_or_create(uaddr); + pte.set_flags(flags); + pte.set_ppn(Addr::from_kaddr(kaddr).ppn()); + pte.write_back().expect("Failed to write back PTE"); + } + + fn mmap_replace_kaddr(&mut self, uaddr: usize, kaddr: usize) { + let mut pte = self.find_pte_or_create(uaddr); + pte.set_ppn(Addr::from_kaddr(kaddr).ppn()); + pte.write_back().expect("Failed to write back PTE"); + } + + fn mmap_replace_perm(&mut self, uaddr: usize, perm: MapPerm) { + let flags = perm.into(); + + let mut pte = self.find_pte_or_create(uaddr); + pte.set_flags(flags); + pte.write_back().expect("Failed to write back PTE"); + } + + fn munmap(&mut self, vaddr: usize) { + let mut pte = self.find_pte(vaddr).expect("PTE not found for munmap"); + pte.set_flags(PTEFlags::empty()); + pte.write_back().expect("Failed to write back PTE for munmap"); + } + + fn munmap_with_check(&mut self, uaddr: usize, kaddr: usize) -> bool { + if let Some(mut pte) = self.find_pte(uaddr) { + // Using atomic opearation is unnessary here, + // because pagetable is write-locked during munmap_with_check. + if pte.ppn().to_addr().kaddr() == kaddr { + pte.set_flags(PTEFlags::empty()).write_back().expect("Failed to write back PTE for munmap_with_check"); + return true; + } else { + return false; + } + } else { + false + } + } + + fn take_access_dirty_bit(&mut self, uaddr: usize) -> Option<(bool, bool)> { + self.find_pte(uaddr).map(|mut pte| { + let flags = pte.flags(); + let accessed = flags.contains(PTEFlags::A); + let dirty = flags.contains(PTEFlags::D); + pte.set_flags(flags.difference(PTEFlags::A | PTEFlags::D)).write_back().expect("Failed to write back PTE when taking access and dirty bits"); + (accessed, dirty) + }) + } + + // fn mapped_page(&self, uaddr: usize) -> Option { + // if let Some(pte) = self.find_pte(uaddr) { + // let kaddr = pte.ppn().to_addr().kaddr(); + // let perm: MapPerm = pte.flags().into(); + // Some(crate::arch::arch::MappedPage { kaddr, perm }) + // } else { + // None + // } + // } +} + +pub struct NormalPageAllocator; + +impl PageAllocator for NormalPageAllocator { + fn alloc_zero() -> usize { + mm::page::alloc_zero() + } +} + +pub type PageTable = PageTableImpls; + +impl PageTable { + pub const fn new() -> Self { + Self { + root: 0, + _marker: core::marker::PhantomData, + } + } +} diff --git a/src/arch/riscv/pagetable/pte.rs b/src/arch/riscv/pagetable/pte.rs new file mode 100644 index 0000000..dd278b3 --- /dev/null +++ b/src/arch/riscv/pagetable/pte.rs @@ -0,0 +1,248 @@ +use bitflags::bitflags; +use core::ptr::NonNull; +use core::fmt; + +use crate::kernel::mm::MapPerm; +use crate::kernel::mm; +use crate::arch::riscv::{PGBITS, PGMASK}; +use crate::arch::{kaddr_to_paddr, paddr_to_kaddr}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Addr(usize); + +impl Addr { + pub fn new(addr: *const T) -> Self { + Addr(addr as usize) + } + + pub fn from_paddr(paddr: usize) -> Self { + // Addr(paddr + kaddr_offset()) + Addr(paddr_to_kaddr(paddr)) + } + + pub const fn from_kaddr(kaddr: usize) -> Self { + Addr(kaddr) + } + + pub const fn from_vaddr(vaddr: usize) -> Self { + Addr(vaddr) + } + + pub fn paddr(self) -> usize { + kaddr_to_paddr(self.0) + // self.0 - kaddr_offset() + } + + pub const fn kaddr(self) -> usize { + self.0 + } + + pub const fn vaddr(self) -> usize { + self.0 + } + + pub const fn pgoff(self) -> usize { + self.0 & PGMASK + } + + pub const fn vpn(self) -> [usize; 3] { + [ + (self.0 >> 30) & 0x1ff, + (self.0 >> 21) & 0x1ff, + (self.0 >> 12) & 0x1ff, + ] + } + + pub fn ppn(self) -> PPN { + // debug_assert!(self.paddr() <= 0x88000000, "PPN out of range: 0x{:x}", self.paddr()); + PPN::new(self.paddr() >> PGBITS) + } + + pub const fn ptr(self) -> *mut usize { + self.0 as *mut usize + } +} + +impl fmt::Display for Addr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:x}", self.0) + } +} + +impl From for *mut usize { + fn from(addr: Addr) -> Self { + addr.ptr() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct PPN { + ppn: usize +} + +impl PPN { + pub fn new(ppn: usize) -> Self { + PPN { ppn } + } + + pub const fn value(self) -> usize { + self.ppn + } + + pub fn to_paddr(self) -> usize { + // debug_assert!((self.ppn << PGBITS) < 0x88000000, "PPN out of range: 0x{:x}", self.ppn); + self.ppn << PGBITS + } + + pub const fn from_paddr(paddr: usize) -> Self { + PPN { ppn: paddr >> PGBITS } + } + + pub fn to_addr(self) -> Addr { + Addr::from_paddr(self.to_paddr()) + } +} + +impl fmt::Display for PPN { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PPN(0x{:x})", self.ppn) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct PTE { + pte: usize, + ptr: Option>, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct PTEFlags: u16 { + const V = 1 << 0; // Valid bit + const R = 1 << 1; // Readable + const W = 1 << 2; // Writable + const X = 1 << 3; // Executable + const U = 1 << 4; // User-accessible + const G = 1 << 5; // Global page + const A = 1 << 6; // Accessed bit + const D = 1 << 7; // Dirty bit + } +} + +impl From for PTEFlags { + fn from(perm: MapPerm) -> Self { + let mut flags = PTEFlags::V; + if perm.contains(MapPerm::R) { flags |= PTEFlags::R; } + if perm.contains(MapPerm::W) { flags |= PTEFlags::W; } + if perm.contains(MapPerm::X) { flags |= PTEFlags::X; } + if perm.contains(MapPerm::U) { flags |= PTEFlags::U; } + flags + } +} + +impl Into for PTEFlags { + fn into(self) -> MapPerm { + let mut perm = MapPerm::empty(); + if self.contains(PTEFlags::R) { perm |= MapPerm::R; } + if self.contains(PTEFlags::W) { perm |= MapPerm::W; } + if self.contains(PTEFlags::X) { perm |= MapPerm::X; } + if self.contains(PTEFlags::U) { perm |= MapPerm::U; } + perm + } +} + +impl PTE { + pub fn from_ptr(ptr: NonNull) -> Self { + let pte = unsafe { ptr.read() }; + Self { + pte, + ptr: Some(ptr), + } + } + + pub fn from_raw_ptr(ptr: *mut usize) -> Self { + Self::from_ptr(NonNull::new(ptr).expect("PTE pointer cannot be null")) + } + + pub const fn flags(&self) -> PTEFlags { + PTEFlags::from_bits_truncate((self.pte & 0x1ff) as u16) + } + + pub fn set_flags(&mut self, flags: PTEFlags) -> &mut Self { + self.pte = (self.pte & !0x1ff) | (flags.bits() as usize); + self + } + + pub fn ppn(self) -> PPN { + PPN::new((self.pte >> 10) & ((1 << 44) - 1)) + } + + pub fn set_ppn(&mut self, ppn: PPN) -> &mut Self { + self.pte = (self.pte & !(((1 << 44) - 1) << 10)) | (ppn.value() << 10); + self + } + + pub fn next_level(&self) -> PTETable { + debug_assert!(self.page() as usize != 0); + PTETable::new(self.page()) + } + + pub fn page(&self) -> *mut usize { + self.ppn().to_addr().ptr() + } + + pub fn write_back(&self) -> Result<(), ()> { + match self.ptr { + Some(ptr) => { + unsafe { ptr.write(self.pte) }; + Ok(()) + } + None => Err(()), + } + } + + pub const fn is_valid(self) -> bool { + self.flags().contains(PTEFlags::V) + } +} + +impl fmt::Display for PTE { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PTE({:#x}, {})", self.pte, self.ppn()) + } +} + +pub struct PTETable { + base: *mut usize +} + +impl PTETable { + pub fn new(base: *mut usize) -> Self { + debug_assert!(base as usize != 0); + PTETable { base } + } + + pub fn get(&self, index: usize) -> PTE { + PTE::from_raw_ptr(unsafe { self.base.add(index) }) + } + + pub fn set(&mut self, index: usize, pte: PTE) { + unsafe { self.base.add(index).write(pte.pte) }; + } + + pub fn free(&self) { + mm::page::free(self.base as usize); + } +} + +impl From<*mut usize> for PTETable { + fn from(base: *mut usize) -> Self { + PTETable::new(base) + } +} + +impl Into for PTE { + fn into(self) -> PTETable { + PTETable::new(self.ppn().to_addr().ptr() as *mut usize) + } +} diff --git a/src/arch/riscv/process/context.rs b/src/arch/riscv/process/context.rs new file mode 100644 index 0000000..75aad7d --- /dev/null +++ b/src/arch/riscv/process/context.rs @@ -0,0 +1,166 @@ +use crate::arch::arch::UserContextTrait; +use crate::arch::riscv::pagetable::get_kernel_satp; +use crate::arch::riscv::process::traphandle::{usertrap_handler, return_to_user}; +use crate::kernel::mm::AddrSpace; +use crate::kernel::scheduler::KernelStack; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct UserContext { + /* 0 */ pub gpr: [usize; 32], + /* 32 */ pub kernel_tp: usize, + /* 33 */ pub kernel_sp: usize, + /* 34 */ pub user_satp: usize, + /* 35 */ pub kernel_satp: usize, + /* 36 */ pub usertrap_handler: usize, + pub user_entry: usize, // User program entry point +} + +impl UserContextTrait for UserContext { + fn new() -> Self { + let kernel_satp = get_kernel_satp(); + + UserContext { + gpr: [0; 32], + kernel_tp: 0, + kernel_sp: 0, + user_satp: 0, + kernel_satp, + usertrap_handler: usertrap_handler as usize, + user_entry: 0, + } + } + + fn new_clone(&self) -> Self { + let mut new_context = self.clone(); + new_context.kernel_sp = 0; // Reset kernel stack pointer + new_context.user_satp = 0; // Reset user address space pointer + new_context.kernel_tp = 0; // Reset kernel thread pointer + + new_context.gpr[10] = 0; // clone returns 0 to the child process + + new_context + } + + fn get_user_stack_top(&self) -> usize { + self.gpr[2] // sp + } + + fn set_user_stack_top(&mut self, user_stack_top: usize) { + self.gpr[2] = user_stack_top; + } + + fn set_kernel_stack_top(&mut self, kernel_stack_top: usize) { + self.kernel_sp = kernel_stack_top; + } + + fn set_addrspace(&mut self, addrspace: &AddrSpace) { + addrspace.with_pagetable(|pagetable| { + self.user_satp = pagetable.get_satp(); + }); + } + + fn set_sigaction_restorer(&mut self, uptr_restorer: usize) -> &mut Self { + self.gpr[1] = uptr_restorer; // ra + self + } + + fn set_arg(&mut self, index: usize, arg: usize) -> &mut Self { + debug_assert!(index <= 7); + self.gpr[10 + index] = arg; + self + } + + fn restore_from_signal(&mut self, sigcontext: &SigContext) -> &mut Self { + self.gpr[1..32].copy_from_slice(&sigcontext.gregs); + self.user_entry = sigcontext.pc; + self + } + + fn get_user_entry(&self) -> usize { + self.user_entry + } + + fn set_user_entry(&mut self, entry: usize) -> &mut Self { + self.user_entry = entry; + self + } + + fn skip_syscall_instruction(&mut self) { + self.user_entry += 4; // Skip ecall instruction + } + + fn set_tls(&mut self, tls: usize) { + self.gpr[4] = tls; // tp + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct KernelContext { + pub ra: usize, + pub sp: usize, + pub s : [usize; 12], + pub a0: usize, +} + +impl KernelContext { + pub fn new(kernel_stack: &KernelStack) -> Self { + KernelContext { + ra: return_to_user as usize, + sp: kernel_stack.get_top(), + s: [0; 12], + a0: 0, + } + } + + pub fn new_idle() -> Self { + KernelContext { + ra: 0, + sp: 0, + s : [0; 12], + a0: 0, + } + } + + pub fn set_entry(&mut self, entry: usize) { + self.ra = entry; + } +} + +#[repr(C)] +#[repr(align(16))] +#[derive(Clone, Copy, Debug)] +pub struct SigContext { + pub pc: usize, + pub gregs: [usize; 31], // General registers + pub fpregs: [u64; 66] // Floating point registers +} + +impl SigContext { + pub fn empty() -> Self { + SigContext { + pc: 0, + gregs: [0; 31], + fpregs: [0; 66], + } + } +} + +impl Into for UserContext { + fn into(self) -> SigContext { + let mut gregs: [usize; 31] = [0; 31]; + gregs.copy_from_slice(&self.gpr[1..32]); + SigContext { + pc: self.user_entry, + gregs, + fpregs: [0; 66], // Placeholder, actual FPU state handling needed + } + } +} + +unsafe impl Send for UserContext {} +unsafe impl Sync for UserContext {} + +unsafe impl Send for KernelContext {} +unsafe impl Sync for KernelContext {} diff --git a/src/arch/riscv/process/mod.rs b/src/arch/riscv/process/mod.rs new file mode 100644 index 0000000..f929958 --- /dev/null +++ b/src/arch/riscv/process/mod.rs @@ -0,0 +1,3 @@ +pub mod context; +pub mod switch; +pub mod traphandle; diff --git a/src/arch/riscv/process/switch.rs b/src/arch/riscv/process/switch.rs new file mode 100644 index 0000000..5e6aaca --- /dev/null +++ b/src/arch/riscv/process/switch.rs @@ -0,0 +1,11 @@ +use crate::arch::KernelContext; + +unsafe extern "C" { + fn asm_kernel_switch(from: *mut KernelContext, to: *mut KernelContext); +} + +pub fn kernel_switch(from: *mut KernelContext, to: *mut KernelContext) { + unsafe { + asm_kernel_switch(from, to); + } +} diff --git a/src/arch/riscv/process/traphandle.rs b/src/arch/riscv/process/traphandle.rs new file mode 100644 index 0000000..1a8df1d --- /dev/null +++ b/src/arch/riscv/process/traphandle.rs @@ -0,0 +1,196 @@ +use crate::arch::riscv::fdt::svadu_enable; +use crate::kernel::mm::MemAccessType; +use crate::kernel::scheduler::current; +use crate::kernel::trap; +use crate::kernel::syscall; +use crate::arch::riscv::csr::*; +use crate::arch::riscv::UserContext; +use crate::arch::riscv::TRAMPOLINE_BASE; +use crate::arch::UserContextTrait; +use crate::kinfo; + +unsafe extern "C" { + fn asm_usertrap_entry (user_context: *mut UserContext) -> !; + fn asm_usertrap_return(user_context: *const UserContext) -> !; +} + +fn handle_syscall() { + let tcb = current::tcb(); + + tcb.with_user_context_mut(|user_context|{ + let syscall_args: syscall::Args = [ + user_context.gpr[10], // a0 + user_context.gpr[11], // a1 + user_context.gpr[12], // a2 + user_context.gpr[13], // a3 + user_context.gpr[14], // a4 + user_context.gpr[15], // a5 + user_context.gpr[16], // a6 + ]; + + let syscall_num = user_context.gpr[17]; // a7 + + user_context.gpr[10] = trap::syscall(syscall_num, &syscall_args) as usize; + }); +} + +fn svadu_mark_page_accessed(uaddr: usize) -> bool { + let mut pagetable = current::addrspace().pagetable().write(); + pagetable.mark_page_accessed(uaddr) +} + +fn svadu_mark_page_dirty(uaddr: usize) -> bool { + let mut pagetable = current::addrspace().pagetable().write(); + pagetable.mark_page_dirty(uaddr) +} + +unsafe extern "C" { + fn asm_kerneltrap_entry() -> !; +} + +pub fn usertrap_handler() -> ! { + stvec::write(asm_kerneltrap_entry as usize); + current::tcb().user_context().set_user_entry(sepc::read()); + + trap::trap_enter(); + + match scause::cause() { + scause::Cause::Trap(trap) => { + match trap { + scause::Trap::EcallU => handle_syscall(), + scause::Trap::InstPageFault => { + let addr = stval::read(); + if svadu_enable() || !svadu_mark_page_accessed(addr) { + trap::memory_fault(addr, MemAccessType::Execute); + } + }, + scause::Trap::LoadPageFault => { + let addr = stval::read(); + if svadu_enable() || !svadu_mark_page_accessed(addr) { + trap::memory_fault(addr, MemAccessType::Read); + } + }, + scause::Trap::StorePageFault => { + let addr = stval::read(); + if svadu_enable() || !svadu_mark_page_dirty(addr) || !svadu_mark_page_dirty(addr) { + trap::memory_fault(addr, MemAccessType::Write); + } + }, + scause::Trap::IllegalInst => { + trap::illegal_inst(); + } + scause::Trap::InstAddrMisaligned | scause::Trap::LoadAddrMisaligned | scause::Trap::StoreAddrMisaligned => { + trap::memory_misaligned(); + } + _ => { + let inst: u32 = current::addrspace().copy_from_user(sepc::read()).unwrap(); + panic!("Unhandled user trap: {:?}, sepc={:#x}, stval={:#x}, stinst={:#x}, cause={:?}", trap, sepc::read(), stval::read(), inst, scause::cause()); + } + } + }, + + scause::Cause::Interrupt(interrupt) => { + match interrupt { + scause::Interrupt::Software => { + kinfo!("Software interrupt occurred"); + }, + scause::Interrupt::Timer => { + // kinfo!("Timer interrupt occurred"); + trap::timer_interrupt(); + }, + scause::Interrupt::External => { + kinfo!("External interrupt occurred"); + }, + scause::Interrupt::Counter => { + kinfo!("Counter interrupt occurred"); + }, + } + }, + } + + return_to_user(); +} + +fn usertrap_return(user_context: *const UserContext) -> ! { + let trampoline_usertrap_return = + (TRAMPOLINE_BASE + (asm_usertrap_return as usize - asm_usertrap_entry as usize)) + as usize; + + unsafe { + core::arch::asm!( + "jr {target}", + target = in(reg) trampoline_usertrap_return, + in("a0") user_context, + options(noreturn) + ); + } +} + +pub fn return_to_user() -> ! { + trap::trap_return(); + + let tcb = current::tcb(); + + sepc::write(tcb.user_context().get_user_entry()); + stvec::write(TRAMPOLINE_BASE); + sscratch::write(tcb.get_user_context_uaddr()); + + Sstatus::read() + .set_spie(true) // Enable interrupts in user mode + .set_spp(true) // Set previous mode to user + .write(); + + let user_context_ptr = tcb.get_user_context_ptr(); + + // ktrace!("Return to user mode: entry={:#x}, user_context={:#x}", tcb.user_context().get_user_entry(), user_context_ptr as usize); + + usertrap_return(user_context_ptr); +} + +#[unsafe(no_mangle)] +pub fn kerneltrap_handler() { + let sepc = sepc::read(); + + match scause::cause() { + scause::Cause::Trap(trap) => { + match trap { + scause::Trap::StorePageFault => { + let stval = stval::read(); + let task = current::task(); + let kstack = task.kstack(); + if kstack.check_stack_overflow(stval) { + panic!("Kernel stack overflow detected at address: {:#x}, tid={}", stval, current::tid()); + } else { + panic!("Kernel page fault at address: {:#x}, sepc={:#x}, cause={:?}, kstack_top={:#x}", stval, sepc, trap, kstack.get_top()); + } + } + _ => { + panic!("Unhandled kernel trap: {:?}, sepc={:#x}, stval={:#x}, cause={:?}", trap, sepc, stval::read(), scause::cause()); + } + } + }, + + scause::Cause::Interrupt(interrupt) => { + match interrupt { + scause::Interrupt::Software => { + kinfo!("Kernel software interrupt occurred"); + }, + scause::Interrupt::Timer => { + // kinfo!("Kernel timer interrupt occurred"); + trap::timer_interrupt(); + }, + scause::Interrupt::External => { + kinfo!("Kernel external interrupt occurred"); + }, + scause::Interrupt::Counter => { + kinfo!("Kernel counter interrupt occurred"); + }, + } + }, + + } + + Sstatus::read().set_spp(false).write(); // Set previous mode to supervisor + + sepc::write(sepc); +} diff --git a/src/arch/riscv/sbi_driver/char.rs b/src/arch/riscv/sbi_driver/char.rs new file mode 100644 index 0000000..5f5defa --- /dev/null +++ b/src/arch/riscv/sbi_driver/char.rs @@ -0,0 +1,56 @@ +use alloc::string::String; +use alloc::sync::Arc; + +use crate::kernel::errno::SysResult; +use crate::kernel::event::{PollEvent, PollEventSet}; +use crate::driver::{CharDriverOps, DeviceType, DriverOps}; +use crate::driver::chosen::kconsole::KConsole; + +use super::sbi; + +pub struct SBIConsoleDriver; + +impl DriverOps for SBIConsoleDriver { + fn name(&self) -> &str { + "sbi-console" + } + + fn device_name(&self) -> String { + "sbi-console".into() + } + + fn device_type(&self) -> DeviceType { + DeviceType::Char + } + + fn as_char_driver(self: Arc) -> alloc::sync::Arc { + self + } +} + +impl CharDriverOps for SBIConsoleDriver { + fn putchar(&self, c: u8) { + sbi::putchar(c); + } + + fn getchar(&self) -> Option { + None + } + + fn poll(&self, _waker: usize, _event: PollEventSet) -> SysResult> { + // unimplemented!() + Ok(None) + } + + fn poll_cancel(&self) { + unimplemented!() + } +} + +pub struct SBIKConsole; + +impl KConsole for SBIKConsole { + fn kputs(&self, s: &str) { + s.bytes().for_each(sbi::putchar); + } +} diff --git a/src/arch/riscv/sbi_driver/mod.rs b/src/arch/riscv/sbi_driver/mod.rs new file mode 100644 index 0000000..ca8713b --- /dev/null +++ b/src/arch/riscv/sbi_driver/mod.rs @@ -0,0 +1,8 @@ +mod char; +mod pmu; +mod sbi; + +pub use char::*; +pub use pmu::*; + +pub use sbi::*; diff --git a/src/arch/riscv/sbi_driver/pmu.rs b/src/arch/riscv/sbi_driver/pmu.rs new file mode 100644 index 0000000..1b82454 --- /dev/null +++ b/src/arch/riscv/sbi_driver/pmu.rs @@ -0,0 +1,20 @@ +use crate::driver::chosen::kpmu::KPMU; +use crate::driver::PMUDriverOps; + +use super::sbi; + +pub struct SBIPMUDriver; + +impl PMUDriverOps for SBIPMUDriver { + fn shutdown(&self) -> ! { + sbi::shutdown(); + } +} + +pub struct SBIKPMU; + +impl KPMU for SBIKPMU { + fn shutdown(&self) -> ! { + sbi::shutdown(); + } +} diff --git a/src/arch/riscv/sbi_driver/sbi.rs b/src/arch/riscv/sbi_driver/sbi.rs new file mode 100644 index 0000000..3adbfd4 --- /dev/null +++ b/src/arch/riscv/sbi_driver/sbi.rs @@ -0,0 +1,46 @@ +struct SBIRet { + _error: usize, + _value: usize, +} + +fn sbi_call(fid: usize, eid: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) -> SBIRet { + let mut error; + let mut value; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") arg0 => error, + inlateout("a1") arg1 => value, + in("a2") arg2, + in("a3") arg3, + in("a4") arg4, + in("a5") arg5, + in("a6") fid, + in("a7") eid, + options(nostack, preserves_flags) + ); + } + SBIRet { + _error: error, + _value: value, + } +} + +pub fn shutdown() -> ! { + sbi_call(0x0, 0x8, 0, 0, 0, 0, 0, 0); + + loop { + unsafe { + core::arch::asm!("wfi"); + } + } +} + +pub fn putchar(c: u8) -> () { + sbi_call(0x0, 0x1, c as usize, 0, 0, 0, 0, 0); +} + +pub fn set_timer(time: u64) { + sbi_call(0x0, 0x0, time as usize, (time >> 32) as usize, 0, 0, 0, 0); +} diff --git a/src/driver/block/mod.rs b/src/driver/block/mod.rs new file mode 100644 index 0000000..1925040 --- /dev/null +++ b/src/driver/block/mod.rs @@ -0,0 +1,4 @@ +mod virtio; + +pub use virtio::*; +pub mod starfive_sdio; diff --git a/src/driver/block/starfive_sdio/device.rs b/src/driver/block/starfive_sdio/device.rs new file mode 100644 index 0000000..655b660 --- /dev/null +++ b/src/driver/block/starfive_sdio/device.rs @@ -0,0 +1,39 @@ +use alloc::string::String; +use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::Mutex; + +use crate::driver::block::{BlockDevice, BlockDriver}; + +use super::driver::EMMCDriver; + +const BLOCK_SIZE: usize = 512; + +pub struct EMMCDeviceInner { + base: usize, +} + +impl EMMCDeviceInner { + pub fn new(base: usize) -> Self { + Self { base } + } + + pub fn read_block(&mut self, block: usize, buf: &mut [u8]) -> Result<(), ()> { + assert!(buf.len() == 512); + Ok(()) + } +} + +pub struct EMMCDevice { + inner: Arc>, +} + +impl BlockDevice for EMMCDevice { + fn name(&self) -> &str { + "emmc" + } + + fn driver(&self) -> Box { + Box::new(EMMCDriver::new(&self.inner)) + } +} diff --git a/src/driver/block/starfive_sdio/driver/cmd.rs b/src/driver/block/starfive_sdio/driver/cmd.rs new file mode 100644 index 0000000..37c401d --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/cmd.rs @@ -0,0 +1,72 @@ +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Copy, Clone)] +pub enum Cmd { + GoIdleState, + AllSendCid, + SendRelativeAddr, + SetDSR, + SelectCard, + SendIfCond, + SendCsd, + SendCid, + StopTransmission, + SendStatus, + GoInactiveState, + SetBlockLen, + ReadSingleBlock, + ReadMultipleBlock, + WriteSingleBlock, + WriteMultipleBlock, + EraseWrBlkStart, + EraseWrBlkEnd, + Erase, + AppCmd, + GenCmd, + SetBusWidth, + SdStatus, + SendNumWrBlocks, + SetWrBlkEraseCnt, + SdSendOpCond, + SetClrCardDetect, + SendScr, + // Private + ResetClock, +} + +impl From for u8 { + fn from(val: Cmd) -> Self { + match val { + Cmd::GoIdleState => 0, + Cmd::AllSendCid => 2, + Cmd::SendRelativeAddr => 3, + Cmd::SetDSR => 4, + Cmd::SelectCard => 7, + Cmd::SendIfCond => 8, + Cmd::SendCsd => 9, + Cmd::SendCid => 10, + Cmd::StopTransmission => 12, + Cmd::SendStatus => 13, + Cmd::GoInactiveState => 15, + Cmd::SetBlockLen => 16, + Cmd::ReadSingleBlock => 17, + Cmd::ReadMultipleBlock => 18, + Cmd::WriteSingleBlock => 24, + Cmd::WriteMultipleBlock => 25, + Cmd::EraseWrBlkStart => 32, + Cmd::EraseWrBlkEnd => 33, + Cmd::Erase => 38, + Cmd::AppCmd => 55, + Cmd::GenCmd => 56, + Cmd::SetBusWidth => 6, + Cmd::SdStatus => 13, + Cmd::SendNumWrBlocks => 22, + Cmd::SetWrBlkEraseCnt => 23, + Cmd::SdSendOpCond => 41, + Cmd::SetClrCardDetect => 42, + Cmd::SendScr => 51, + _ => { + panic!("Not implemented for cmd {:?}", val); + } + } + } +} diff --git a/src/driver/block/starfive_sdio/driver/driver.rs b/src/driver/block/starfive_sdio/driver/driver.rs new file mode 100644 index 0000000..2a892e0 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/driver.rs @@ -0,0 +1,118 @@ +use core::time::Duration; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::format; +use visionfive2_sd::{Vf2SdDriver, SDIo, SleepOps}; + +use crate::driver::{BlockDriverOps, DeviceType, DriverOps}; +use crate::kernel::event::timer; +use crate::klib::SpinLock; + +struct SDIOImpls { + pub base: usize +} + +impl SDIo for SDIOImpls { + fn read_reg_at(&self, offset: usize) -> u32 { + let addr = (self.base + offset) as *const u32; + unsafe { addr.read_volatile() } + } + + fn write_reg_at(&mut self, offset: usize, val: u32) { + let addr = (self.base + offset) as *mut u32; + unsafe { + addr.write_volatile(val); + } + } + + fn read_data_at(&self, offset: usize) -> u64 { + let addr = (self.base + offset) as *const u64; + unsafe { addr.read_volatile() } + } + + fn write_data_at(&mut self, offset: usize, val: u64) { + let addr = (self.base + offset) as *mut u64; + unsafe { + addr.write_volatile(val); + } + } +} + +struct SleepOpsImpls; + +impl SleepOps for SleepOpsImpls { + fn sleep_ms(ms: usize) { + timer::spin_delay(Duration::from_millis(ms as u64)); + } + + fn sleep_ms_until(ms: usize, f: impl FnMut() -> bool) { + timer::wait_until(Duration::from_millis(ms as u64), f); + } +} + +pub struct Driver { + base: usize, + num: i32, + inner: SpinLock> +} + +impl Driver { + pub fn new(num: i32, base: usize) -> Self { + let inner = Vf2SdDriver::new(SDIOImpls { base }); + Driver { + num, + base, + inner: SpinLock::new(inner) + } + } + + pub fn init(&self) -> Result<(), ()> { + if self.base != 0x16020000 { + return Err(()); + } + + self.inner.lock().init(); + + Ok(()) + } +} + +impl DriverOps for Driver { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn name(&self) -> &str { + "starfive_sdio" + } + + fn device_name(&self) -> String { + format!("sdio{}", self.num) + } + + fn as_block_driver(self: Arc) -> Arc { + self + } +} + +impl BlockDriverOps for Driver { + fn read_block(&self, block: usize, buf: &mut [u8]) -> Result<(), ()> { + self.inner.lock().read_block(block, buf); + + Ok(()) + } + + fn write_block(&self, block: usize, buf: &[u8]) -> Result<(), ()> { + self.inner.lock().write_block(block, buf); + + Ok(()) + } + + fn get_block_size(&self) -> u32 { + 512 + } + + fn get_block_count(&self) -> u64 { + 0 + } +} diff --git a/src/driver/block/starfive_sdio/driver/lib.rs b/src/driver/block/starfive_sdio/driver/lib.rs new file mode 100644 index 0000000..b0231a7 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/lib.rs @@ -0,0 +1,593 @@ +#![no_std] +#[cfg(feature = "alloc")] +extern crate alloc; + +use crate::cmd::*; +use crate::register::*; +use crate::utils::*; +use core::fmt::{Display, Formatter}; +use core::mem::size_of; +use log::*; +use preprint::pprintln; + +pub use utils::{SDIo, SleepOps}; + +mod cmd; +mod register; +mod utils; + +enum DataTransType<'a> { + None, + Read(&'a mut [u8]), + Write(&'a [u8]), +} + +fn wait_ms_util_can_send_cmd(io: &mut T) -> bool { + let f = || { + let cmd_reg = CmdReg::from(read_reg(io, CMD_REG)); + !cmd_reg.start_cmd() + }; + S::sleep_ms_until(1, f); + f() +} + +fn wait_ms_util_can_send_data(io: &mut T) -> bool { + let f = || { + let status_reg = StatusReg::from(read_reg(io, STATUS_REG)); + !status_reg.data_busy() + }; + S::sleep_ms_until(100, f); + f() +} + +fn wait_ms_util_response(io: &mut T) -> bool { + let f = || { + let raw_int_status_reg = RawInterruptStatusReg::from(read_reg(io, RAW_INT_STATUS_REG)); + let int = raw_int_status_reg.int_status(); + let raw_int_status = RawInterrupt::from(int); + raw_int_status.command_done() + }; + S::sleep_ms_until(1, f); + f() +} + +fn fifo_filled_cnt(io: &mut T) -> usize { + let status = StatusReg::from(read_reg(io, STATUS_REG)); + status.fifo_count() as usize +} + +fn send_cmd( + io: &mut T, + cmd_type: Cmd, + cmd: CmdReg, + arg: CmdArg, + data_trans_type: DataTransType, +) -> Option<[u32; 4]> { + let res = wait_ms_util_can_send_cmd::<_, S>(io); + assert!(res); + if cmd.data_expected() { + let res = wait_ms_util_can_send_data::<_, S>(io); + assert!(res) + } + info!("send cmd type:{:?}, value:{:#?}", cmd_type, cmd); + // write arg + write_reg(io, ARG_REG, arg.into()); + write_reg(io, CMD_REG, cmd.into()); + // Wait for cmd accepted + let command_accept = wait_ms_util_can_send_cmd::<_, S>(io); + info!("command accepted {}", command_accept); + + if cmd.response_expect() { + let res = wait_ms_util_response::<_, S>(io); + debug!("wait_ms_util_response:{:?}", res); + } + + if cmd.data_expected() { + let mut fifo_addr = FIFO_DATA_REG; + match data_trans_type { + DataTransType::Read(buffer) => { + trace!("data_expected read...."); + let mut buf_offset = 0; + S::sleep_ms_until(250, || { + let raw_int_status_reg = + RawInterruptStatusReg::from(read_reg(io, RAW_INT_STATUS_REG)); + let int = raw_int_status_reg.int_status(); + let mut raw_int_status = RawInterrupt::from(int); + if raw_int_status.rxdr() { + debug!("RXDR...."); + while fifo_filled_cnt(io) >= 2 { + let data = read_fifo(io, fifo_addr); + for i in 0..8 { + buffer[buf_offset] = (data >> (i * 8)) as u8; + buf_offset += 1; + } + fifo_addr += size_of::(); + } + } + raw_int_status.dto() || raw_int_status.have_error() + }); + info!( + "buf_offset:{}, receive {} bytes", + buf_offset, + buf_offset * 8 + ); + } + DataTransType::Write(buffer) => { + let mut buf_offset = 0; + S::sleep_ms_until(250, || { + let raw_int_status = read_reg(io, RAW_INT_STATUS_REG); + let mut raw_int_status = RawInterrupt::from(raw_int_status as u16); + if raw_int_status.txdr() { + debug!("TXDR...."); + // Hard coded FIFO depth + while fifo_filled_cnt(io) < 120 && buf_offset < buffer.len() { + let mut data: u64 = 0; + for i in 0..8 { + data |= (buffer[buf_offset] as u64) << (i * 8); + buf_offset += 1; + } + write_fifo(io, fifo_addr, data); + fifo_addr += size_of::(); + } + } + raw_int_status.dto() || raw_int_status.have_error() + }); + info!("buf_offset:{}, send {} bytes", buf_offset, buf_offset * 8); + } + _ => { + panic!("Not implemented") + } + } + debug!("Current FIFO count: {}", fifo_filled_cnt(io)); + } + // Clear interrupt by writing 1 + let raw_int_status = read_reg(io, RAW_INT_STATUS_REG); + write_reg(io, RAW_INT_STATUS_REG, raw_int_status); + // check error + let raw_int_status = RawInterruptStatusReg::from(raw_int_status); + let mut raw_int_status = RawInterrupt::from(raw_int_status.int_status()); + let resp = [ + read_reg(io, RESP0_REG), + read_reg(io, RESP1_REG), + read_reg(io, RESP2_REG), + read_reg(io, RESP3_REG), + ]; + if raw_int_status.have_error() { + error!("card has error {:#?}", raw_int_status); + error!("cmd {:#?}", cmd); + error!("resp {:x?}", resp[0]); + return None; + } + Some(resp) +} + +fn reset_clock(io: &mut T) { + // disable clock + let mut clock_enable = ClockEnableReg::from(0); + // write to CLOCK_ENABLE_REG + write_reg(io, CLOCK_ENABLE_REG, clock_enable.into()); + // send reset clock command + let clock_cmd = CmdReg::from(0) + .with_start_cmd(true) + .with_wait_prvdata_complete(true) + .with_update_clock_registers_only(true); + send_cmd::<_, S>( + io, + Cmd::ResetClock, + clock_cmd, + CmdArg::new(0), + DataTransType::None, + ); + // set clock divider to 400kHz (low) + let clock_divider = ClockDividerReg::new().with_clk_divider0(4); + write_reg(io, CLK_DIVIDER_REG, clock_divider.into()); + // send_cmd(Cmd::ResetClock,clock_disable_cmd,CmdArg::new(0)); + // enable clock + clock_enable.set_clk_enable(1); + write_reg(io, CLOCK_ENABLE_REG, clock_enable.into()); + // send reset clock command + send_cmd::<_, S>( + io, + Cmd::ResetClock, + clock_cmd, + CmdArg::new(0), + DataTransType::None, + ); + info!( + "now clk enable {:#?}", + ClockEnableReg::from(read_reg(io, CLOCK_ENABLE_REG)) + ); + pprintln!("reset clock success"); +} + +fn reset_fifo(io: &mut T) { + let ctrl = ControlReg::from(read_reg(io, CTRL_REG)).with_fifo_reset(true); + // todo!(why write to fifo data)? + // write_reg(CTRL_REG,ctrl.raw()); + write_reg(io, FIFO_DATA_REG, ctrl.into()); + pprintln!("reset fifo success"); +} + +fn reset_dma(io: &mut T) { + let buf_mode_reg = BusModeReg::from(read_reg(io, BUS_MODE_REG)) + .with_de(false) + .with_swr(true); + write_reg(io, BUS_MODE_REG, buf_mode_reg.into()); + let ctrl = ControlReg::from(read_reg(io, CTRL_REG)) + .with_dma_reset(true) + .with_use_internal_dmac(false); + // ctrl.dma_enable().set(u1!(0)); + write_reg(io, CTRL_REG, ctrl.into()); + pprintln!("reset dma success"); +} + +fn set_transaction_size(io: &mut T, blk_size: u32, byte_count: u32) { + let blk_size = BlkSizeReg::new(blk_size); + write_reg(io, BLK_SIZE_REG, blk_size.into()); + let byte_count = ByteCountReg::new(byte_count); + write_reg(io, BYTE_CNT_REG, byte_count.into()); +} + +fn test_read(io: &mut T) { + pprintln!("test read, try read 0 block"); + set_transaction_size(io, 512, 512); + let cmd17 = CmdReg::from(Cmd::ReadSingleBlock); + let arg = CmdArg::new(0); + let mut buffer: [u8; 512] = [0; 512]; + let _resp = send_cmd::<_, S>( + io, + Cmd::ReadSingleBlock, + cmd17, + arg, + DataTransType::Read(&mut buffer), + ) + .unwrap(); + info!("Current FIFO count: {}", fifo_filled_cnt(io)); + let byte_slice = buffer.as_slice(); + pprintln!("sd header 16bytes: {:x?}", &byte_slice[..2]); +} + +/// for test driver +#[allow(unused)] +fn test_write_read(io: &mut T) { + set_transaction_size(io, 512, 512); + // write a block data + let cmd24 = CmdReg::from(Cmd::WriteSingleBlock); + let arg = CmdArg::new(0); + let mut buffer: [u8; 512] = [0; 512]; + buffer.fill(u8::MAX); + let _resp = send_cmd::<_, S>( + io, + Cmd::WriteSingleBlock, + cmd24, + arg, + DataTransType::Write(&buffer), + ) + .unwrap(); + // info!("resp csr: {:#?}",resp[0]); //csr reg + info!("Current FIFO count: {}", fifo_filled_cnt(io)); + // read a block data + let cmd17 = CmdReg::from(Cmd::ReadSingleBlock); + let arg = CmdArg::new(0); + let mut buffer: [u8; 512] = [0; 512]; + let _resp = send_cmd::<_, S>( + io, + Cmd::ReadSingleBlock, + cmd17, + arg, + DataTransType::Read(&mut buffer), + ) + .unwrap(); + // info!("resp csr: {:#?}",resp[0]); //csr reg + info!("Current FIFO count: {}", fifo_filled_cnt(io)); + let byte_slice = buffer.as_slice(); + debug!("Head 16 bytes: {:#x?}", &byte_slice[..2]); +} + +// send acmd51 to read csr reg +fn check_bus_width(io: &mut T, rca: u32) -> usize { + let cmd55 = CmdReg::from(Cmd::AppCmd); + let cmd_arg = CmdArg::new(rca << 16); + let _resp = send_cmd::<_, S>(io, Cmd::AppCmd, cmd55, cmd_arg, DataTransType::None).unwrap(); + // send acmd51 + // 1. set transact size + set_transaction_size(io, 8, 8); + // 2. send command + let acmd51 = CmdReg::from(Cmd::SendScr); + let mut buffer: [u8; 512] = [0; 512]; // 512B + send_cmd::<_, S>( + io, + Cmd::SendScr, + acmd51, + CmdArg::new(0), + DataTransType::Read(&mut buffer), + ); + info!("Current FIFO count: {}", fifo_filled_cnt(io)); //2 + let resp = u64::from_be(read_fifo(io, FIFO_DATA_REG)); + pprintln!("Bus width supported: {:b}", (resp >> 48) & 0xF); + info!("Current FIFO count: {}", fifo_filled_cnt(io)); //0 + 0 +} + +fn check_csd(io: &mut T, rca: u32) { + let cmd = CmdReg::from(Cmd::SendCsd); + let resp = send_cmd::<_, S>( + io, + Cmd::SendCsd, + cmd, + CmdArg::new(rca << 16), + DataTransType::None, + ) + .unwrap(); + let status = resp[0]; + pprintln!("status: {:b}", status); +} + +fn select_card(io: &mut T, rca: u32) { + let cmd7 = CmdReg::from(Cmd::SelectCard); + let cmd_arg = CmdArg::new(rca << 16); + let resp = send_cmd::<_, S>(io, Cmd::SelectCard, cmd7, cmd_arg, DataTransType::None).unwrap(); + let r1 = resp[0]; + info!("status: {:b}", r1); +} + +fn check_rca(io: &mut T) -> u32 { + let cmd3 = CmdReg::from(Cmd::SendRelativeAddr); + let resp = send_cmd::<_, S>( + io, + Cmd::SendRelativeAddr, + cmd3, + CmdArg::new(0), + DataTransType::None, + ) + .unwrap(); + let rca = resp[0] >> 16; + info!("rca: {:#x}", rca); + info!("card status: {:b}", resp[0] & 0xffff); + rca +} + +fn check_cid(io: &mut T) { + let cmd2 = CmdReg::from(Cmd::AllSendCid); + let resp = send_cmd::<_, S>( + io, + Cmd::AllSendCid, + cmd2, + CmdArg::new(0), + DataTransType::None, + ); + if let Some(resp) = resp { + // to 128 bit + let resp = resp[0] as u128 + | ((resp[1] as u128) << 32) + | ((resp[2] as u128) << 64) + | ((resp[3] as u128) << 96); + let cid = Cid::new(resp); + #[cfg(feature = "alloc")] + pprintln!("cid: {}", cid.fmt()); + #[cfg(not(feature = "alloc"))] + pprintln!("cid: {:?}", cid); + } +} + +fn check_version(io: &mut T) -> u8 { + // check voltage + let cmd8 = CmdReg::from(Cmd::SendIfCond); + let cmd8_arg = CmdArg::new(0x1aa); + let resp = send_cmd::<_, S>(io, Cmd::SendIfCond, cmd8, cmd8_arg, DataTransType::None).unwrap(); + if (resp[0] & 0xaa) == 0 { + error!("card {} unusable", 0); + pprintln!("card version: 1.0"); + return 1; + } + pprintln!("card voltage: {:#x?}", resp[0]); + pprintln!("card version: 2.0"); + 2 +} + +fn check_big_support(io: &mut T) -> bool { + loop { + // send cmd55 + let cmd55 = CmdReg::from(Cmd::AppCmd); + send_cmd::<_, S>(io, Cmd::AppCmd, cmd55, CmdArg::new(0), DataTransType::None); + let cmd41 = CmdReg::from(Cmd::SdSendOpCond); + let cmd41_arg = CmdArg::new((1 << 30) | (1 << 24) | 0xFF8000); + let resp = + send_cmd::<_, S>(io, Cmd::SdSendOpCond, cmd41, cmd41_arg, DataTransType::None).unwrap(); + info!("ocr: {:#x?}", resp[0]); + let ocr = resp[0]; + if ocr.get_bit(31) { + pprintln!("card is ready"); + if ocr.get_bit(30) { + pprintln!("card is high capacity"); + } else { + pprintln!("card is standard capacity"); + } + break; + } + S::sleep_ms(10); + } + true +} + +fn init_sdcard(io: &mut T) { + // read DETECT_REG + let detect = read_reg(io, CDETECT_REG); + info!("detect: {:#?}", CDetectReg::new(detect)); + // read POWER_REG + let power = read_reg(io, POWER_REG); + info!("power: {:#?}", PowerReg::new(power)); + // read CLOCK_ENABLE_REG + let clock_enable = read_reg(io, CLOCK_ENABLE_REG); + info!("clock_enable: {:#?}", ClockEnableReg::from(clock_enable)); + // read CARD_TYPE_REG + let card_type = read_reg(io, CTYPE_REG); + info!("card_type: {:#?}", CardTypeReg::from(card_type)); + // read Control Register + let control = read_reg(io, CTRL_REG); + info!("control: {:#?}", ControlReg::from(control)); + // read bus mode register + let bus_mode = read_reg(io, BUS_MODE_REG); + info!("bus_mode(DMA): {:#?}", BusModeReg::from(bus_mode)); + // read DMA Descriptor List Base Address Register + let dma_desc_base_lower = read_reg(io, DBADDRL_REG); + let dma_desc_base_upper = read_reg(io, DBADDRU_REG); + let dma_desc_base: usize = + dma_desc_base_lower as usize | ((dma_desc_base_upper as usize) << 32); + info!("dma_desc_base: {:#x?}", dma_desc_base); + // read clock divider register + let clock_divider = read_reg(io, CLK_DIVIDER_REG); + info!("clock_divider: {:#?}", ClockDividerReg::from(clock_divider)); + + // reset card clock to 400Mhz + reset_clock::<_, S>(io); + // reset fifo + reset_fifo(io); + + // set data width --> 1bit + let ctype = CardTypeReg::from(0).with_card_width4_1(0); + write_reg(io, CTYPE_REG, ctype.into()); + + // reset dma + reset_dma(io); + + let ctrl = ControlReg::from(read_reg(io, CTRL_REG)); + info!("ctrl: {:#?}", ctrl); + + // go idle state + let cmd0 = CmdReg::from(Cmd::GoIdleState); + // cmd0.response_expect().set(u1!(0)); + send_cmd::<_, S>( + io, + Cmd::GoIdleState, + cmd0, + CmdArg::new(0), + DataTransType::None, + ); + pprintln!("card is in idle state"); + + check_version::<_, S>(io); + + check_big_support::(io); + + check_cid::<_, S>(io); + let rca = check_rca::<_, S>(io); + pprintln!("rca: {:#x?}", rca); + check_csd::<_, S>(io, rca); + + // let raw_int_status = RawInterruptStatusReg::from(read_reg(io,RAW_INT_STATUS_REG)); + // pprintln!("RAW_INT_STATUS_REG: {:#?}", raw_int_status); + + S::sleep_ms(1); + + select_card::<_, S>(io, rca); + + let status = StatusReg::from(read_reg(io, STATUS_REG)); + info!("Now FIFO Count is {}", status.fifo_count()); + + // check bus width + check_bus_width::<_, S>(io, rca); + // try read a block data + test_read::<_, S>(io); + // test_write_read(); + + info!("CTRL_REG: {:#?}", ControlReg::from(read_reg(io, CTRL_REG))); + let raw_int_status = RawInterruptStatusReg::from(read_reg(io, RAW_INT_STATUS_REG)); + info!("RAW_INT_STATUS_REG: {:#?}", raw_int_status); + // Clear interrupt by writing 1 + write_reg(io, RAW_INT_STATUS_REG, raw_int_status.into()); + + pprintln!("init sd success"); +} + +#[derive(Debug, Copy, Clone)] +pub enum Vf2SdDriverError { + InitError, + ReadError, + WriteError, + TimeoutError, + UnknownError, +} + +impl Display for Vf2SdDriverError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Vf2SdDriverError::InitError => write!(f, "init error"), + Vf2SdDriverError::ReadError => write!(f, "read error"), + Vf2SdDriverError::WriteError => write!(f, "write error"), + Vf2SdDriverError::TimeoutError => write!(f, "timeout error"), + Vf2SdDriverError::UnknownError => write!(f, "unknown error"), + } + } +} + +pub type Result = core::result::Result; + +fn read_block(io: &mut T, block: usize, buf: &mut [u8]) -> Result { + assert_eq!(buf.len(), 512); + set_transaction_size(io, 512, 512); + let cmd17 = CmdReg::from(Cmd::ReadSingleBlock); + let arg = CmdArg::new(block as u32); + let _resp = send_cmd::<_, S>( + io, + Cmd::ReadSingleBlock, + cmd17, + arg, + DataTransType::Read(buf), + ) + .unwrap(); + info!("Current FIFO count: {}", fifo_filled_cnt(io)); + Ok(buf.len()) +} + +fn write_block(io: &mut T, block: usize, buf: &[u8]) -> Result { + assert_eq!(buf.len(), 512); + set_transaction_size(io, 512, 512); + let cmd24 = CmdReg::from(Cmd::WriteSingleBlock); + let arg = CmdArg::new(block as u32); + let _resp = send_cmd::<_, S>( + io, + Cmd::WriteSingleBlock, + cmd24, + arg, + DataTransType::Write(buf), + ) + .unwrap(); + info!("Current FIFO count: {}", fifo_filled_cnt(io)); + Ok(buf.len()) +} + +/// Vf2SdDriver +/// +/// # Example +/// ```rust no run +/// fn sleep(ms:usize){} +/// use visionfive2_sd::Vf2SdDriver; +/// let driver = Vf2SdDriver::new(sleep); +/// driver.init(); +/// let mut buf = [0u8;512]; +/// driver.read_block(0,&mut buf); +/// driver.write_block(0,&buf); +/// ``` +pub struct Vf2SdDriver { + io: T, + _sleep: core::marker::PhantomData, +} + +impl Vf2SdDriver { + pub fn new(io: T) -> Self { + Self { + io, + _sleep: core::marker::PhantomData, + } + } + pub fn init(&mut self) { + init_sdcard::(&mut self.io); + } + pub fn read_block(&mut self, block: usize, buf: &mut [u8]) { + read_block::<_, S>(&mut self.io, block, buf).unwrap(); + } + pub fn write_block(&mut self, block: usize, buf: &[u8]) { + write_block::<_, S>(&mut self.io, block, buf).unwrap(); + } +} diff --git a/src/driver/block/starfive_sdio/driver/mod.rs b/src/driver/block/starfive_sdio/driver/mod.rs new file mode 100644 index 0000000..e86008e --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/mod.rs @@ -0,0 +1,7 @@ +mod driver; + +// mod cmd; +// mod utils; +// mod register; + +pub use driver::Driver; diff --git a/src/driver/block/starfive_sdio/driver/register.rs b/src/driver/block/starfive_sdio/driver/register.rs new file mode 100644 index 0000000..20bbf9c --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/register.rs @@ -0,0 +1,539 @@ +use super::cmd::Cmd; +use super::utils::GetBit; +use bitfield_struct::bitfield; + +// pub const SDIO_BASE: usize = 0x16020000; +pub const CTRL_REG: usize = 0x00; +pub const POWER_REG: usize = 0x04; +pub const BLK_SIZE_REG: usize = 0x1c; +pub const BYTE_CNT_REG: usize = 0x20; +pub const CMD_REG: usize = 0x2c; +pub const ARG_REG: usize = 0x28; +pub const RESP0_REG: usize = 0x30; +pub const RESP1_REG: usize = 0x34; +pub const RESP2_REG: usize = 0x38; +pub const RESP3_REG: usize = 0x3c; +pub const STATUS_REG: usize = 0x48; +pub const CDETECT_REG: usize = 0x50; +pub const BUS_MODE_REG: usize = 0x80; +pub const CTYPE_REG: usize = 0x18; +pub const CLOCK_ENABLE_REG: usize = 0x10; +pub const DBADDRL_REG: usize = 0x88; // DMA DES Address Lower +pub const DBADDRU_REG: usize = 0x8c; // DMA DES Address Upper +pub const CLK_DIVIDER_REG: usize = 0x08; +pub const RAW_INT_STATUS_REG: usize = 0x44; +pub const FIFO_DATA_REG: usize = 0x600; + +macro_rules! impl_into_u32 { + ($name:ident) => { + impl From<$name> for u32 { + fn from(val: $name) -> u32 { + val.0 + } + } + }; +} + +macro_rules! impl_new { + ($name:ident) => { + impl $name { + pub fn new(value: u32) -> Self { + $name(value) + } + } + }; +} + +#[derive(Debug)] +pub struct CmdArg(u32); +impl_new!(CmdArg); +impl_into_u32!(CmdArg); + +/// Number of bytes to be transferred; should be integer multiple of Block Size for block transfers. +#[derive(Debug)] +pub struct ByteCountReg(u32); +impl_new!(ByteCountReg); +impl_into_u32!(ByteCountReg); +#[derive(Debug)] +pub struct BlkSizeReg(u32); +impl_new!(BlkSizeReg); +impl_into_u32!(BlkSizeReg); + +#[derive(Debug)] +pub struct PowerReg(u32); +impl_new!(PowerReg); +impl_into_u32!(PowerReg); + +#[derive(Debug)] +pub struct CDetectReg(u32); +impl_new!(CDetectReg); +impl_into_u32!(CDetectReg); + +#[bitfield(u32,order = Msb)] +pub struct CmdReg { + pub start_cmd: bool, + reserved: bool, + /// Use Hold Register + /// + /// 0 - CMD and DATA sent to card bypassing HOLD Register + /// + /// 1 - CMD and DATA sent to card through the HOLD Register For more information, + /// refer to “Host Controller Output Path Timing” on page 320. + pub use_hold_reg: bool, + pub volt_switch: bool, + pub boot_mode: bool, + pub disable_boot: bool, + pub expect_boot_ack: bool, + pub enable_boot: bool, + pub ccs_expected: bool, + pub read_ceata_device: bool, + /// 0 - Normal command sequence + /// 1 - Do not send commands, just update clock register value into card clock domain + pub update_clock_registers_only: bool, + #[bits(5)] + pub card_number: u16, + pub send_initialization: bool, + pub stop_abort_cmd: bool, + ///0 - Send command at once, even if previous data transfer has not completed + /// + /// 1 - Wait for previous data transfer completion before sending command + /// + /// The wait_prvdata_complete = 0 option typically used to query status of card + /// during data transfer or to stop current data transfer; card_number should be same as in previous command. + pub wait_prvdata_complete: bool, + /// + /// 0 - No stop command sent at end of data transfer + /// + /// 1 - Send stop command at end of data transfer + /// Don't care if no data expected from card. + pub send_auto_stop: bool, + /// + /// 0 - Block data transfer command + /// + /// 1 - Stream data transfer command Don’t care if no data expected. + pub transfer_mode: bool, + /// 0 - Read from card + /// + /// 1 - Write to card + /// + /// Don’t care if no data expected from card. + pub transfer_dir: bool, + /// 0 - No data transfer expected (read/write) 1 - Data transfer expected (read/write) + pub data_expected: bool, + /// 0 - Do not check response CRC + /// + /// 1 - Check response CRC + /// + /// Some of command responses do not return valid CRC bits. + /// + /// Software should disable CRC checks for those commands in order to disable CRC checking by controller. + pub check_response_crc: bool, + /// 0 - Short response expected from card 1 - Long response expected from card + pub response_length: bool, + /// 0 - No response expected from card 1 - Response expected from card + pub response_expect: bool, + #[bits(6)] + /// Command index + pub cmd_index: u16, +} + +#[bitfield(u32,order = Msb)] +pub struct ClockEnableReg { + /// Low-power control for up to 16 SD card clocks and one MMC card clock supported. + /// + /// 0 - Non-low-power mode + /// + /// 1 - Low-power mode; stop clock when card in IDLE (should be normally set to only + /// MMC and SD memory cards; for SDIO cards, if interrupts must be detected, clock should not be stopped). + pub cclk_low_power: u16, + /// + /// Clock-enable control for up to 16 SD card clocks and one MMC card clock supported. + /// + /// 0 - Clock disabled + /// + /// 1 - Clock enabled + pub clk_enable: u16, +} + +#[bitfield(u32,order = Msb)] +pub struct CardTypeReg { + /// One bit per card indicates if card is 8-bit: + /// 0 - Non 8-bit mode + /// + /// 1 - 8-bit mode + /// + /// Bit[31] corresponds to card[15]; bit[16] corresponds to card[0]. + pub card_width8: u16, + /// One bit per card indicates if card is 1-bit or 4-bit: + /// 0 - 1-bit mode + /// + /// 1 - 4-bit mode + /// + /// Bit[15] corresponds to card[15], bit[0] corresponds to card[0]. + /// + /// Only NUM_CARDS*2 number of bits are implemented. + pub card_width4_1: u16, +} + +#[bitfield(u32,order = Msb)] +pub struct ClockDividerReg { + pub clk_divider3: u8, + pub clk_divider2: u8, + pub clk_divider1: u8, + /// Clock divider-0 value. Clock division is 2* n. For example, value of 0 means + /// + /// divide by 2*0 = 0 (no division, bypass), value of 1 means divide by 2*1 = 2, + /// value of “ff” means divide by 2*255 = 510, and so on. + pub clk_divider0: u8, +} + +#[bitfield(u32,order = Msb)] +pub struct RawInterruptStatusReg { + /// Interrupt from SDIO card; one bit for each card. Bit[31] corresponds to Card[15], + /// and bit[16] is for Card[0]. Writes to these bits clear them. Value of 1 clears bit and 0 leaves bit intact. + /// + /// 0 - No SDIO interrupt from card + /// + /// 1 - SDIO interrupt from card + pub sdiojnterrupt: u16, + /// Writes to bits clear status bit. Value of 1 clears status bit, and value of 0 leaves bit intact. + /// Bits are logged regardless of interrupt mask status. + pub int_status: u16, +} + +#[bitfield(u32,order = Msb)] +pub struct BusModeReg { + #[bits(21)] + reserved: u32, + /// Programmable Burst Length. These bits indicate the maximum number of beats to be performed + /// in one IDMAC transaction. The IDMAC will always attempt to burst as specified in PBL + /// each time it starts a Burst transfer on the host bus. + /// The permissible values are 1,4, 8, 16, 32, 64, 128 and 256. + /// This value is the mirror of MSIZE of FIFOTH register. In order to change this value, + /// write the required value to FIFOTH register. This is an encode value as follows. + #[bits(3)] + pub pbl: u8, + /// IDMAC Enable. When set, the IDMAC is enabled. + /// DE is read/write. + pub de: bool, + /// Descriptor Skip Length. Specifies the number of HWord/Word/Dword (depending on 16/32/64-bit bus) + /// to skip between two unchained descriptors. This is applicable only for dual buffer structure. + /// DSL is read/write. + #[bits(5)] + pub dsl: u8, + pub fd: bool, + /// Software Reset. When set, the DMA Controller resets all its internal registers. + /// SWR is read/write. It is automatically cleared after 1 clock cycle. + #[bits(1)] + pub swr: bool, +} + +#[bitfield(u32,order = Msb)] +pub struct StatusReg { + /// DMA request signal state; either dw_dma_req or ge_dma_req, depending on DW-DMA or Generic-DMA selection. + pub dma_req: bool, + ///DMA acknowledge signal state; either dw_dma_ack or ge_dma_ack, depending on DW-DMA or Generic-DMA selection. + pub dma_ack: bool, + /// FIFO count - Number of filled locations in FIFO + #[bits(13)] + pub fifo_count: u16, + /// Index of previous response, including any auto-stop sent by core. + #[bits(6)] + pub response_index: u8, + /// Data transmit or receive state-machine is busy + pub data_state_mc_busy: bool, + /// Inverted version of raw selected card_data[0] 0 - card data not busy 1 - card data busy + pub data_busy: bool, + /// Raw selected card_data[3]; checks whether card is present 0 - card not present + /// + /// 1 - card present + pub data_3_status: bool, + #[bits(4)] + pub command_fsm_states: u8, + /// FIFO is full status + pub fifo_full: bool, + pub fifo_empty: bool, + /// FIFO reached Transmit watermark level; not qualified with data + /// + /// transfer. + pub fifo_tx_watermark: bool, + /// + /// FIFO reached Receive watermark level; not qualified with data + /// + /// transfer. + pub fifo_rx_watermark: bool, +} +#[bitfield(u32,order = Msb)] +pub struct ControlReg { + #[bits(6)] + reserved: u8, + /// Present only for the Internal DMAC configuration; else, it is reserved. + /// 0– The host performs data transfers through the slave interface + /// 1– Internal DMAC used for data transfer + pub use_internal_dmac: bool, + /// External open-drain pullup: + /// + /// 0- Disable + /// 1 - Enable + /// Inverted value of this bit is output to ccmd_od_pullup_en_n port. + /// When bit is set, command output always driven in open-drive mode; + /// that is, DWC_mobile_storage drives either 0 or high impedance, and does not drive hard 1. + pub enable_od_pullup: bool, + /// Card regulator-B voltage setting; output to card_volt_b port. + /// + /// Optional feature; ports can be used as general-purpose outputs. + #[bits(4)] + pub card_voltage_b: u8, + /// Card regulator-A voltage setting; output to card_volt_a port. + /// + /// Optional feature; ports can be used as general-purpose outputs. + #[bits(4)] + pub card_voltage_a: u8, + #[bits(4)] + pub reserved1: u8, + /// 0 - Interrupts not enabled in CE-ATA device (nIEN = 1 in ATA control register) + /// 1 - Interrupts are enabled in CE-ATA device (nIEN = 0 in ATA control register) + /// Software should appropriately write to this bit after power-on reset or any other reset to CE-ATA device. + /// After reset, usually CE-ATA device interrupt is disabled (nIEN = 1). + /// If the host enables CE-ATA device interrupt, then software should set this bit. + pub ceata_device_interrupt: bool, + /// 0 - Clear bit if DWC_mobile_storage does not reset the bit. + /// 1 - Send internally generated STOP after sending CCSD to CE-ATA device. + pub send_auto_stop_ccsd: bool, + /// + /// 0 - Clear bit if DWC_mobile_storage does not reset the bit. + /// + /// 1 - Send Command Completion Signal Disable (CCSD) to CE-ATA device + pub send_ccsd: bool, + /// 0 - No change + /// + /// 1 - After suspend command is issued during read-transfer, software polls card to + /// find when suspend happened. Once suspend occurs, software sets bit to reset data state-machine, + /// which is waiting for next block of data. Bit automatically clears once data state­machine resets to idle. + /// + /// Used in SDIO card suspend sequence. + pub abort_read_data: bool, + /// + /// 0 - No change + /// + /// 1 - Send auto IRQ response + /// + /// Bit automatically clears once response is sent. + /// + /// To wait for MMC card interrupts, host issues CMD40, and DWC_mobile_storage waits for + /// interrupt response from MMC card(s). In meantime, if host wants DWC_mobile_storage + /// to exit waiting for interrupt state, it can set this bit, at which time DWC_mobile_storage + /// command state-machine sends CMD40 response on bus and returns to idle state. + pub send_irq_response: bool, + /// + /// 0 - Clear read wait + /// + /// 1 - Assert read wait For sending read-wait to SDIO cards. + pub read_wait: bool, + /// + /// 0 - Disable DMA transfer mode + /// + /// 1 - Enable DMA transfer mode + pub dma_enable: bool, + /// + /// Global interrupt enable/disable bit: + /// + /// 0 - Disable interrupts + /// + /// 1 - Enable interrupts + /// + /// The int port is 1 only when this bit is 1 and one or more unmasked interrupts are set. + pub int_enable: bool, + reserved2: bool, + /// 0 - No change + /// + /// 1 - Reset internal DMA interface control logic + /// + /// To reset DMA interface, firmware should set bit to 1. This bit is auto-cleared after two AHB clocks. + pub dma_reset: bool, + /// 0 - No change + /// + /// 1 - Reset to data FIFO To reset FIFO pointers + /// + /// To reset FIFO, firmware should set bit to 1. This bit is auto-cleared after completion of reset operation. + pub fifo_reset: bool, + /// + /// 0 - No change + /// + /// 1 - Reset DWC_mobile_storage controller + pub controller_reset: bool, +} + +#[bitfield(u16,order = Msb)] +pub struct RawInterrupt { + /// End-bit error (read)/write no CRC (EBE) + pub ebe: bool, + /// Auto command done (ACD) + pub acd: bool, + /// Start-bit error (SBE) /Busy Clear Interrupt (BCI) + pub sbe: bool, + /// Hardware locked write error (HLE) + pub hle: bool, + /// FIFO underrun/overrun error (FRUN) + pub frun: bool, + /// Data starvation-by-host timeout (HTO) /Volt_switch_int + pub hto: bool, + /// Data read timeout (DRTO)/Boot Data Start (BDS) + pub drto: bool, + /// Response timeout (RTO)/Boot Ack Received (BAR) + pub rto: bool, + /// Data CRC error (DCRC) + pub dcrc: bool, + /// Response CRC error (RCRC) + pub rcrc: bool, + /// Receive FIFO data request (RxDR) + pub rxdr: bool, + /// Transmit FIFO data request (TXDR) + pub txdr: bool, + /// Data transfer over (DtO) + pub dto: bool, + /// Command done (CD) + pub command_done: bool, + /// Response error (RE) + pub response_err: bool, + /// Card detect (Cd) + pub card_dectect: bool, +} + +// mid:u8, +// oid:u16, +// pnm:u32, +// prv:u8, +// psn:u32, +// reserved:u4, +// mdt:u12, +// crc:u7, +// zero:u1, + +#[derive(Debug)] +pub struct Cid(u128); + +#[allow(dead_code)] +impl Cid { + pub fn new(value: u128) -> Self { + Cid(value) + } + + #[cfg(feature = "alloc")] + pub fn fmt(&self) -> alloc::string::String { + use alloc::format; + use alloc::string::ToString; + let mid = self.0.get_bits(120, 127) as u8; + let oid = self.0.get_bits(104, 119) as u16; // 2char + let oid = core::str::from_utf8(&oid.to_be_bytes()) + .unwrap() + .to_string(); + let pnm = self.0.get_bits(64, 103) as u64; // 5char + let pnm = core::str::from_utf8(&pnm.to_be_bytes()[0..5]) + .unwrap() + .to_string(); + let prv_big = self.0.get_bits(60, 63) as u8; // + let prv_small = self.0.get_bits(56, 59) as u8; // + let prv = format!("{}.{}", prv_big, prv_small); + let psn = self.0.get_bits(24, 55) as u32; // + let year = self.0.get_bits(12, 19) as u8; // + let month = self.0.get_bits(8, 11) as u8; // + let mdt = format!("{}-{}", year as usize + 2000, month); + let res = format!( + "mid:{} oid:{} pnm:{} prv:{} psn:{} mdt:{}", + mid, oid, pnm, prv, psn, mdt + ); + res + } + + pub fn mid(&self) -> u8 { + self.0.get_bits(120, 127) as u8 + } + + #[cfg(feature = "alloc")] + pub fn oid(&self) -> alloc::string::String { + use alloc::string::ToString; + let oid = self.0.get_bits(104, 119) as u16; // 2char + let oid = core::str::from_utf8(&oid.to_be_bytes()) + .unwrap() + .to_string(); + oid + } + #[cfg(feature = "alloc")] + pub fn pnm(&self) -> alloc::string::String { + use alloc::string::ToString; + let pnm = self.0.get_bits(64, 103) as u64; // 5char + let pnm = core::str::from_utf8(&pnm.to_be_bytes()[0..5]) + .unwrap() + .to_string(); + pnm + } + #[cfg(feature = "alloc")] + pub fn prv(&self) -> alloc::string::String { + let prv_big = self.0.get_bits(60, 63) as u8; // + let prv_small = self.0.get_bits(56, 59) as u8; // + let prv = alloc::format!("{}.{}", prv_big, prv_small); + prv + } + pub fn psn(&self) -> u32 { + self.0.get_bits(24, 55) as u32 + } + #[cfg(feature = "alloc")] + pub fn mdt(&self) -> alloc::string::String { + let year = self.0.get_bits(12, 19) as u8; // + let month = self.0.get_bits(8, 11) as u8; // + let mdt = alloc::format!("{}-{}", year as usize + 2000, month); + mdt + } +} + +impl RawInterrupt { + pub fn have_error(&mut self) -> bool { + self.rto() || self.dcrc() || self.response_err() || self.drto() || self.sbe() || self.ebe() + } +} + +impl CmdReg { + pub fn default(card_number: usize, cmd_number: u8) -> Self { + CmdReg::new() + .with_start_cmd(true) + .with_use_hold_reg(true) + .with_response_expect(true) + .with_wait_prvdata_complete(true) + .with_check_response_crc(true) + .with_card_number(card_number as u16) + .with_cmd_index(cmd_number as u16) + } + pub fn with_no_data(card_number: usize, cmd_number: u8) -> Self { + CmdReg::default(card_number, cmd_number) + } + + pub fn with_data(card_number: usize, cmd_number: u8) -> Self { + CmdReg::default(card_number, cmd_number).with_data_expected(true) + } +} + +impl From for CmdReg { + fn from(value: Cmd) -> Self { + match value { + Cmd::GoIdleState => { + CmdReg::with_no_data(0, value.into()).with_send_initialization(true) + } + Cmd::SendIfCond | Cmd::AppCmd | Cmd::SendRelativeAddr | Cmd::SelectCard => { + CmdReg::with_no_data(0, value.into()) + } + Cmd::SdSendOpCond => { + CmdReg::with_no_data(0, value.into()).with_check_response_crc(false) + } + Cmd::SendCsd => CmdReg::with_no_data(0, value.into()).with_check_response_crc(false), + Cmd::AllSendCid => CmdReg::with_no_data(0, value.into()) + .with_check_response_crc(false) + .with_response_length(true), + Cmd::SendScr | Cmd::ReadSingleBlock => CmdReg::with_data(0, value.into()), + Cmd::WriteSingleBlock => CmdReg::with_data(0, value.into()).with_transfer_dir(true), + _ => { + panic!("Not implemented") + } + } + } +} diff --git a/src/driver/block/starfive_sdio/driver/utils.rs b/src/driver/block/starfive_sdio/driver/utils.rs new file mode 100644 index 0000000..39b8308 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver/utils.rs @@ -0,0 +1,82 @@ +// use super::register::SDIO_BASE; + +// pub fn read_fifo(io: &T, addr: usize) -> u64 { +// io.read_data_at(addr - SDIO_BASE) +// } + +// pub fn write_fifo(io: &mut T, addr: usize, val: u64) { +// io.write_data_at(addr - SDIO_BASE, val); +// } + +// pub fn write_reg(io: &mut T, addr: usize, val: u32) { +// io.write_reg_at(addr - SDIO_BASE, val); +// } + +// pub fn read_reg(io: &T, addr: usize) -> u32 { +// io.read_reg_at(addr - SDIO_BASE) +// } + +// pub trait SDIo { +// fn read_reg_at(&self, offset: usize) -> u32; +// fn write_reg_at(&mut self, offset: usize, val: u32); +// fn read_data_at(&self, offset: usize) -> u64; +// fn write_data_at(&mut self, offset: usize, val: u64); +// } + +// pub trait SleepOps { +// fn sleep_ms(ms: usize); +// fn sleep_ms_until(ms: usize, f: impl FnMut() -> bool); +// } + +pub trait GetBit { + type Output; + fn get_bit(&self, bit: u8) -> bool; + fn get_bits(&self, start: u8, end: u8) -> Self::Output; +} + +impl GetBit for u32 { + type Output = u32; + fn get_bit(&self, bit: u8) -> bool { + (*self & (1 << bit)) != 0 + } + fn get_bits(&self, start: u8, end: u8) -> Self::Output { + let mask = (1 << (end - start + 1)) - 1; + (*self >> start) & mask + } +} + +impl GetBit for u128 { + type Output = u128; + fn get_bit(&self, bit: u8) -> bool { + (*self & (1 << bit)) != 0 + } + fn get_bits(&self, start: u8, end: u8) -> Self::Output { + let mask = (1 << (end - start + 1)) - 1; + (*self >> start) & mask + } +} + +// #[cfg(test)] +// mod tests { + +// use super::*; +// #[test] +// fn test_get_bit() { +// let val = 0b1010_1010u32; +// assert!(!val.get_bit(0)); +// assert!(val.get_bit(1)); +// assert!(!val.get_bit(2)); +// assert!(val.get_bit(3)); +// assert!(!val.get_bit(4)); +// assert!(val.get_bit(5)); +// assert!(!val.get_bit(6)); +// assert!(val.get_bit(7)); +// } + +// #[test] +// fn test_get_bits() { +// let val = 0b1010_1010u32; +// assert_eq!(val.get_bits(0, 3), 0b1010); +// assert_eq!(val.get_bits(4, 7), 0b1010); +// } +// } diff --git a/src/driver/block/starfive_sdio/driver_/cmd.rs b/src/driver/block/starfive_sdio/driver_/cmd.rs new file mode 100644 index 0000000..526b1a3 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/cmd.rs @@ -0,0 +1,266 @@ +use super::reg::CmdMask; +use core::fmt::Debug; + +use super::sd_reg::{CardStatus, Cic, Cid, Csd, Ocr, Rca}; + +const ALL_SEND_CID: u32 = 2; +const SEND_RCA: u32 = 3; +const SWITCH_FUNCTION: u32 = 6; +const SELECT_CARD: u32 = 7; +const SEND_IF_COND: u32 = 8; +const SEND_CSD: u32 = 9; +const STOP_TRANSMISSION: u32 = 12; +const READ_SINGLE_BLOCK: u32 = 17; +const WRITE_SINGLE_BLOCK: u32 = 24; +const APP_CMD: u32 = 55; +const ACMD_SD_SEND_OP_COND: u32 = 41; +const ACMD_SET_BUS: u32 = 6; +#[derive(Clone, Copy, Default)] +pub struct Command { + reg_flags: u32, + index: u32, + arg: u32, + resp_ty: ResponseType, +} + +impl Debug for Command { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Command ") + .field("\n\tindex", &self.index) + .field("\n\targ", &self.arg) + .field("\n\tflags", &self.reg_flags) + .field("\n\tresponse type", &self.resp_ty) + .finish() + } +} + +impl Command { + fn transfer_cmd(index: u32, resp_ty: ResponseType, addr: u32, write_mode: bool) -> Self { + let mut cmd = Command::default(); + cmd.index = index; + cmd.arg = addr; + cmd.resp_ty = resp_ty; + cmd.reg_flags |= CmdMask::start_cmd.bits() + | CmdMask::use_hold_reg.bits() + | CmdMask::data_expected.bits() + | CmdMask::wait_prvdata_complete.bits() + | CmdMask::response_expect.bits() + | CmdMask::check_response_crc.bits(); + if write_mode { + cmd.reg_flags |= CmdMask::write.bits(); + } + cmd + } + + fn no_data_cmd_r48(index: u32, resp_ty: ResponseType, arg: u32) -> Self { + let mut cmd = Command::default(); + cmd.index = index; + cmd.resp_ty = resp_ty; + cmd.arg = arg; + cmd.reg_flags |= CmdMask::start_cmd.bits() + | CmdMask::use_hold_reg.bits() + | CmdMask::wait_prvdata_complete.bits() + | CmdMask::response_expect.bits() + | CmdMask::check_response_crc.bits(); + cmd + } +// low 5 bits are OP Num + pub fn to_cmd(&self) -> u32 { + self.reg_flags | self.index + } + + pub fn arg(&self) -> u32 { + self.arg + } + pub fn data_exp(&self) -> bool { + self.reg_flags & CmdMask::data_expected.bits() != 0 + } + + pub fn resp_exp(&self) -> bool { + self.resp_ty != ResponseType::Non + } + + pub fn resp_lang(&self) -> bool { + self.resp_ty == ResponseType::R2 + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ResponseType { + Non = 0, + R1 = 1, + R1b = 10, + R2 = 2, + R3 = 3, + R6 = 6, + R7 = 7, +} + +impl Default for ResponseType { + fn default() -> Self { + Self::Non + } +} +impl Debug for ResponseType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Non => write!(f, "Non"), + Self::R1 => write!(f, "R1"), + Self::R1b => write!(f, "R1b"), + Self::R2 => write!(f, "R2"), + Self::R3 => write!(f, "R3"), + Self::R6 => write!(f, "R6"), + Self::R7 => write!(f, "R7"), + } + } +} + +pub enum Response { + Rz, + R48(u32), + R136((u32, u32, u32, u32)), +} + +impl Response { + pub(crate) fn card_status(self) -> CardStatus { + match self { + Response::R48(r) => CardStatus::from(r), + _ => CardStatus::default(), + } + } + pub(crate) fn csd(self) -> Csd { + match self { + Self::R136(r) => Csd::from(r), + _ => Csd::default(), + } + } + pub(crate) fn cid(self) -> Cid { + match self { + Self::R136(r) => Cid::from(r), + _ => Cid::default(), + } + } + + pub(crate) fn ocr(self) -> Ocr { + match self { + Response::R48(r) => Ocr::from(r), + _ => Ocr::default(), + } + } + + pub(crate) fn cic(self) -> Cic { + match self { + Response::R48(r) => Cic::from(r), + _ => Cic::default(), + } + } + + pub(crate) fn rca(self) -> Rca { + match self { + Response::R48(r) => Rca::from(r), + _ => Rca::default(), + } + } +} + +pub fn idle() -> Command { + let mut cmd = Command::default(); + cmd.reg_flags |= CmdMask::send_initialization.bits() + | CmdMask::start_cmd.bits() + | CmdMask::wait_prvdata_complete.bits() + | CmdMask::use_hold_reg.bits(); + cmd +} + +pub fn up_clk() -> Command { + let mut cmd = Command::default(); + cmd.reg_flags |= CmdMask::update_clock_registers_only.bits() + | CmdMask::wait_prvdata_complete.bits() + | CmdMask::start_cmd.bits() + | CmdMask::use_hold_reg.bits(); + cmd +} + +/// CMD12: Stop transmission +pub fn stop_transmission() -> Command { + let mut cmd = Command::default(); + cmd.reg_flags |= CmdMask::start_cmd.bits() + | CmdMask::use_hold_reg.bits() + | CmdMask::stop_abort_cmd.bits() + | CmdMask::response_expect.bits() + | CmdMask::check_response_crc.bits(); + cmd.index = STOP_TRANSMISSION; + cmd.arg = 0; + cmd.resp_ty = ResponseType::R1b; + cmd +} + +/// CMD2: Ask any card to send their CID +pub fn all_send_cid() -> Command { + let mut cmd = Command::no_data_cmd_r48(ALL_SEND_CID, ResponseType::R2, 0); + cmd.reg_flags |= CmdMask::response_length.bits(); + cmd +} + +/// CMD6: switch function +pub fn switch_function(arg: u32) -> Command { + Command::no_data_cmd_r48(SWITCH_FUNCTION, ResponseType::R1, arg) +} + +/// CMD7: Select or deselect card +pub fn select_card(rca: u16) -> Command { + let arg = u32::from(rca) << 16; + Command::no_data_cmd_r48(SELECT_CARD, ResponseType::R1b, arg) +} + +/// CMD9: Send CSD +pub fn send_csd(rca: u16) -> Command { + let arg = u32::from(rca) << 16; + let mut cmd = Command::no_data_cmd_r48(SEND_CSD, ResponseType::R2, arg); + cmd.reg_flags |= CmdMask::response_length.bits(); + cmd +} + +/// CMD17: Read a single block from the card +pub fn read_single_block(addr: u32) -> Command { + Command::transfer_cmd(READ_SINGLE_BLOCK, ResponseType::R1, addr, false) +} + +/// CMD24: Write block +pub fn write_single_block(addr: u32) -> Command { + Command::transfer_cmd(WRITE_SINGLE_BLOCK, ResponseType::R1, addr, true) +} + +/// CMD55: App Command. Indicates that next command will be a app command +pub fn app_cmd(rca: u16) -> Command { + Command::no_data_cmd_r48(APP_CMD, ResponseType::R1, u32::from(rca) << 16) +} + +/// CMD3: Send RCA +pub fn send_relative_address() -> Command { + Command::no_data_cmd_r48(SEND_RCA, ResponseType::R6, 0) +} + +/// CMD8: Sends memory card interface conditions +pub fn send_if_cond(voltage: u32, checkpattern: u32) -> Command { + let arg = voltage << 8 | checkpattern; + Command::no_data_cmd_r48(SEND_IF_COND, ResponseType::R7, arg) +} + +pub fn set_bus_width(arg: u32) -> Command { + Command::no_data_cmd_r48(ACMD_SET_BUS, ResponseType::R1, arg) +} + +/// ACMD41: App Op Command +pub fn sd_send_op_cond(host_high_capacity_support: bool, sr18: bool) -> Command { + let mut cmd = Command::default(); + let arg = u32::from(host_high_capacity_support) << 30 | u32::from(sr18) << 24 | 1 << 20; + cmd.arg = arg; + cmd.index = ACMD_SD_SEND_OP_COND; + cmd.resp_ty = ResponseType::R3; + cmd.reg_flags |= CmdMask::start_cmd.bits() + | CmdMask::use_hold_reg.bits() + | CmdMask::wait_prvdata_complete.bits() + | CmdMask::response_expect.bits(); + cmd +} diff --git a/src/driver/block/starfive_sdio/driver_/driver.rs b/src/driver/block/starfive_sdio/driver_/driver.rs new file mode 100644 index 0000000..694c108 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/driver.rs @@ -0,0 +1,448 @@ +use core::time::Duration; +use core::sync::atomic::{AtomicU16, Ordering}; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::format; + +use crate::driver::{BlockDriverOps, DeviceType, DriverOps}; +use crate::kernel::event::timer::{wait_until, spin_delay}; +use crate::{arch, kdebug, kinfo, kwarn}; + +use super::reg::*; +use super::cmd::*; +use super::err::*; +use super::sd_reg::*; + +pub struct Driver { + num: i32, + base: usize, + rca: AtomicU16, +} + +impl Driver { + pub fn new(num: i32, base: usize) -> Self { + Driver { num, base, rca: AtomicU16::new(0) } + } + + fn read_reg(&self, offset: usize) -> u32 { + unsafe { core::ptr::read_volatile((self.base + offset) as *const u32) } + } + + fn write_reg(&self, offset: usize, val: u32) { + unsafe { core::ptr::write_volatile((self.base + offset) as *mut u32, val); } + } + + fn wait_for_cmd_line(&self) -> Result<(), Timeout> { + if wait_until(Duration::from_millis(0xFF), || { + self.read_reg(REG_CMD) & CmdMask::start_cmd.bits() == 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitCmdLine) + } + } + + fn wait_for_cmd_done(&self) -> Result<(), Timeout> { + if wait_until(Duration::from_millis(0xFF), || { + self.read_reg(REG_RINTSTS) & InterruptMask::cmd.bits() != 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitCmdDone) + } + } + + fn wait_for_data_line(&self) -> Result<(), Timeout> { + if wait_until(Duration::from_millis(DATA_TMOUT_DEFUALT as u64 * 1000), || { + self.read_reg(REG_STATUS) & StatusMask::data_busy.bits() == 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitDataLine) + } + } + + fn wait_reset(&self, mask: u32) -> Result<(), Timeout> { + if wait_until(Duration::from_millis(10), || { + self.read_reg(REG_CTRL) & mask == 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitReset) + } + } + + fn reset_clock(&self, ena: u32, div: u32) -> Result<(), Timeout> { + self.wait_for_cmd_line()?; + self.write_reg(REG_CLKENA, 0); + self.write_reg(REG_CLKDIV, div); + + let cmd = up_clk(); + self.write_reg(REG_CMDARG, cmd.arg()); + self.write_reg(REG_CMD, cmd.to_cmd()); + if ena == 0 { + return Ok(()); + } + + self.wait_for_cmd_line()?; + self.write_reg(REG_CMD, cmd.to_cmd()); + + self.wait_for_cmd_line()?; + self.write_reg(REG_CLKENA, ena); + self.write_reg(REG_CMDARG, 0); + self.write_reg(REG_CMD, cmd.to_cmd()); + kdebug!("reset clock"); + + Ok(()) + } + + fn send_cmd(&self, cmd: Command) -> Result { + loop { + self.wait_for_data_line()?; + self.wait_for_cmd_line()?; + self.write_reg(REG_RINTSTS, InterruptMask::all().bits()); + self.write_reg(REG_CMDARG, cmd.arg()); + self.write_reg(REG_CMD, cmd.to_cmd()); + if self.read_reg(REG_RINTSTS) & InterruptMask::hle.bits() == 0 { + kdebug!("Send CMD {:?}", CmdMask::from_bits(cmd.to_cmd()).unwrap()); + break; + } + } + kdebug!( + "{:?}", + InterruptMask::from_bits(self.read_reg(REG_RINTSTS)).unwrap() + ); + kdebug!("{:?}", StatusMask::from_bits(self.read_reg(REG_STATUS)).unwrap()); + self.wait_for_cmd_done()?; + let resp = if cmd.resp_exp() { + let mask: u32 = self.read_reg(REG_RINTSTS); + if mask & InterruptMask::rto.bits() != 0 { + self.write_reg(REG_RINTSTS, mask); + kwarn!( + "Response Timeout, mask: {:?}", + InterruptMask::from_bits(mask).unwrap() + ); + return Err(Interrupt::ResponseTimeout.into()); + } else if mask & InterruptMask::re.bits() != 0 { + self.write_reg(REG_RINTSTS, mask); + kwarn!( + "Response Error, mask : {:?}", + InterruptMask::from_bits(mask).unwrap() + ); + return Err(Interrupt::ResponseErr.into()); + } + if cmd.resp_lang() { + let resp0 = self.read_reg(REG_RESP0); + let resp1 = self.read_reg(REG_RESP1); + let resp2 = self.read_reg(REG_RESP2); + let resp3 = self.read_reg(REG_RESP3); + Response::R136((resp0, resp1, resp2, resp3)) + } else { + Response::R48(self.read_reg(REG_RESP0)) + } + } else { + Response::Rz + }; + if cmd.data_exp() { + self.wait_reset(ControlMask::fifo_reset.bits())?; + self.write_reg(REG_BLKSIZ, BLKSIZ_DEFAULT); + self.write_reg(REG_BYTCNT, BLKSIZ_DEFAULT); + } + + Ok(resp) + } + + fn check_version(&self) -> Result<(), CardError> { + let cmd = send_if_cond(1, 0xAA); + let cic = self.send_cmd(cmd)?.cic(); + if cic.voltage_accepted() == 1 && cic.pattern() == 0xAA { + kdebug!("sd vision 2.0"); + spin_delay(Duration::from_millis(10)); + Ok(()) + } else { + Err(CardError::VoltagePattern) + } + } + + fn check_v18_sdhc(&self) -> Result<(), CardError> { + loop { + let cmd = app_cmd(0); + let status = self.send_cmd(cmd)?.card_status(); + kdebug!("{status:?}"); + let cmd = sd_send_op_cond(true, true); + let ocr = self.send_cmd(cmd)?.ocr(); + if !ocr.is_busy() { + if ocr.high_capacity() { + kdebug!("card is high capacity!"); + } + if ocr.v18_allowed() { + kdebug!("card can switch to 1.8 voltage!"); + } + break; + } + spin_delay(Duration::from_millis(10)); + } + spin_delay(Duration::from_millis(10)); + Ok(()) + } + + fn check_rca(&self) -> Result { + let cmd = send_relative_address(); + let rca = self.send_cmd(cmd)?.rca(); + kdebug!("{:?}", rca); + spin_delay(Duration::from_millis(10)); + Ok(rca) + } + + fn check_csd(&self, rca: Rca) -> Result<(), CardError> { + let cmd = send_csd(rca.address()); + let csd = self.send_cmd(cmd)?.csd(); + kdebug!("{:?}", csd); + spin_delay(Duration::from_millis(10)); + Ok(()) + } + + fn check_cid(&self) -> Result<(), CardError> { + let cmd = all_send_cid(); + let cid = self.send_cmd(cmd)?.cid(); + kdebug!("{:?}", cid); + spin_delay(Duration::from_millis(10)); + Ok(()) + } + + fn function_switch(&self, arg: u32) -> Result<(), CardError> { + let cmd = switch_function(arg); + let status = self.send_cmd(cmd)?.card_status(); + kdebug!("{:?}", status); + spin_delay(Duration::from_millis(10)); + Ok(()) + } + + fn set_bus(&self, rca: Rca) -> Result<(), CardError> { + self.send_cmd(app_cmd(rca.address()))?; + let status = self.send_cmd(set_bus_width(2))?.card_status(); + kdebug!("{:?}", status); + spin_delay(Duration::from_millis(10)); + Ok(()) +} + + fn sel_card(&self, rca: Rca) -> Result<(), CardError> { + let cmd = select_card(rca.address()); + let status = self.send_cmd(cmd)?.card_status(); + kdebug!("{:?}", status); + spin_delay(Duration::from_millis(10)); + Ok(()) + } + + pub fn init(&self) -> Result<(), CardError> { + let hconf = HardConf::from(self.read_reg(REG_HCON)); + kdebug!("{:?}", hconf); + + // Reset Control Register + let reset_mask = ControlMask::controller_reset.bits() + | ControlMask::fifo_reset.bits() + | ControlMask::dma_reset.bits(); + self.write_reg(REG_CTRL, reset_mask); + self.wait_reset(reset_mask)?; + + // Enable power + self.write_reg(REG_PWREN, 1); + self.reset_clock(1, 62)?; + self.write_reg(REG_TMOUT, 0xFFFFFFFF); + + // setup interrupt mask + self.write_reg(REG_RINTSTS, InterruptMask::all().bits()); + self.write_reg(REG_INTMASK, 0); + self.write_reg(REG_CTYPE, 1); + self.write_reg(REG_BMOD, 1); + + // Enumerate card stack + self.send_cmd(idle())?; + spin_delay(Duration::from_millis(10)); + // delay(Duration::from_millis(10)); + + self.check_version()?; + self.check_v18_sdhc()?; + self.check_cid()?; + let rca = self.check_rca()?; + self.rca.store(rca.address(), Ordering::Relaxed); + self.check_csd(rca)?; + self.sel_card(rca)?; + self.function_switch(16777201)?; + self.set_bus(rca)?; + self.reset_clock(1, 1)?; + + kinfo!("sdio init success!"); + Ok(()) + } + + fn stop_transmission_ops(&self) -> Result<(), CardError> { + let cmd = stop_transmission(); + loop { + self.wait_for_cmd_line()?; + self.write_reg(REG_RINTSTS, InterruptMask::all().bits()); + self.write_reg(REG_CMDARG, cmd.arg()); + self.write_reg(REG_CMD, cmd.to_cmd()); + if self.read_reg(REG_RINTSTS) & InterruptMask::hle.bits() == 0 { + kdebug!("send {:?}", CmdMask::from_bits(cmd.to_cmd()).unwrap()); + break; + } + } + let status = Response::R48(self.read_reg(REG_RESP0)).card_status(); + kdebug!("{status:?}"); + self.wait_for_cmd_done()?; + Ok(()) + } + + fn fifo_cnt(&self) -> u32 { + let status = self.read_reg(REG_STATUS); + (status >> 17) & 0x1FFF + } + + fn read_fifo(&self, offset: usize) -> u8 { + let addr = (self.base + 0x200 + offset) as *mut u8; + unsafe { addr.read_volatile() } + } + + fn write_fifo(&self, offset: usize, val: u8) { + let addr = (self.base + 0x200 + offset) as *mut u8; + unsafe { + addr.write_volatile(val); + } + } + + fn read_data(&self, buf: &mut [u8; BLKSIZ_DEFAULT as usize]) -> Result<(), CardError> { + let mut offset = 0; + // let timer = Timer::start(Duration::from_micros(DATA_TMOUT_DEFUALT as u64)); + let deadline = arch::get_time_us() + DATA_TMOUT_DEFUALT as u64; + loop { + let mask = self.read_reg(REG_RINTSTS); + if offset == BLKSIZ_DEFAULT as usize && InterruptMask::dto.bits() & mask != 0 { + break; + } + Interrupt::check(mask)?; + spin_delay(Duration::from_micros(10)); + + if arch::get_time_us() > deadline { + return Err(CardError::DataTransferTimeout); + } + + if mask & InterruptMask::rxdr.bits() != 0 || mask & InterruptMask::dto.bits() != 0 { + while self.fifo_cnt() > 1 { + buf[offset] = self.read_fifo(offset); + offset += 1; + } + self.write_reg(REG_RINTSTS, InterruptMask::rxdr.bits()); + } + } + self.write_reg(REG_RINTSTS, self.read_reg(REG_RINTSTS)); + Ok(()) + } + + fn write_data(&self, buf: &[u8; BLKSIZ_DEFAULT as usize]) -> Result<(), CardError> { + // let timer = Timer::start(Duration::from_micros(DATA_TMOUT_DEFUALT as u64)); + let deadline = arch::get_time_us() + DATA_TMOUT_DEFUALT as u64; + loop { + let mask = self.read_reg(REG_RINTSTS); + if InterruptMask::dto.bits() & mask != 0 { + break; + } + Interrupt::check(mask)?; + spin_delay(Duration::from_micros(10)); + if arch::get_time_us() > deadline { + return Err(CardError::DataTransferTimeout); + } + if mask & InterruptMask::txdr.bits() != 0 { + for offset in 0..BLKSIZ_DEFAULT as usize { + self.write_fifo(offset, buf[offset]) + } + self.write_reg(REG_RINTSTS, InterruptMask::txdr.bits()); + } + } + self.write_reg(REG_RINTSTS, self.read_reg(REG_RINTSTS)); + Ok(()) + } +} + +impl BlockDriverOps for Driver { + fn read_block(&self, block: usize, buf: &mut [u8]) -> Result<(), ()> { + debug_assert!(buf.len() == BLKSIZ_DEFAULT as usize); + + kdebug!("read block {}", block); + + let cmd = read_single_block(block as u32); + match self.send_cmd(cmd) { + Ok(resp) => { + let status = resp.card_status(); + kdebug!("{status:?}"); + if self.read_data(buf.try_into().unwrap()).is_err() { + self.stop_transmission_ops().map_err(|_| ()) + } else { + kdebug!("read block {} success, buf={:x?}", block, &buf[..32]); + Ok(()) + } + } + Err(err) => { + kdebug!("{err:?}"); + self.stop_transmission_ops().map_err(|_| ()) + } + } + } + + fn write_block(&self, block: usize, buf: &[u8]) -> Result<(), ()> { + kdebug!("write block {}", block); + debug_assert!(buf.len() == BLKSIZ_DEFAULT as usize); + + let cmd = write_single_block(block as u32); + match self.send_cmd(cmd) { + Ok(resp) => { + let status = resp.card_status(); + kdebug!("{status:?}"); + if self.write_data(buf.try_into().unwrap()).is_err() { + self.stop_transmission_ops().map_err(|_| ()) + } else { + kdebug!("write block {} success", block); + Ok(()) + } + } + Err(err) => { + kdebug!("{err:?}"); + self.stop_transmission_ops().map_err(|_| ()) + } + } + } + + fn flush(&self) -> Result<(), ()> { + Ok(()) + } + + fn close(&mut self) -> Result<(), ()> { + Ok(()) + } + + fn get_block_size(&self) -> u32 { + BLKSIZ_DEFAULT + } + + fn get_block_count(&self) -> u64 { + 0 + } +} + +impl DriverOps for Driver { + fn name(&self) -> &str { + "starfive_sdio" + } + + fn device_name(&self) -> String { + format!("sdio{}", self.num) + } + + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn as_block_driver(self: Arc) -> Arc { + self + } +} diff --git a/src/driver/block/starfive_sdio/driver_/err.rs b/src/driver/block/starfive_sdio/driver_/err.rs new file mode 100644 index 0000000..bcbbc60 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/err.rs @@ -0,0 +1,70 @@ +use super::reg::InterruptMask; +use core::fmt::Debug; + +#[derive(Debug, Clone, Copy)] +pub enum CardError { + CardInitErr, + InterruptErr(Interrupt), + TimeoutErr(Timeout), + VoltagePattern, + DataTransferTimeout, +} + +impl From for CardError { + fn from(value: Timeout) -> Self { + Self::TimeoutErr(value) + } +} + +impl From for CardError { + fn from(value: Interrupt) -> Self { + Self::InterruptErr(value) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Timeout { + WaitReset, + WaitCmdLine, + WaitCmdDone, + WaitDataLine, + FifoStatus, +} + +#[derive(Debug, Clone, Copy)] +pub enum Interrupt { + ResponseTimeout, + ResponseErr, + EndBitErr, + StartBitErr, + HardwareLock, + Fifo, + DataReadTimeout, + DataCrc, +} + +impl Interrupt { + pub fn check(mask: u32) -> Result<(), Interrupt> { + let mut ret = Ok(()); + + if mask & InterruptMask::dcrc.bits() != 0 { + ret = Err(Interrupt::DataCrc); + } + if mask & InterruptMask::drto.bits() != 0 { + ret = Err(Interrupt::DataReadTimeout); + } + if mask & InterruptMask::frun.bits() != 0 { + ret = Err(Interrupt::Fifo); + } + if mask & InterruptMask::hle.bits() != 0 { + ret = Err(Interrupt::HardwareLock); + } + if mask & InterruptMask::sbe.bits() != 0 { + ret = Err(Interrupt::StartBitErr); + } + if mask & InterruptMask::ebe.bits() != 0 { + ret = Err(Interrupt::EndBitErr); + } + ret + } +} diff --git a/src/driver/block/starfive_sdio/driver_/mod.rs b/src/driver/block/starfive_sdio/driver_/mod.rs new file mode 100644 index 0000000..16f6f89 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/mod.rs @@ -0,0 +1,7 @@ +mod driver; +mod reg; +mod err; +mod cmd; +mod sd_reg; + +pub use driver::Driver; diff --git a/src/driver/block/starfive_sdio/driver_/ops.rs b/src/driver/block/starfive_sdio/driver_/ops.rs new file mode 100644 index 0000000..66f9d66 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/ops.rs @@ -0,0 +1,312 @@ +use core::sync::atomic::AtomicU16; +use core::sync::atomic::Ordering; +use core::time::Duration; + +use crate::sd::cmd::*; +use crate::sd::reg::*; +use crate::sd::sd_reg::*; +use crate::sd::utils::*; +use crate::timer::delay; +use crate::timer::Timer; +use log::{debug, error, info}; + +use super::err::*; +static RCA: AtomicU16 = AtomicU16::new(0); + +fn send_cmd(cmd: Command) -> Result { + loop { + wait_for_data_line()?; + wait_for_cmd_line()?; + write_reg(REG_RINTSTS, InterruptMask::all().bits()); + write_reg(REG_CMDARG, cmd.arg()); + write_reg(REG_CMD, cmd.to_cmd()); + if read_reg(REG_RINTSTS) & InterruptMask::hle.bits() == 0 { + debug!("Send CMD {:?}", CmdMask::from_bits(cmd.to_cmd()).unwrap()); + break; + } + } + debug!( + "{:?}", + InterruptMask::from_bits(read_reg(REG_RINTSTS)).unwrap() + ); + debug!("{:?}", StatusMask::from_bits(read_reg(REG_STATUS)).unwrap()); + wait_for_cmd_done()?; + let resp = if cmd.resp_exp() { + let mask: u32 = read_reg(REG_RINTSTS); + if mask & InterruptMask::rto.bits() != 0 { + write_reg(REG_RINTSTS, mask); + error!( + "Response Timeout, mask: {:?}", + InterruptMask::from_bits(mask).unwrap() + ); + return Err(Interrupt::ResponseTimeout.into()); + } else if mask & InterruptMask::re.bits() != 0 { + write_reg(REG_RINTSTS, mask); + error!( + "Response Error, mask : {:?}", + InterruptMask::from_bits(mask).unwrap() + ); + return Err(Interrupt::ResponseErr.into()); + } + if cmd.resp_lang() { + let resp0 = read_reg(REG_RESP0); + let resp1 = read_reg(REG_RESP1); + let resp2 = read_reg(REG_RESP2); + let resp3 = read_reg(REG_RESP3); + Response::R136((resp0, resp1, resp2, resp3)) + } else { + Response::R48(read_reg(REG_RESP0)) + } + } else { + Response::Rz + }; + if cmd.data_exp() { + wait_reset(ControlMask::fifo_reset.bits())?; + write_reg(REG_BLKSIZ, BLKSIZ_DEFAULT); + write_reg(REG_BYTCNT, BLKSIZ_DEFAULT); + } + + Ok(resp) +} + +fn read_data(buf: &mut [u8; BLKSIZ_DEFAULT as usize]) -> Result<(), CardError> { + let mut offset = 0; + let timer = Timer::start(Duration::from_micros(DATA_TMOUT_DEFUALT as u64)); + loop { + let mask = read_reg(REG_RINTSTS); + if offset == BLKSIZ_DEFAULT as usize && InterruptMask::dto.bits() & mask != 0 { + break; + } + Interrupt::check(mask)?; + delay(Duration::from_micros(10)); + if timer.timeout() { + return Err(CardError::DataTransferTimeout); + } + if mask & InterruptMask::rxdr.bits() != 0 || mask & InterruptMask::dto.bits() != 0 { + while fifo_cnt() > 1 { + buf[offset] = read_fifo(offset); + offset += 1; + } + write_reg(REG_RINTSTS, InterruptMask::rxdr.bits()); + } + } + write_reg(REG_RINTSTS, read_reg(REG_RINTSTS)); + Ok(()) +} + +fn write_data(buf: &[u8; BLKSIZ_DEFAULT as usize]) -> Result<(), CardError> { + let timer = Timer::start(Duration::from_micros(DATA_TMOUT_DEFUALT as u64)); + loop { + let mask = read_reg(REG_RINTSTS); + if InterruptMask::dto.bits() & mask != 0 { + break; + } + Interrupt::check(mask)?; + delay(Duration::from_micros(10)); + if timer.timeout() { + return Err(CardError::DataTransferTimeout); + } + if mask & InterruptMask::txdr.bits() != 0 { + for offset in 0..BLKSIZ_DEFAULT as usize { + write_fifo(offset, buf[offset]) + } + write_reg(REG_RINTSTS, InterruptMask::txdr.bits()); + } + } + write_reg(REG_RINTSTS, read_reg(REG_RINTSTS)); + Ok(()) +} + +fn reset_clock(ena: u32, div: u32) -> Result<(), Timeout> { + wait_for_cmd_line()?; + write_reg(REG_CLKENA, 0); + write_reg(REG_CLKDIV, div); + let cmd = up_clk(); + write_reg(REG_CMDARG, cmd.arg()); + write_reg(REG_CMD, cmd.to_cmd()); + if ena == 0 { + return Ok(()); + } + wait_for_cmd_line()?; + write_reg(REG_CMD, cmd.to_cmd()); + wait_for_cmd_line()?; + write_reg(REG_CLKENA, ena); + write_reg(REG_CMDARG, 0); + write_reg(REG_CMD, cmd.to_cmd()); + debug!("reset clock"); + Ok(()) +} + +pub(crate) fn init_card() -> Result<(), CardError> { + info!("init sdio..."); + let hconf = HardConf::from(read_reg(REG_HCON)); + debug!("{hconf:?}"); + // Reset Control Register + let reset_mask = ControlMask::controller_reset.bits() + | ControlMask::fifo_reset.bits() + | ControlMask::dma_reset.bits(); + write_reg(REG_CTRL, reset_mask); + wait_reset(reset_mask)?; + // enable power + write_reg(REG_PWREN, 1); + reset_clock(1, 62)?; + write_reg(REG_TMOUT, 0xFFFFFFFF); + // setup interrupt mask + write_reg(REG_RINTSTS, InterruptMask::all().bits()); + write_reg(REG_INTMASK, 0); + write_reg(REG_CTYPE, 1); + write_reg(REG_BMOD, 1); + // // enumerate card stack + send_cmd(idle())?; + delay(Duration::from_millis(10)); + check_version()?; + check_v18_sdhc()?; + check_cid()?; + let rca = check_rca()?; + RCA.store(rca.address(), Ordering::Relaxed); + check_csd(rca)?; + sel_card(rca)?; + function_switch(16777201)?; + set_bus(rca)?; + reset_clock(1, 1)?; + info!("sdio init success!"); + Ok(()) +} + +fn check_version() -> Result<(), CardError> { + let cmd = send_if_cond(1, 0xAA); + let cic = send_cmd(cmd)?.cic(); + if cic.voltage_accepted() == 1 && cic.pattern() == 0xAA { + debug!("sd vision 2.0"); + delay(Duration::from_millis(10)); + Ok(()) + } else { + Err(CardError::VoltagePattern) + } +} + +fn check_v18_sdhc() -> Result<(), CardError> { + loop { + let cmd = app_cmd(0); + let status = send_cmd(cmd)?.card_status(); + debug!("{status:?}"); + let cmd = sd_send_op_cond(true, true); + let ocr = send_cmd(cmd)?.ocr(); + if !ocr.is_busy() { + if ocr.high_capacity() { + debug!("card is high capacity!"); + } + if ocr.v18_allowed() { + debug!("card can switch to 1.8 voltage!"); + } + break; + } + delay(Duration::from_millis(10)); + } + delay(Duration::from_millis(10)); + Ok(()) +} + +fn check_rca() -> Result { + let cmd = send_relative_address(); + let rca = send_cmd(cmd)?.rca(); + debug!("{:?}", rca); + delay(Duration::from_millis(10)); + Ok(rca) +} + +fn check_cid() -> Result<(), CardError> { + let cmd = all_send_cid(); + let cid = send_cmd(cmd)?.cid(); + debug!("{:?}", cid); + delay(Duration::from_millis(10)); + Ok(()) +} + +fn check_csd(rca: Rca) -> Result<(), CardError> { + let cmd = send_csd(rca.address()); + let csd = send_cmd(cmd)?.csd(); + debug!("{:?}", csd); + delay(Duration::from_millis(10)); + Ok(()) +} + +fn sel_card(rca: Rca) -> Result<(), CardError> { + let cmd = select_card(rca.address()); + let status = send_cmd(cmd)?.card_status(); + debug!("{:?}", status); + delay(Duration::from_millis(10)); + Ok(()) +} + +fn function_switch(arg: u32) -> Result<(), CardError> { + let cmd = switch_function(arg); + let status = send_cmd(cmd)?.card_status(); + debug!("{:?}", status); + delay(Duration::from_millis(10)); + Ok(()) +} + +fn set_bus(rca: Rca) -> Result<(), CardError> { + send_cmd(app_cmd(rca.address()))?; + let status = send_cmd(set_bus_width(2))?.card_status(); + debug!("{:?}", status); + delay(Duration::from_millis(10)); + Ok(()) +} + +fn stop_transmission_ops() -> Result<(), CardError> { + let cmd = stop_transmission(); + loop { + wait_for_cmd_line()?; + write_reg(REG_RINTSTS, InterruptMask::all().bits()); + write_reg(REG_CMDARG, cmd.arg()); + write_reg(REG_CMD, cmd.to_cmd()); + if read_reg(REG_RINTSTS) & InterruptMask::hle.bits() == 0 { + debug!("send {:?}", CmdMask::from_bits(cmd.to_cmd()).unwrap()); + break; + } + } + let status = Response::R48(read_reg(REG_RESP0)).card_status(); + debug!("{status:?}"); + wait_for_cmd_done()?; + Ok(()) +} + +pub(crate) fn read_block(buf: &mut [u8; 512], addr: u32) -> Result<(), CardError> { + let cmd = read_single_block(addr); + match send_cmd(cmd) { + Ok(resp) => { + let status = resp.card_status(); + debug!("{status:?}"); + if read_data(buf).is_err() { + stop_transmission_ops() + } else { + Ok(()) + } + } + Err(err) => { + debug!("{err:?}"); + stop_transmission_ops() + } + } +} + +pub(crate) fn write_block(buf: &[u8; BLKSIZ_DEFAULT as usize], addr: u32) -> Result<(), CardError> { + let cmd = write_single_block(addr); + match send_cmd(cmd) { + Ok(resp) => { + let status = resp.card_status(); + debug!("{status:?}"); + if write_data(buf).is_err() { + stop_transmission_ops() + } else { + Ok(()) + } + } + Err(err) => { + debug!("{err:?}"); + stop_transmission_ops() + } + } +} diff --git a/src/driver/block/starfive_sdio/driver_/reg.rs b/src/driver/block/starfive_sdio/driver_/reg.rs new file mode 100644 index 0000000..a4cb166 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/reg.rs @@ -0,0 +1,256 @@ +use core::fmt::Debug; + +use bitflags::bitflags; + +pub(crate) const REG_CTRL: usize = 0x000; +bitflags! { + pub(crate) struct ControlMask: u32{ + const use_internal_dmac = 0b1 << 25; + const enable_od_pullup = 0b1 << 24; + const card_voltage_b = 0b1111 << 20; + const card_voltage_a = 0b1111 << 16; + const ceata_device_interrupt = 0b1 << 11; + const send_auto_stop_ccsd = 0b1 << 10; + const send_ccsd = 0b1 << 9; + const abort_read_data = 0b1 << 8; + const send_irq_response = 0b1 << 7; + const read_wait = 0b1 << 6; + const dma_enable = 0b1 << 5; + const int_enable = 0b1 << 4; + const dma_reset = 0b1 << 2; + const fifo_reset = 0b1 << 1; + const controller_reset = 0b1; + } +} +pub(crate) const REG_PWREN: usize = 0x004; + +pub(crate) const REG_CLKDIV: usize = 0x008; +pub(crate) const REG_CLKENA: usize = 0x010; + +pub(crate) const REG_TMOUT: usize = 0x014; +pub(crate) const DATA_TMOUT_DEFUALT: u32 = 0xFFFFFF << 8; +pub(crate) const REG_CTYPE: usize = 0x018; +pub(crate) const REG_BLKSIZ: usize = 0x01C; +pub(crate) const BLKSIZ_DEFAULT: u32 = 0x200; + +pub(crate) const REG_BYTCNT: usize = 0x020; + +pub(crate) const REG_INTMASK: usize = 0x024; +pub(crate) const REG_RINTSTS: usize = 0x044; + +bitflags! { + #[derive(Debug)] + pub(crate) struct InterruptMask: u32{ + const sdio_int_mask = 0xFFFF << 16; + const ebe = 0b1 << 15; + const acd = 0b1 << 14; + const sbe = 0b1 << 13; + const hle = 0b1 << 12; + const frun = 0b1 << 11; + const hto = 0b1 << 10; + const drto = 0b1 << 9; + const rto = 0b1 << 8; + const dcrc = 0b1 << 7; + const rcrc = 0b1 << 6; + const rxdr = 0b1 << 5; + const txdr = 0b1 << 4; + const dto = 0b1 << 3; + const cmd = 0b1 << 2; + const re = 0b1 << 1; + const cd = 0b1; + } +} + +pub(crate) const REG_CMDARG: usize = 0x028; +pub(crate) const REG_CMD: usize = 0x02C; +bitflags! { + #[derive(Debug)] + pub(crate) struct CmdMask:u32{ + const start_cmd = 0b1 << 31; + const use_hold_reg = 0b1 << 29; + const volt_switch = 0b1 << 28; + const boot_mode = 0b1 << 27; + const disable_boot = 0b1 << 26; + const expect_boot_ack = 0b1 << 25; + const enable_boot = 0b1 << 24; + const ccs_expected = 0b1 << 23; + const read_ceata_device = 0b1 << 22; + const update_clock_registers_only = 0b1 << 21; + const card_number = 0b11111 << 16; + const send_initialization = 0b1 << 15; + const stop_abort_cmd = 0b1 << 14; + const wait_prvdata_complete = 0b1 << 13; + const send_auto_stop = 0b1 << 12; + const transfer_mode = 0b1 << 11; + const write = 0b1 << 10; + const data_expected = 0b1 << 9; + const check_response_crc = 0b1 << 8; + const response_length = 0b1 << 7; + const response_expect = 0b1 << 6; + const cmd_index = 0x3F; + } +} + +pub(crate) const REG_RESP0: usize = 0x030; +pub(crate) const REG_RESP1: usize = 0x034; +pub(crate) const REG_RESP2: usize = 0x038; +pub(crate) const REG_RESP3: usize = 0x03C; +pub(crate) const REG_STATUS: usize = 0x048; + +bitflags! { + #[derive(Debug)] + pub(crate) struct StatusMask:u32{ + const dma_req= 0b1<< 31; + const dma_ack= 0b1<< 30; + const fifo_count= 0x1FFF<< 17; + const response_index= 0x3F<< 11; + const data_state_mc_busy= 0b1<< 10; + const data_busy= 0b1<< 9; + const data_3_status= 0b1<< 8; + const command_fsm_states= 0xF<< 4; + const fifo_full= 0b1<< 3; + const fifo_empty= 0b1<< 2; + const fifo_tx_watermark= 0b1<< 1; + const fifo_rx_watermark= 0b1; + } +} + +// pub(crate) const REG_FIFOTH: u32 = 0x04C; +// pub(crate) const REG_CDETECT: u32 = 0x050; +// pub(crate) const REG_WRTPRT: u32 = 0x054; +// pub(crate) const REG_GPIO: u32 = 0x058; +// pub(crate) const REG_TCMCNT: u32 = 0x05C; +// pub(crate) const REG_TBBCNT: u32 = 0x060; +// pub(crate) const REG_DEBNCE: u32 = 0x064; +// pub(crate) const REG_USRID: u32 = 0x068; +// pub(crate) const REG_VERID: u32 = 0x06C; +pub(crate) const REG_HCON: usize = 0x070; + +bitflags! { + #[derive(Debug)] + struct HardConfig: u32{ + const card_type = 0x1; + const num_cards_sub1 = 0x1F<<1; + const h_bus_type = 0x1 << 6; + const h_data_width = 0b111<<7; + const h_addr_width = 0x3F << 10; + const dma_interface = 0b11 << 16; + const ge_dma_data_width = 0b111 << 18; + const fifo_ram_inside = 0b1 << 21; + const impl_hold_reg = 0b1 << 22; + const set_clk_false_path = 0b1 <<23; + const num_clk_div_sub1 = 0b11<<24; + const area_optimized = 0b1 << 26; + const fifo_depth = 0x1F << 27; + } +} + +pub(crate) struct HardConf(u32); +impl From for HardConf { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl Debug for HardConf { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut f = f.debug_map(); + let conf = self.0; + if conf | HardConfig::card_type.bits() != 0 { + f.entry(&HardConfig::card_type, &"SD_MMC"); + } else { + f.entry(&HardConfig::card_type, &"MMC_ONLY"); + } + f.entry( + &HardConfig::num_cards_sub1, + &((conf & HardConfig::num_cards_sub1.bits()) >> 1), + ); + if conf | HardConfig::h_bus_type.bits() != 0 { + f.entry(&HardConfig::h_bus_type, &"AHB"); + } else { + f.entry(&HardConfig::h_bus_type, &"APB"); + } + let h_data = (conf & HardConfig::h_data_width.bits()) >> 7; + match h_data { + 0 => { + f.entry(&HardConfig::h_data_width, &"16 bit"); + } + 1 => { + f.entry(&HardConfig::h_data_width, &"32 bit"); + } + 2 => { + f.entry(&HardConfig::h_data_width, &"64 bit"); + } + _ => { + f.entry(&HardConfig::h_data_width, &"Other"); + } + } + f.entry( + &HardConfig::h_addr_width, + &((conf & HardConfig::h_addr_width.bits()) >> 10), + ); + let dma_ifc = (conf & HardConfig::dma_interface.bits()) >> 16; + match dma_ifc { + 0 => { + f.entry(&HardConfig::dma_interface, &"None"); + } + 1 => { + f.entry(&HardConfig::dma_interface, &"DW_DMA"); + } + 2 => { + f.entry(&HardConfig::dma_interface, &"GENERIC_DMA"); + } + 3 => { + f.entry(&HardConfig::dma_interface, &"NON-DW-DMA"); + } + _ => {} + } + let ge_dma = (conf & HardConfig::ge_dma_data_width.bits()) >> 18; + match ge_dma { + 0 => { + f.entry(&HardConfig::ge_dma_data_width, &"None"); + } + 1 => { + f.entry(&HardConfig::ge_dma_data_width, &"DW_DMA"); + } + 2 => { + f.entry(&HardConfig::ge_dma_data_width, &"GENERIC_DMA"); + } + _ => { + f.entry(&HardConfig::ge_dma_data_width, &"Unknown"); + } + } + if conf | HardConfig::fifo_ram_inside.bits() != 0 { + f.entry(&HardConfig::fifo_ram_inside, &"INSIDE"); + } else { + f.entry(&HardConfig::fifo_ram_inside, &"OUTSIDE"); + } + if conf | HardConfig::impl_hold_reg.bits() != 0 { + f.entry(&HardConfig::impl_hold_reg, &"hold register"); + } else { + f.entry(&HardConfig::impl_hold_reg, &"no hold register"); + } + if conf | HardConfig::set_clk_false_path.bits() != 0 { + f.entry(&HardConfig::set_clk_false_path, &"false path set"); + } else { + f.entry(&HardConfig::set_clk_false_path, &"no false path"); + } + f.entry( + &HardConfig::num_clk_div_sub1, + &((conf & HardConfig::num_clk_div_sub1.bits()) >> 24), + ); + if conf | HardConfig::area_optimized.bits() != 0 { + f.entry(&HardConfig::area_optimized, &"Area optimization"); + } else { + f.entry(&HardConfig::area_optimized, &"no area optimization"); + } + f.entry( + &HardConfig::fifo_depth, + &((conf & HardConfig::fifo_depth.bits()) >> 27), + ); + f.finish() + } +} + +pub(crate) const REG_BMOD: usize = 0x080; + diff --git a/src/driver/block/starfive_sdio/driver_/sd_reg.rs b/src/driver/block/starfive_sdio/driver_/sd_reg.rs new file mode 100644 index 0000000..b422790 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/sd_reg.rs @@ -0,0 +1,711 @@ +use core::{fmt::Debug, str}; + +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SDSpecVersion { + V1_0, + V1_10, + V2, + V3, + V4, + V5, + V6, + V7, + Unknown, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[allow(unused)] +pub enum BusWidth { + #[non_exhaustive] + Unknown, + One = 1, + Four = 4, + Eight = 8, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum BlockSize { + #[non_exhaustive] + B1 = 0, + B2 = 1, + B4 = 2, + B8 = 3, + B16 = 4, + B32 = 5, + B64 = 6, + B128 = 7, + B256 = 8, + B512 = 9, + B1024 = 10, + B2048 = 11, + B4096 = 12, + B8192 = 13, + B16kB = 14, + Unknown = 15, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[allow(non_camel_case_types)] +pub enum CurrentConsumption { + I_0mA, + I_1mA, + I_5mA, + I_10mA, + I_25mA, + I_35mA, + I_45mA, + I_60mA, + I_80mA, + I_100mA, + I_200mA, +} + +impl From<&CurrentConsumption> for u32 { + fn from(i: &CurrentConsumption) -> u32 { + match i { + CurrentConsumption::I_0mA => 0, + CurrentConsumption::I_1mA => 1, + CurrentConsumption::I_5mA => 5, + CurrentConsumption::I_10mA => 10, + CurrentConsumption::I_25mA => 25, + CurrentConsumption::I_35mA => 35, + CurrentConsumption::I_45mA => 45, + CurrentConsumption::I_60mA => 60, + CurrentConsumption::I_80mA => 80, + CurrentConsumption::I_100mA => 100, + CurrentConsumption::I_200mA => 200, + } + } +} +impl CurrentConsumption { + fn from_minimum_reg(reg: u128) -> CurrentConsumption { + match reg & 0x7 { + 0 => CurrentConsumption::I_0mA, + 1 => CurrentConsumption::I_1mA, + 2 => CurrentConsumption::I_5mA, + 3 => CurrentConsumption::I_10mA, + 4 => CurrentConsumption::I_25mA, + 5 => CurrentConsumption::I_35mA, + 6 => CurrentConsumption::I_60mA, + _ => CurrentConsumption::I_100mA, + } + } + fn from_maximum_reg(reg: u128) -> CurrentConsumption { + match reg & 0x7 { + 0 => CurrentConsumption::I_1mA, + 1 => CurrentConsumption::I_5mA, + 2 => CurrentConsumption::I_10mA, + 3 => CurrentConsumption::I_25mA, + 4 => CurrentConsumption::I_35mA, + 5 => CurrentConsumption::I_45mA, + 6 => CurrentConsumption::I_80mA, + _ => CurrentConsumption::I_200mA, + } + } +} +impl Debug for CurrentConsumption { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let ma: u32 = self.into(); + write!(f, "{} mA", ma) + } +} + +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +#[allow(dead_code)] +pub enum CurrentState { + Ready = 1, + Identification = 2, + Standby = 3, + Transfer = 4, + Sending = 5, + Receiving = 6, + Programming = 7, + Disconnected = 8, + BusTest = 9, + Sleep = 10, + Error = 128, +} + +impl From for CurrentState { + fn from(n: u8) -> Self { + match n { + 1 => Self::Ready, + 2 => Self::Identification, + 3 => Self::Standby, + 4 => Self::Transfer, + 5 => Self::Sending, + 6 => Self::Receiving, + 7 => Self::Programming, + 8 => Self::Disconnected, + 9 => Self::BusTest, + 10 => Self::Sleep, + _ => Self::Error, + } + } +} +#[derive(Copy, Clone, Default)] +pub struct Ocr(u32); +impl From for Ocr { + fn from(value: u32) -> Self { + Self(value) + } +} +impl Ocr { + pub fn is_busy(&self) -> bool { + self.0 & 0x8000_0000 == 0 + } + pub fn voltage_window_mv(&self) -> Option<(u16, u16)> { + let mut window = (self.0 >> 15) & 0x1FF; + let mut min = 2_700; + + while window & 1 == 0 && window != 0 { + min += 100; + window >>= 1; + } + let mut max = min; + while window != 0 { + max += 100; + window >>= 1; + } + + if max == min { + None + } else { + Some((min, max)) + } + } + + pub fn v18_allowed(&self) -> bool { + self.0 & 0x0100_0000 != 0 + } + + pub fn over_2tb(&self) -> bool { + self.0 & 0x0800_0000 != 0 + } + + pub fn uhs2_card_status(&self) -> bool { + self.0 & 0x2000_0000 != 0 + } + + pub fn high_capacity(&self) -> bool { + self.0 & 0x4000_0000 != 0 + } +} + +impl Debug for Ocr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("OCR: Operation Conditions Register") + .field( + "Voltage Window (mV)", + &self.voltage_window_mv().unwrap_or((0, 0)), + ) + .field("S18A (UHS-I only)", &self.v18_allowed()) + .field("Over 2TB flag (SDUC only)", &self.over_2tb()) + .field("UHS-II Card", &self.uhs2_card_status()) + .field( + "Card Capacity Status (CSS)", + &if self.high_capacity() { + "SDHC/SDXC/SDUC" + } else { + "SDSC" + }, + ) + .field("Busy", &self.is_busy()) + .finish() + } +} +#[derive(Copy, Clone, Default)] +pub struct Cid { + inner: u128, + bytes: [u8; 16], +} +impl From<(u32, u32, u32, u32)> for Cid { + fn from(value: (u32, u32, u32, u32)) -> Self { + let inner = (value.3 as u128) << 96 + | (value.2 as u128) << 64 + | (value.1 as u128) << 32 + | (value.0 as u128); + Self { + inner, + bytes: inner.to_be_bytes(), + } + } +} + +impl From for Cid { + fn from(value: u128) -> Self { + Self { + inner: value, + bytes: value.to_be_bytes(), + } + } +} + +impl Cid { + pub fn manufacturer_id(&self) -> u8 { + self.bytes[0] + } + #[allow(unused)] + pub fn crc7(&self) -> u8 { + (self.bytes[15] >> 1) & 0x7F + } + + pub fn oem_id(&self) -> &str { + str::from_utf8(&self.bytes[1..3]).unwrap_or(&"") + } + + pub fn product_name(&self) -> &str { + str::from_utf8(&self.bytes[3..8]).unwrap_or(&"") + } + + pub fn product_revision(&self) -> u8 { + self.bytes[8] + } + + pub fn serial(&self) -> u32 { + (self.inner >> 24) as u32 + } + + pub fn manufacturing_date(&self) -> (u8, u16) { + ( + (self.inner >> 8) as u8 & 0xF, // Month + ((self.inner >> 12) as u16 & 0xFF) + 2000, // Year + ) + } +} + +impl Debug for Cid { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CID: Card Identification") + .field("Manufacturer ID", &self.manufacturer_id()) + .field("OEM ID", &self.oem_id()) + .field("Product Name", &self.product_name()) + .field("Product Revision", &self.product_revision()) + .field("Product Serial Number", &self.serial()) + .field("Manufacturing Date", &self.manufacturing_date()) + .finish() + } +} +#[derive(Copy, Clone, Default)] +pub struct Csd(u128); +impl From<(u32, u32, u32, u32)> for Csd { + fn from(value: (u32, u32, u32, u32)) -> Self { + let inner = (value.3 as u128) << 96 + | (value.2 as u128) << 64 + | (value.1 as u128) << 32 + | (value.0 as u128); + Self(inner) + } +} + +impl Csd { + pub fn version(&self) -> u8 { + (self.0 >> 126) as u8 & 3 + } + + pub fn transfer_rate(&self) -> u8 { + (self.0 >> 96) as u8 + } + + pub fn block_length(&self) -> BlockSize { + // Read block length + match (self.0 >> 80) & 0xF { + 0 => BlockSize::B1, + 1 => BlockSize::B2, + 2 => BlockSize::B4, + 3 => BlockSize::B8, + 4 => BlockSize::B16, + 5 => BlockSize::B32, + 6 => BlockSize::B64, + 7 => BlockSize::B128, + 8 => BlockSize::B256, + 9 => BlockSize::B512, + 10 => BlockSize::B1024, + 11 => BlockSize::B2048, + 12 => BlockSize::B4096, + 13 => BlockSize::B8192, + 14 => BlockSize::B16kB, + _ => BlockSize::Unknown, + } + } + + pub fn read_current_minimum_vdd(&self) -> CurrentConsumption { + CurrentConsumption::from_minimum_reg((self.0 >> 59) & 0x7) + } + + pub fn write_current_minimum_vdd(&self) -> CurrentConsumption { + CurrentConsumption::from_minimum_reg((self.0 >> 56) & 0x7) + } + + pub fn read_current_maximum_vdd(&self) -> CurrentConsumption { + CurrentConsumption::from_maximum_reg((self.0 >> 53) & 0x7) + } + + pub fn write_current_maximum_vdd(&self) -> CurrentConsumption { + CurrentConsumption::from_maximum_reg((self.0 >> 50) & 0x7) + } + + pub fn block_count(&self) -> u64 { + match self.version() { + 0 => { + // SDSC + let c_size: u16 = ((self.0 >> 62) as u16) & 0xFFF; + let c_size_mult: u8 = ((self.0 >> 47) as u8) & 7; + + ((c_size + 1) as u64) * ((1 << (c_size_mult + 2)) as u64) + } + 1 => { + // SDHC/SDXC + (((self.0 >> 48) as u64 & 0x3F_FFFF) + 1) * 1024 + } + 2 => { + // SDUC + (((self.0 >> 48) as u64 & 0xFFF_FFFF) + 1) * 1024 + } + _ => 0, + } + } + + pub fn card_size(&self) -> u64 { + let block_size_bytes = 1 << self.block_length() as u64; + + self.block_count() * block_size_bytes + } + + pub fn erase_size_blocks(&self) -> u32 { + if (self.0 >> 46) & 1 == 1 { + // ERASE_BLK_EN + 1 + } else { + let sector_size_tens = (self.0 >> 43) & 0x7; + let sector_size_units = (self.0 >> 39) & 0xF; + + (sector_size_tens as u32 * 10) + (sector_size_units as u32) + } + } +} + +impl Debug for Csd { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CSD: Card Specific Data") + .field("Transfer Rate", &self.transfer_rate()) + .field("Block Count", &self.block_count()) + .field("Card Size (bytes)", &self.card_size()) + .field("Read I (@min VDD)", &self.read_current_minimum_vdd()) + .field("Write I (@min VDD)", &self.write_current_minimum_vdd()) + .field("Read I (@max VDD)", &self.read_current_maximum_vdd()) + .field("Write I (@max VDD)", &self.write_current_maximum_vdd()) + .field("Erase Size (Blocks)", &self.erase_size_blocks()) + .finish() + } +} +#[derive(Copy, Clone, Default)] +pub struct CardStatus(u32); +impl From for CardStatus { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl CardStatus { + pub fn ecc_disabled(&self) -> bool { + self.0 & 0x4000 != 0 + } + + pub fn fx_event(&self) -> bool { + self.0 & 0x40 != 0 + } + + pub fn ake_seq_error(&self) -> bool { + self.0 & 0x8 != 0 + } + + pub fn out_of_range(&self) -> bool { + self.0 & 0x8000_0000 != 0 + } + + pub fn address_error(&self) -> bool { + self.0 & 0x4000_0000 != 0 + } + + pub fn block_len_error(&self) -> bool { + self.0 & 0x2000_0000 != 0 + } + + pub fn erase_seq_error(&self) -> bool { + self.0 & 0x1000_0000 != 0 + } + + pub fn erase_param(&self) -> bool { + self.0 & 0x800_0000 != 0 + } + + pub fn wp_violation(&self) -> bool { + self.0 & 0x400_0000 != 0 + } + + pub fn card_is_locked(&self) -> bool { + self.0 & 0x200_0000 != 0 + } + + pub fn lock_unlock_failed(&self) -> bool { + self.0 & 0x100_0000 != 0 + } + + pub fn com_crc_error(&self) -> bool { + self.0 & 0x80_0000 != 0 + } + + pub fn illegal_command(&self) -> bool { + self.0 & 0x40_0000 != 0 + } + + pub fn card_ecc_failed(&self) -> bool { + self.0 & 0x20_0000 != 0 + } + + pub fn cc_error(&self) -> bool { + self.0 & 0x10_0000 != 0 + } + + pub fn error(&self) -> bool { + self.0 & 0x8_0000 != 0 + } + + pub fn csd_overwrite(&self) -> bool { + self.0 & 0x1_0000 != 0 + } + + pub fn wp_erase_skip(&self) -> bool { + self.0 & 0x8000 != 0 + } + + pub fn erase_reset(&self) -> bool { + self.0 & 0x2000 != 0 + } + + pub fn state(&self) -> CurrentState { + CurrentState::from(((self.0 >> 9) & 0xF) as u8) + } + + pub fn ready_for_data(&self) -> bool { + self.0 & 0x100 != 0 + } + + pub fn app_cmd(&self) -> bool { + self.0 & 0x20 != 0 + } +} +impl Debug for CardStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Card Status") + .field("Out of range error", &self.out_of_range()) + .field("Address error", &self.address_error()) + .field("Block len error", &self.block_len_error()) + .field("Erase seq error", &self.erase_seq_error()) + .field("Erase param error", &self.erase_param()) + .field("Write protect error", &self.wp_violation()) + .field("Card locked", &self.card_is_locked()) + .field("Password lock unlock error", &self.lock_unlock_failed()) + .field( + "Crc check for the previous command failed", + &self.com_crc_error(), + ) + .field("Illegal command", &self.illegal_command()) + .field("Card internal ecc failed", &self.card_ecc_failed()) + .field("Internal card controller error", &self.cc_error()) + .field("General Error", &self.error()) + .field("Csd error", &self.csd_overwrite()) + .field("Write protect error", &self.wp_erase_skip()) + .field("Command ecc disabled", &self.ecc_disabled()) + .field("Erase sequence cleared", &self.erase_reset()) + .field("Card state", &self.state()) + .field("Buffer empty", &self.ready_for_data()) + .field("Extension event", &self.fx_event()) + .field("Card expects app cmd", &self.app_cmd()) + .field("Auth process error", &self.ake_seq_error()) + .finish() + } +} +#[derive(Copy, Clone, Default)] +pub struct Rca(u32); +impl From for Rca { + fn from(value: u32) -> Self { + Self(value) + } +} +impl Rca { + pub fn address(&self) -> u16 { + (self.0 >> 16) as u16 + } + + pub fn status(&self) -> u16 { + self.0 as u16 + } +} + +impl Debug for Rca { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Rca") + .field("address", &self.address()) + .field("status", &self.status()) + .finish() + } +} + +#[derive(Copy, Clone, Default)] +pub struct Scr(u64); +impl From<(u32, u32)> for Scr { + fn from(value: (u32, u32)) -> Self { + let val = (value.1 as u64) << 32 | (value.0 as u64); + Self(val) + } +} + +impl Scr { + pub fn version(&self) -> SDSpecVersion { + let spec = (self.0 >> 56) & 0xF; + let spec3 = (self.0 >> 47) & 1; + let spec4 = (self.0 >> 42) & 1; + let specx = (self.0 >> 38) & 0xF; + + // Ref PLSS_v7_10 Table 5-17 + match (spec, spec3, spec4, specx) { + (0, 0, 0, 0) => SDSpecVersion::V1_0, + (1, 0, 0, 0) => SDSpecVersion::V1_10, + (2, 0, 0, 0) => SDSpecVersion::V2, + (2, 1, 0, 0) => SDSpecVersion::V3, + (2, 1, 1, 0) => SDSpecVersion::V4, + (2, 1, _, 1) => SDSpecVersion::V5, + (2, 1, _, 2) => SDSpecVersion::V6, + (2, 1, _, 3) => SDSpecVersion::V7, + _ => SDSpecVersion::Unknown, + } + } + + #[allow(unused)] + pub fn bus_widths(&self) -> u8 { + // Ref PLSS_v7_10 Table 5-21 + ((self.0 >> 48) as u8) & 0xF + } + + pub fn bus_width_one(&self) -> bool { + (self.0 >> 48) & 1 != 0 + } + + pub fn bus_width_four(&self) -> bool { + (self.0 >> 50) & 1 != 0 + } +} + +impl Debug for Scr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SCR: SD CARD Configuration Register") + .field("Version", &self.version()) + .field("1-bit width", &self.bus_width_one()) + .field("4-bit width", &self.bus_width_four()) + .finish() + } +} +#[derive(Copy, Clone, Default)] +pub struct Cic(u32); + +impl From for Cic { + fn from(word: u32) -> Self { + Self(word) + } +} + +impl Cic { + pub fn voltage_accepted(&self) -> u8 { + (self.0 >> 8) as u8 + } + + pub fn pattern(&self) -> u8 { + self.0 as u8 + } +} + +#[derive(Clone, Copy, Default)] +pub struct SdStatus { + inner: [u32; 16], +} + +impl From<[u32; 16]> for SdStatus { + fn from(value: [u32; 16]) -> Self { + Self { inner: value } + } +} + +impl SdStatus { + pub fn bus_width(&self) -> BusWidth { + match (self.inner[15] >> 30) & 3 { + 0 => BusWidth::One, + 2 => BusWidth::Four, + _ => BusWidth::Unknown, + } + } + + pub fn secure_mode(&self) -> bool { + self.inner[15] & 0x2000_0000 != 0 + } + + pub fn sd_memory_card_type(&self) -> u16 { + self.inner[15] as u16 + } + + pub fn protected_area_size(&self) -> u32 { + self.inner[14] + } + + pub fn speed_class(&self) -> u8 { + (self.inner[13] >> 24) as u8 + } + + pub fn move_performance(&self) -> u8 { + (self.inner[13] >> 16) as u8 + } + + pub fn allocation_unit_size(&self) -> u8 { + (self.inner[13] >> 12) as u8 & 0xF + } + + pub fn erase_size(&self) -> u16 { + (self.inner[13] & 0xFF) as u16 | ((self.inner[12] >> 24) & 0xFF) as u16 + } + + pub fn erase_timeout(&self) -> u8 { + (self.inner[12] >> 18) as u8 & 0x3F + } + + pub fn video_speed_class(&self) -> u8 { + (self.inner[11] & 0xFF) as u8 + } + + pub fn app_perf_class(&self) -> u8 { + (self.inner[9] >> 16) as u8 & 0xF + } + + pub fn discard_support(&self) -> bool { + self.inner[8] & 0x0200_0000 != 0 + } +} +impl Debug for SdStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SD Status") + .field("Bus Width", &self.bus_width()) + .field("Secured Mode", &self.secure_mode()) + .field("SD Memory Card Type", &self.sd_memory_card_type()) + .field("Protected Area Size (B)", &self.protected_area_size()) + .field("Speed Class", &self.speed_class()) + .field("Video Speed Class", &self.video_speed_class()) + .field("Application Performance Class", &self.app_perf_class()) + .field("Move Performance (MB/s)", &self.move_performance()) + .field("AU Size", &self.allocation_unit_size()) + .field("Erase Size (units of AU)", &self.erase_size()) + .field("Erase Timeout (s)", &self.erase_timeout()) + .field("Discard Support", &self.discard_support()) + .finish() + } +} diff --git a/src/driver/block/starfive_sdio/driver_/utils.rs b/src/driver/block/starfive_sdio/driver_/utils.rs new file mode 100644 index 0000000..69e0248 --- /dev/null +++ b/src/driver/block/starfive_sdio/driver_/utils.rs @@ -0,0 +1,91 @@ +use crate::timer::Timer; +use core::time::Duration; + +use super::{ + err::Timeout, + reg::{ + CmdMask, InterruptMask, StatusMask, DATA_TMOUT_DEFUALT, REG_CMD, REG_CTRL, REG_RINTSTS, + REG_STATUS, + }, +}; + +const SDIO_BASE: usize = 0x16020000; +#[inline] +pub(crate) fn write_reg(reg: u32, val: u32) { + let addr = (SDIO_BASE + reg as usize) as *mut u32; + unsafe { + addr.write_volatile(val); + } +} +#[inline] +pub(crate) fn read_reg(reg: u32) -> u32 { + let addr = (SDIO_BASE + reg as usize) as *mut u32; + unsafe { addr.read_volatile() } +} + +pub(crate) fn wait_for bool>(dur: Duration, mut f: F) -> bool { + let timer = Timer::start(dur); + loop { + if timer.timeout() { + return false; + } + if f() { + break; + } + } + true +} + +pub(crate) fn wait_for_cmd_line() -> Result<(), Timeout> { + if !wait_for(Duration::from_millis(0xFF), || { + read_reg(REG_CMD) & CmdMask::start_cmd.bits() == 0 + }) { + Err(Timeout::WaitCmdLine) + } else { + Ok(()) + } +} + +pub(crate) fn wait_for_data_line() -> Result<(), Timeout> { + if wait_for(Duration::from_millis(DATA_TMOUT_DEFUALT as u64), || { + read_reg(REG_STATUS) & StatusMask::data_busy.bits() == 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitDataLine) + } +} + +pub(crate) fn wait_for_cmd_done() -> Result<(), Timeout> { + if wait_for(Duration::from_millis(0xFF), || { + read_reg(REG_RINTSTS) & InterruptMask::cmd.bits() != 0 + }) { + Ok(()) + } else { + Err(Timeout::WaitCmdDone) + } +} + +pub(crate) fn wait_reset(mask: u32) -> Result<(), Timeout> { + if wait_for(Duration::from_millis(10), || read_reg(REG_CTRL) & mask == 0) { + Ok(()) + } else { + Err(Timeout::WaitReset) + } +} + +pub(crate) fn fifo_cnt() -> u32 { + let status = read_reg(REG_STATUS); + (status >> 17) & 0x1FFF +} + +pub(crate) fn read_fifo(offset: usize) -> u8 { + let addr = (SDIO_BASE + 0x200 + offset) as *mut u8; + unsafe { addr.read_volatile() } +} +pub(crate) fn write_fifo(offset: usize, val: u8) { + let addr = (SDIO_BASE + 0x200 + offset) as *mut u8; + unsafe { + addr.write_volatile(val); + } +} diff --git a/src/driver/block/starfive_sdio/emmc.rs b/src/driver/block/starfive_sdio/emmc.rs new file mode 100644 index 0000000..c0a5375 --- /dev/null +++ b/src/driver/block/starfive_sdio/emmc.rs @@ -0,0 +1,53 @@ +// use alloc::string::String; +// use alloc::boxed::Box; + +// use super::{BlockDevice, BlockDriver}; + +// pub struct EMMCDevice; + +// impl BlockDevice for EMMCDevice { +// fn name(&self) -> String { +// "emmc".into() +// } + +// fn driver(&self) -> Box { +// Box::new(EMMCDriver::new()) +// } +// } + +// #[derive(Clone)] +// pub struct EMMCDriver { +// base: usize, +// } + +// impl EMMCDriver { +// pub fn new() -> Self { +// Self { base: 0x3F300000 } // Raspberry Pi 3 +// } +// } + +// impl BlockDriver for EMMCDriver { +// fn clone(&self) -> Box { +// Box::new(self.clone()) +// } + +// fn read_block(&mut self, _block: usize, _buf: &mut [u8]) -> Result<(), ()> { +// unimplemented!() +// } + +// fn write_block(&mut self, _block: usize, _buf: &[u8]) -> Result<(), ()> { +// unimplemented!() +// } + +// fn flush(&mut self) -> Result<(), ()> { +// Ok(()) +// } + +// fn get_block_size(&self) -> u32 { +// 512 +// } + +// fn get_block_count(&self) -> u64 { +// 0 +// } +// } diff --git a/src/driver/block/starfive_sdio/matcher.rs b/src/driver/block/starfive_sdio/matcher.rs new file mode 100644 index 0000000..f671253 --- /dev/null +++ b/src/driver/block/starfive_sdio/matcher.rs @@ -0,0 +1,41 @@ +use core::sync::atomic::{AtomicI32, Ordering}; +use alloc::sync::Arc; + +use crate::driver::{Device, DriverMatcher, DriverOps}; +use crate::arch::{self, map_kernel_addr}; +use crate::kernel::mm::MapPerm; +use crate::kwarn; + +use super::driver::Driver; + +pub struct Matcher { + count: AtomicI32, +} + +impl Matcher { + pub const fn new() -> Self { + Matcher { + count: AtomicI32::new(0), + } + } +} + +impl DriverMatcher for Matcher { + fn try_match(&self, device: &Device) -> Option> { + if device.compatible() != "snps,dw-mshc" { + return None; + } + + let pages = arch::page_count(device.mmio_size()); + map_kernel_addr(pages, device.mmio_base(), device.mmio_size(), MapPerm::RW); + + let driver = Driver::new(self.count.fetch_add(1, Ordering::Relaxed), device.mmio_base()); + let r = driver.init(); + if let Err(e) = r { + kwarn!("Failed to init starfive_sdio driver: {:?}", e); + None + } else { + Some(Arc::new(driver)) + } + } +} diff --git a/src/driver/block/starfive_sdio/mod.rs b/src/driver/block/starfive_sdio/mod.rs new file mode 100644 index 0000000..db4d832 --- /dev/null +++ b/src/driver/block/starfive_sdio/mod.rs @@ -0,0 +1,4 @@ +mod matcher; +mod driver; + +pub use matcher::Matcher; diff --git a/src/driver/block/virtio/device.rs b/src/driver/block/virtio/device.rs new file mode 100644 index 0000000..963f4b3 --- /dev/null +++ b/src/driver/block/virtio/device.rs @@ -0,0 +1,28 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; + +use crate::driver::block::{BlockDevice, BlockDriver}; + +use super::{VirtIOBlockDriverInner, VirtIOBlockDriver}; + +pub struct VirtIOBlockDevice { + driver: Arc, +} + +// impl VirtIOBlockDevice { +// pub fn new(addr: usize) -> Self { +// Self { +// driver: Arc::new(VirtIOBlockDriverInner::new(addr)), +// } +// } +// } + +// impl BlockDevice for VirtIOBlockDevice { +// fn name(&self) -> &str { +// "virtio_blk".into() +// } + +// fn driver(&self) -> Box { +// Box::new(VirtIOBlockDriver::new(self.driver.clone())) +// } +// } diff --git a/src/driver/block/virtio/driver.rs b/src/driver/block/virtio/driver.rs new file mode 100644 index 0000000..b29c19a --- /dev/null +++ b/src/driver/block/virtio/driver.rs @@ -0,0 +1,142 @@ +use alloc::format; +use alloc::sync::Arc; +use alloc::string::String; +use virtio_drivers::device::blk::VirtIOBlk; +use virtio_drivers::transport::mmio::MmioTransport; + +use crate::driver::BlockDriverOps; +use crate::driver::{DeviceType, DriverOps}; +use crate::driver::virtio::VirtIOHal; +use crate::klib::SpinLock; + +const BLOCK_SIZE: usize = 512; + +pub struct VirtIOBlockDriver { + num: u32, + driver: SpinLock> +} + +impl VirtIOBlockDriver { + pub fn new(num: u32, transport: MmioTransport) -> Self { + Self { + num, + driver: SpinLock::new(VirtIOBlk::new(transport).unwrap()) + } + } +} + +impl DriverOps for VirtIOBlockDriver { + fn name(&self) -> &str { + "virtio_blk_driver" + } + + fn device_name(&self) -> String { + format!("virtio_block{}", self.num) + } + + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn as_block_driver(self: Arc) -> Arc { + self + } +} + +impl BlockDriverOps for VirtIOBlockDriver { + fn read_block(&self, block: usize, buf: &mut [u8]) -> Result<(), ()> { + self.driver.lock().read_blocks(block, buf).map_err(|_| ()) + } + + fn write_block(&self, block: usize, buf: &[u8]) -> Result<(), ()> { + self.driver.lock().write_blocks(block, buf).map_err(|_| ()) + } + + fn read_blocks(&self, start_block: usize, buf: &mut [u8]) -> Result<(), ()> { + self.driver.lock().read_blocks(start_block, buf).map_err(|_| ()) + } + + fn write_blocks(&self, start_block: usize, buf: &[u8]) -> Result<(), ()> { + self.driver.lock().write_blocks(start_block, buf).map_err(|_| ()) + } + + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<(), ()> { + let mut length = buf.len(); + let mut block = offset / BLOCK_SIZE; + + let mut block_buf = [0u8; BLOCK_SIZE]; + let mut buf_offset = 0; + + let block_offset = offset % BLOCK_SIZE; + if block_offset != 0 { + self.read_block(block, &mut block_buf)?; + + let read_size = core::cmp::min(BLOCK_SIZE - block_offset, length); + buf[buf_offset..buf_offset + read_size].copy_from_slice(&block_buf[block_offset..block_offset + read_size]); + + buf_offset += read_size; + length -= read_size; + block += 1; + } + + while length != 0 { + self.read_block(block, &mut block_buf)?; + + let read_size = core::cmp::min(length, BLOCK_SIZE); + buf[buf_offset..buf_offset + read_size].copy_from_slice(&block_buf[..read_size]); + + buf_offset += read_size; + length -= read_size; + block += 1; + } + + Ok(()) + } + + fn write_at(&self, offset: usize, buf: &[u8]) -> Result<(), ()> { + let mut length = buf.len(); + let mut block = offset / BLOCK_SIZE; + + let mut block_buf = [0u8; BLOCK_SIZE]; + let mut buf_offset = 0; + + let block_offset = offset % BLOCK_SIZE; + if block_offset != 0 { + self.read_block(block, &mut block_buf)?; + + let write_size = core::cmp::min(BLOCK_SIZE - block_offset, length); + block_buf[block_offset..block_offset + write_size].copy_from_slice(&buf[buf_offset..buf_offset + write_size]); + self.write_block(block, &block_buf)?; + + buf_offset += write_size; + length -= write_size; + block += 1; + } + + while length != 0 { + self.read_block(block, &mut block_buf)?; + + let write_size = core::cmp::min(length, BLOCK_SIZE); + block_buf[..write_size].copy_from_slice(&buf[buf_offset..buf_offset + write_size]); + self.write_block(block, &block_buf)?; + + buf_offset += write_size; + length -= write_size; + block += 1; + } + + Ok(()) + } + + fn flush(&self) -> Result<(), ()> { + Ok(()) + } + + fn get_block_size(&self) -> u32 { + BLOCK_SIZE as u32 + } + + fn get_block_count(&self) -> u64 { + self.driver.lock().capacity() + } +} diff --git a/src/driver/block/virtio/inner.rs b/src/driver/block/virtio/inner.rs new file mode 100644 index 0000000..ad35633 --- /dev/null +++ b/src/driver/block/virtio/inner.rs @@ -0,0 +1,35 @@ +use core::ptr::NonNull; +use spin::Mutex; +use virtio_drivers::device::blk::VirtIOBlk; +use virtio_drivers::transport::mmio::{VirtIOHeader, MmioTransport}; + +use crate::driver::virtio::VirtIOHal; + +pub struct VirtIOBlockDriverInner { + driver: Mutex>, +} + +impl VirtIOBlockDriverInner { + pub fn new(addr: usize) -> Self { + let addr_ptr = addr as *mut usize; + let transport = unsafe { + MmioTransport::new(NonNull::new(addr_ptr as *mut VirtIOHeader).unwrap()) + .expect("Failed to create MMIO transport for VirtIO block driver") + }; + Self { + driver: Mutex::new(VirtIOBlk::new(transport).unwrap()), + } + } + + pub fn read_blocks(&self, block: usize, buf: &mut [u8]) -> Result<(), ()> { + self.driver.lock().read_blocks(block, buf).map_err(|_| ()) + } + + pub fn write_blocks(&self, block: usize, buf: &[u8]) -> Result<(), ()> { + self.driver.lock().write_blocks(block, buf).map_err(|_| ()) + } + + pub fn capacity(&self) -> u64 { + self.driver.lock().capacity() + } +} diff --git a/src/driver/block/virtio/mod.rs b/src/driver/block/virtio/mod.rs new file mode 100644 index 0000000..cc35dd5 --- /dev/null +++ b/src/driver/block/virtio/mod.rs @@ -0,0 +1,3 @@ +mod driver; + +pub use driver::VirtIOBlockDriver; diff --git a/src/driver/char/charfile.rs b/src/driver/char/charfile.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/driver/char/mod.rs b/src/driver/char/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/driver/char/mod.rs @@ -0,0 +1 @@ + diff --git a/src/driver/chosen/kconsole.rs b/src/driver/chosen/kconsole.rs new file mode 100644 index 0000000..787a231 --- /dev/null +++ b/src/driver/chosen/kconsole.rs @@ -0,0 +1,23 @@ +use spin::RwLock; + +pub trait KConsole : Sync { + fn kputs(&self, s: &str); +} + +struct EmptyKConsole; + +impl KConsole for EmptyKConsole { + fn kputs(&self, _s: &str) { + // Do nothing + } +} + +static KCONSOLE: RwLock<&'static dyn KConsole> = RwLock::new(&EmptyKConsole); + +pub fn register(console: &'static dyn KConsole) { + *KCONSOLE.write() = console; +} + +pub fn kputs(s: &str) { + KCONSOLE.read().kputs(s); +} diff --git a/src/driver/chosen/kpmu.rs b/src/driver/chosen/kpmu.rs new file mode 100644 index 0000000..7be110d --- /dev/null +++ b/src/driver/chosen/kpmu.rs @@ -0,0 +1,21 @@ +pub trait KPMU : Send + Sync { + fn shutdown(&self) -> !; +} + +struct EmptyKPMU; + +impl KPMU for EmptyKPMU { + fn shutdown(&self) -> ! { + loop {} + } +} + +static KPMU_DRIVER: spin::RwLock<&'static dyn KPMU> = spin::RwLock::new(&EmptyKPMU); + +pub fn register(kpmu: &'static dyn KPMU) { + *KPMU_DRIVER.write() = kpmu; +} + +pub fn shutdown() -> ! { + KPMU_DRIVER.read().shutdown() +} diff --git a/src/driver/chosen/mod.rs b/src/driver/chosen/mod.rs new file mode 100644 index 0000000..2f26d7d --- /dev/null +++ b/src/driver/chosen/mod.rs @@ -0,0 +1,2 @@ +pub mod kconsole; +pub mod kpmu; \ No newline at end of file diff --git a/src/driver/device.rs b/src/driver/device.rs new file mode 100644 index 0000000..5ab9848 --- /dev/null +++ b/src/driver/device.rs @@ -0,0 +1,41 @@ +pub enum DeviceType { + Block, + Char, + Network, + Other, +} + +#[derive(Debug)] +pub struct Device<'a> { + pub mmio_base: usize, + pub mmio_size: usize, + pub name: &'a str, + pub compatible: &'a str, +} + +impl<'a> Device<'a> { + pub fn new(mmio_base: usize, mmio_size: usize, name: &'a str, compatible: &'a str) -> Device<'a> { + Device { + mmio_base, + mmio_size, + name, + compatible, + } + } + + pub fn mmio_base(&self) -> usize { + self.mmio_base + } + + pub fn mmio_size(&self) -> usize { + self.mmio_size + } + + pub fn name(&self) -> &'a str { + self.name + } + + pub fn compatible(&self) -> &'a str { + self.compatible + } +} diff --git a/src/driver/driver.rs b/src/driver/driver.rs new file mode 100644 index 0000000..1f2e835 --- /dev/null +++ b/src/driver/driver.rs @@ -0,0 +1,145 @@ +use alloc::sync::Arc; +use alloc::string::String; + +use crate::kernel::errno::SysResult; +use crate::kernel::event::{PollEvent, PollEventSet}; + +use super::DeviceType; + +pub trait DriverOps { + fn name(&self) -> &str; + + fn device_name(&self) -> String; + fn device_type(&self) -> DeviceType; + + fn as_block_driver(self: Arc) -> Arc { + unreachable!() + } + + fn as_char_driver(self: Arc) -> Arc { + unreachable!() + } +} + +use downcast_rs::{impl_downcast, Downcast}; + +pub trait BlockDriverOps: DriverOps + Downcast { + fn read_block(&self, block: usize, buf: &mut [u8]) -> Result<(), ()>; + fn write_block(&self, block: usize, buf: &[u8]) -> Result<(), ()>; + + fn read_blocks(&self, start_block: usize, buf: &mut [u8]) -> Result<(), ()> { + let block_size = self.get_block_size() as usize; + debug_assert!(block_size <= 512); + let block_count = buf.len() / block_size; + for i in 0..block_count { + self.read_block(start_block + i, &mut buf[i * block_size..(i + 1) * block_size])?; + } + Ok(()) + } + + fn write_blocks(&self, start_block: usize, buf: &[u8]) -> Result<(), ()> { + let block_size = self.get_block_size() as usize; + debug_assert!(block_size <= 512); + let block_count = buf.len() / block_size; + for i in 0..block_count { + self.write_block(start_block + i, &buf[i * block_size..(i + 1) * block_size])?; + } + Ok(()) + } + + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<(), ()> { + let block_size = self.get_block_size() as usize; + debug_assert!(block_size <= 512); + + let mut length = buf.len(); + let mut block = offset / block_size; + + let mut block_buf = [0u8; 512]; + let mut buf_offset = 0; + + let block_offset = offset % block_size; + if block_offset != 0 { + self.read_block(block, &mut block_buf[..block_size])?; + + let read_size = core::cmp::min(block_size - block_offset, length); + buf[buf_offset..buf_offset + read_size].copy_from_slice(&block_buf[block_offset..block_offset + read_size]); + + buf_offset += read_size; + length -= read_size; + block += 1; + } + + while length != 0 { + self.read_block(block, &mut block_buf)?; + + let read_size = core::cmp::min(length, block_size); + buf[buf_offset..buf_offset + read_size].copy_from_slice(&block_buf[..read_size]); + + buf_offset += read_size; + length -= read_size; + block += 1; + } + + Ok(()) + } + fn write_at(&self, offset: usize, buf: &[u8]) -> Result<(), ()> { + let block_size = self.get_block_size() as usize; + debug_assert!(block_size <= 512); + + let mut length = buf.len(); + let mut block = offset / block_size; + + let mut block_buf = [0u8; 512]; + let mut buf_offset = 0; + + let block_offset = offset % block_size; + if block_offset != 0 { + self.read_block(block, &mut block_buf)?; + + let write_size = core::cmp::min(block_size - block_offset, length); + block_buf[block_offset..block_offset + write_size].copy_from_slice(&buf[buf_offset..buf_offset + write_size]); + self.write_block(block, &block_buf)?; + + buf_offset += write_size; + length -= write_size; + block += 1; + } + + while length != 0 { + self.read_block(block, &mut block_buf)?; + + let write_size = core::cmp::min(length, block_size); + block_buf[..write_size].copy_from_slice(&buf[buf_offset..buf_offset + write_size]); + self.write_block(block, &block_buf)?; + + buf_offset += write_size; + length -= write_size; + block += 1; + } + + Ok(()) + } + + fn flush(&self) -> Result<(), ()> { + Ok(()) + } + + fn get_block_size(&self) -> u32; + + fn get_block_count(&self) -> u64; +} + +impl_downcast!(BlockDriverOps); + +pub trait CharDriverOps: DriverOps + Downcast{ + fn putchar(&self, c: u8); + fn getchar(&self) -> Option; + fn poll(&self, waker: usize, event: PollEventSet) -> SysResult>; + fn poll_cancel(&self); +} + +impl_downcast!(CharDriverOps); + +pub trait PMUDriverOps : Sync + Send { + fn shutdown(&self) -> !; +} diff --git a/src/driver/fdt.rs b/src/driver/fdt.rs new file mode 100644 index 0000000..57ae127 --- /dev/null +++ b/src/driver/fdt.rs @@ -0,0 +1,54 @@ +use device_tree_parser::{DeviceTreeNode, DeviceTreeParser, DtbError}; + +use crate::arch::map_kernel_addr; +use crate::driver::manager::found_device; +use crate::kernel::mm::MapPerm; +use crate::kwarn; + +use super::Device; + +fn output_dtb_error(error: DtbError) -> () { + kwarn!("Device Tree Blob error: {:?}", error); +} + +pub fn load_device_tree(fdt: *const u8) -> Result<(), ()> { + let data = unsafe { core::slice::from_raw_parts(fdt as *const u32, 2) }; + let magic = u32::from_be(data[0]); + if magic != 0xd00dfeed { + return Err(()); + } + + let total_size = u32::from_be(data[1]) as usize; + + let data = unsafe { core::slice::from_raw_parts(fdt, total_size) }; + + let parser = DeviceTreeParser::new(data); + let root = parser.parse_tree().map_err(|_| ())?; + + load_fdt_node(&root, None); + + Ok(()) +} + +fn load_fdt_device(node: &DeviceTreeNode, parent: Option<&DeviceTreeNode>) -> Result<(), ()> { + let reg_addr = node.translate_reg_addresses(parent).map_err(output_dtb_error)?; + if reg_addr.len() != 1 { + // kwarn!("Device node {:?} has unsupported reg entries", node.name); + return Ok(()); + } + + let (addr, size) = reg_addr[0]; + + let compatible = node.prop_string("compatible").ok_or(())?; + + let device = Device::new( + addr as usize, + size as usize, + node.name, + &compatible, + ); + + found_device(&device); + + Ok(()) +} diff --git a/src/driver/manager.rs b/src/driver/manager.rs new file mode 100644 index 0000000..5939ffa --- /dev/null +++ b/src/driver/manager.rs @@ -0,0 +1,109 @@ +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; +use spin::RwLock; + +use crate::{kinfo, kwarn}; + +use super::{DriverMatcher, Device, DriverOps, BlockDriverOps, CharDriverOps, DeviceType}; + +pub struct DriverManager { + matchers: RwLock>, + block: RwLock>>, + char: RwLock>>, +} + +impl DriverManager { + const fn new() -> Self { + DriverManager { + matchers: RwLock::new(Vec::new()), + block: RwLock::new(BTreeMap::new()), + char: RwLock::new(BTreeMap::new()), + } + } + + fn register_matcher(&self, matcher: &'static dyn DriverMatcher) { + self.matchers.write().push(matcher); + } + + fn try_match(&self, device: &Device) -> Option> { + for matcher in self.matchers.read().iter() { + if let Some(driver) = matcher.try_match(device) { + return Some(driver); + } + } + kwarn!("No driver found for device: {:?}", device); + None + } + + fn found_device(&self, device: &Device) { + if let Some(driver) = self.try_match(device) { + kinfo!("Registering driver: {} for device {}: {:?}", driver.name(), driver.device_name(), device); + match driver.device_type() { + DeviceType::Block => { + self.register_block_device(driver.as_block_driver()); + } + _ => {} + } + } + } + + fn register_matched_driver(&self, driver: Arc) { + match driver.device_type() { + DeviceType::Block => { + self.register_block_device(driver.as_block_driver()); + } + DeviceType::Char => { + self.register_char_device(driver.as_char_driver()); + } + _ => unimplemented!() + } + } + + fn register_block_device(&self, driver: Arc) { + self.block.write().insert(driver.device_name(), driver); + } + + fn register_char_device(&self, driver: Arc) { + self.char.write().insert(driver.device_name(), driver); + } + + fn get_block_driver(&self, name: &str) -> Option> { + self.block + .read() + .get(name) + .map(|driver| driver.clone()) + } + + fn get_char_driver(&self, name: &str) -> Option> { + self.char + .read() + .get(name) + .map(|driver| driver.clone()) + } +} + +unsafe impl Sync for DriverManager {} + +static DRIVER_MANAGER: DriverManager = DriverManager::new(); + +pub fn found_device(device: &Device) { + DRIVER_MANAGER.found_device(device); +} + +pub fn register_matcher(matcher: &'static dyn DriverMatcher) { + DRIVER_MANAGER.register_matcher(matcher); +} + +pub fn register_matched_driver(driver: Arc) { + DRIVER_MANAGER.register_matched_driver(driver); +} + +pub fn get_block_driver(name: &str) -> Option> { + DRIVER_MANAGER.get_block_driver(name) +} + +pub fn get_char_driver(name: &str) -> Option> { + DRIVER_MANAGER.get_char_driver(name) +} diff --git a/src/driver/matcher.rs b/src/driver/matcher.rs new file mode 100644 index 0000000..3c04510 --- /dev/null +++ b/src/driver/matcher.rs @@ -0,0 +1,19 @@ +use alloc::sync::Arc; + +use super::manager; +use super::{Device, DriverOps}; + +use super::virtio::VirtIODriverMatcher; +use super::block::starfive_sdio; + +pub trait DriverMatcher { + fn try_match(&self, device: &Device) -> Option>; +} + +static VIRTIO: VirtIODriverMatcher = VirtIODriverMatcher::new(); +static VF_SDIO: starfive_sdio::Matcher = starfive_sdio::Matcher::new(); + +pub fn register_matchers() { + manager::register_matcher(&VIRTIO); + manager::register_matcher(&VF_SDIO); +} diff --git a/src/driver/mod.rs b/src/driver/mod.rs new file mode 100644 index 0000000..ee01065 --- /dev/null +++ b/src/driver/mod.rs @@ -0,0 +1,23 @@ +mod virtio; +mod device; +mod driver; +mod matcher; +mod manager; +// mod fdt; + +pub mod block; +pub mod char; +pub mod chosen; + +use matcher::DriverMatcher; + +pub use device::{Device, DeviceType}; +pub use driver::*; + +pub use manager::{get_block_driver, get_char_driver, register_matched_driver, found_device}; +// pub use fdt::load_device_tree; + +#[unsafe(link_section = ".text.init")] +pub fn init() { + matcher::register_matchers(); +} diff --git a/src/driver/virtio/hal.rs b/src/driver/virtio/hal.rs new file mode 100644 index 0000000..36c3fc9 --- /dev/null +++ b/src/driver/virtio/hal.rs @@ -0,0 +1,34 @@ + +use virtio_drivers::{Hal, BufferDirection, PhysAddr}; +use core::ptr::NonNull; + +use crate::kernel::mm::page; +use crate::arch; + +pub struct VirtIOHal; + +unsafe impl Hal for VirtIOHal { + fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { + let kaddr = page::alloc_contiguous(pages); + let ptr = NonNull::new(kaddr as *mut u8).expect("Failed to allocate DMA memory"); + (arch::kaddr_to_paddr(kaddr), ptr) + } + + unsafe fn dma_dealloc(_paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { + let kaddr = vaddr.as_ptr() as usize; + page::free_contiguous(kaddr, pages); + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { + NonNull::new(paddr as * mut u8).expect("Failed to convert MMIO physical address to virtual address") + } + + unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { + return arch::kaddr_to_paddr(buffer.as_ptr() as *mut u8 as usize); + } + + unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { + // Unsharing logic if needed + } +} \ No newline at end of file diff --git a/src/driver/virtio/matcher.rs b/src/driver/virtio/matcher.rs new file mode 100644 index 0000000..21f41be --- /dev/null +++ b/src/driver/virtio/matcher.rs @@ -0,0 +1,47 @@ +use alloc::sync::Arc; +use core::ptr::NonNull; +use core::sync::atomic::AtomicU32; +use virtio_drivers::transport::mmio::{MmioTransport, VirtIOHeader}; +use virtio_drivers::transport::{Transport, DeviceType}; + +use crate::kernel::mm::{MapPerm, page}; +use crate::arch::{self, map_kernel_addr}; +use crate::driver::block::VirtIOBlockDriver; +use crate::driver::{Device, DriverOps, DriverMatcher}; + +pub struct VirtIODriverMatcher { + block_count: AtomicU32, +} + +impl VirtIODriverMatcher { + pub const fn new() -> Self { + Self { + block_count: AtomicU32::new(0), + } + } +} + +impl DriverMatcher for VirtIODriverMatcher { + fn try_match(&self, device: &Device) -> Option> { + if device.compatible() != "virtio,mmio" { + return None; + } + + let mmio_base = page::alloc_contiguous(arch::page_count(device.mmio_size())); + map_kernel_addr(mmio_base, device.mmio_base(), device.mmio_size(), MapPerm::R | MapPerm::W); + + let transport = unsafe { + MmioTransport::new(NonNull::new(mmio_base as *mut VirtIOHeader).unwrap()).ok() + }?; + + match transport.device_type() { + DeviceType::Block => { + Some(Arc::new(VirtIOBlockDriver::new( + self.block_count.fetch_add(1, core::sync::atomic::Ordering::SeqCst), + transport + ))) + } + _ => None, + } + } +} diff --git a/src/driver/virtio/mod.rs b/src/driver/virtio/mod.rs new file mode 100644 index 0000000..6bf2bf1 --- /dev/null +++ b/src/driver/virtio/mod.rs @@ -0,0 +1,5 @@ +mod matcher; +mod hal; + +pub use hal::VirtIOHal; +pub use matcher::VirtIODriverMatcher; diff --git a/src/fs/devfs/def.rs b/src/fs/devfs/def.rs new file mode 100644 index 0000000..810cff4 --- /dev/null +++ b/src/fs/devfs/def.rs @@ -0,0 +1,3 @@ +pub const ROOT_INO: u32 = 0; +pub const NULL_INO: u32 = 1; +pub const ZERO_INO: u32 = 2; \ No newline at end of file diff --git a/src/fs/devfs/mod.rs b/src/fs/devfs/mod.rs new file mode 100644 index 0000000..29261df --- /dev/null +++ b/src/fs/devfs/mod.rs @@ -0,0 +1,8 @@ +mod superblock; +mod root; +mod null; +mod zero; + +mod def; + +pub use superblock::DevFileSystem; diff --git a/src/fs/devfs/null.rs b/src/fs/devfs/null.rs new file mode 100644 index 0000000..9b16b69 --- /dev/null +++ b/src/fs/devfs/null.rs @@ -0,0 +1,63 @@ +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::FileStat; +use crate::fs::{InodeOps, Mode}; +use crate::fs::file::DirResult; + +pub const INO: u32 = 1; + +pub struct NullInode { + sno: u32, +} + +impl NullInode { + pub fn new(sno: u32) -> Self { + Self { sno } + } +} + +impl InodeOps for NullInode { + fn get_ino(&self) -> u32 { + INO + } + + fn get_sno(&self) -> u32 { + self.sno + } + + fn type_name(&self) -> &'static str { + "devfs" + } + + fn readat(&self, _buf: &mut [u8], _offset: usize) -> SysResult { + // /dev/null always returns EOF (0 bytes read) + Ok(0) + } + + fn writeat(&self, buf: &[u8], _offset: usize) -> SysResult { + // /dev/null discards all data but reports success + Ok(buf.len()) + } + + fn get_dent(&self, _index: usize) -> SysResult> { + // /dev/null is not a directory + Err(Errno::ENOTDIR) + } + + fn size(&self) -> SysResult { + Ok(0) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + + kstat.st_ino = INO as u64; + kstat.st_size = 0; + kstat.st_mode = Mode::S_IFCHR.bits() as u32 | 0o666; + + Ok(kstat) + } + + fn mode(&self) -> SysResult { + Ok(Mode::from_bits_truncate(Mode::S_IFCHR.bits() as u32 | 0o666)) + } +} diff --git a/src/fs/devfs/root.rs b/src/fs/devfs/root.rs new file mode 100644 index 0000000..f5cbadd --- /dev/null +++ b/src/fs/devfs/root.rs @@ -0,0 +1,53 @@ +use crate::kernel::errno::{Errno, SysResult}; +use crate::fs::file::DirResult; +use crate::fs::{InodeOps, FileType}; + +use super::def::*; + +pub struct RootInode { + sno: u32, +} + +impl RootInode { + pub fn new(sno: u32) -> Self { + Self { sno } + } +} + +impl InodeOps for RootInode { + fn get_ino(&self) -> u32 { + ROOT_INO + } + + fn get_sno(&self) -> u32 { + self.sno + } + + fn type_name(&self) -> &'static str { + "devfs" + } + + fn get_dent(&self, index: usize) -> SysResult> { + let r = match index { + // 0 => DirResult { ino: ROOT_INO, name: ".".into(), file_type: FileType::Directory, len: 1}, + // 1 => DirResult { ino: NULL_INO, name: "null".into(), file_type: FileType::Regular, len: 1 }, + // 2 => DirResult { ino: ZERO_INO, name: "zero".into(), file_type: FileType::Regular, len: 1 }, + 0 => DirResult { ino: ROOT_INO, name: ".".into(), file_type: FileType::Directory}, + 1 => DirResult { ino: NULL_INO, name: "null".into(), file_type: FileType::Regular}, + 2 => DirResult { ino: ZERO_INO, name: "zero".into(), file_type: FileType::Regular}, + _ => return Ok(None), + }; + + Ok(Some((r, index + 1))) + } + + fn lookup(&self, name: &str) -> SysResult { + let r = match name { + "null" => NULL_INO, + "zero" => ZERO_INO, + _ => return Err(Errno::ENOENT), + }; + + Ok(r) + } +} diff --git a/src/fs/devfs/superblock.rs b/src/fs/devfs/superblock.rs new file mode 100644 index 0000000..45a2758 --- /dev/null +++ b/src/fs/devfs/superblock.rs @@ -0,0 +1,47 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; + +use crate::kernel::errno::SysResult; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use crate::fs::InodeOps; +use crate::driver::BlockDriverOps; + +use super::{root, null, zero}; +use super::def::*; + +pub struct DevFileSystem; + +impl FileSystemOps for DevFileSystem { + fn create(&self, sno: u32, _driver: Option>) -> SysResult> { + return Ok(DevSuperBlock::new(sno) as Arc); + } +} + +struct DevSuperBlock { + sno: u32, +} + +impl DevSuperBlock { + pub fn new(sno: u32) -> Arc { + Arc::new(DevSuperBlock { sno }) + } +} + +impl SuperBlockOps for DevSuperBlock { + fn get_root_ino(&self) -> u32 { + ROOT_INO + } + + fn get_inode(&self, ino: u32) -> SysResult> { + match ino { + ROOT_INO => Ok(Box::new(root::RootInode::new(self.sno))), + NULL_INO => Ok(Box::new(null::NullInode::new(self.sno))), + ZERO_INO => Ok(Box::new(zero::ZeroInode::new(self.sno))), + _ => unreachable!("DevFS only has 3 inodes"), + } + } + + fn unmount(&self) -> SysResult<()> { + Ok(()) + } +} diff --git a/src/fs/devfs/zero.rs b/src/fs/devfs/zero.rs new file mode 100644 index 0000000..024f600 --- /dev/null +++ b/src/fs/devfs/zero.rs @@ -0,0 +1,54 @@ +use crate::kernel::uapi::FileStat; +use crate::kernel::errno::{Errno, SysResult}; +use crate::fs::inode::{Mode, InodeOps}; +use crate::fs::file::DirResult; + +use super::def::ZERO_INO; +pub struct ZeroInode { + sno: u32, +} + +impl ZeroInode { + pub fn new(sno: u32) -> Self { + Self { sno } + } +} + +impl InodeOps for ZeroInode { + fn get_ino(&self) -> u32 { + ZERO_INO + } + + fn get_sno(&self) -> u32 { + self.sno + } + + fn type_name(&self) -> &'static str { + "devfs" + } + + fn readat(&self, buf: &mut [u8], _offset: usize) -> SysResult { + buf.fill(0); + Ok(buf.len()) + } + + fn writeat(&self, buf: &[u8], _offset: usize) -> SysResult { + Ok(buf.len()) + } + + fn get_dent(&self, _index: usize) -> SysResult> { + Err(Errno::ENOTDIR) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + kstat.st_ino = ZERO_INO as u64; + kstat.st_size = 0; + kstat.st_mode = Mode::S_IFCHR.bits() as u32 | 0o666; + Ok(kstat) + } + + fn mode(&self) -> SysResult { + Ok(Mode::from_bits_truncate(Mode::S_IFCHR.bits() as u32 | 0o666)) + } +} diff --git a/src/fs/ext4/ffi.rs b/src/fs/ext4/ffi.rs new file mode 100644 index 0000000..9b6b4fa --- /dev/null +++ b/src/fs/ext4/ffi.rs @@ -0,0 +1,339 @@ +use core::ffi::{c_char, c_void}; +use alloc::ffi::CString; + +use crate::kernel::errno::Errno; +use super::superblock::Ext4SuperBlock; + +unsafe extern "C" { + /* + int kernelx_ext4_register_block_device( + uint32_t block_size, + uint64_t block_count, + uintptr_t f_open, + uintptr_t f_bread, + uintptr_t f_bwrite, + uintptr_t f_close, + void *user, + struct ext4_fs **return_fs + ) + */ + fn kernelx_ext4_create_filesystem( + block_size : u32, + block_count: u64, + f_open: usize, + f_bread: usize, + f_bwrite: usize, + f_close: usize, + user: *mut c_void, + return_fs: *mut usize + ) -> i32; + + /* + int kernelx_ext4_destroy_filesystem( + struct ext4_fs *fs + ) + */ + fn kernelx_ext4_destroy_filesystem( + fs: *mut c_void + ) -> i32; + + /* + int kernelx_ext4_get_inode( + struct ext4_fs *fs, + uint32_t ino, + struct ext4_inode_ref **ret_inode + ) + */ + fn kernelx_ext4_get_inode( + fs: *mut c_void, + ino: u32, + ret_inode: *mut usize + ) -> i32; + + /* + int kernelx_ext4_put_inode( + struct ext4_inode_ref *inode_ref + ) + */ + fn kernelx_ext4_put_inode( + inode_ref: *mut c_void + ) -> i32; + + /* + int kernelx_ext4_inode_lookup( + struct ext4_inode_ref *inode, + const char *name, + uint32_t *ret_ino + ) + */ + fn kernelx_ext4_inode_lookup( + inode: *mut c_void, + name: *const c_char, + ret_ino: *mut u32 + ) -> i32; + + /* + ssize_t kernelx_ext4_inode_readat( + struct ext4_inode_ref *inode, + void *buf, + size_t size, + size_t offset + ) + */ + fn kernelx_ext4_inode_readat( + inode: *mut c_void, + buf: *mut c_void, + size: usize, + offset: usize + ) -> isize; + + /* + ssize_t kernelx_ext4_inode_writeat( + struct ext4_inode_ref *inode_ref, + const void *buf, + size_t size, + size_t fpos + ) + */ + fn kernelx_ext4_inode_writeat( + inode_ref: *mut c_void, + buf: *const c_void, + size: usize, + fpos: usize + ) -> isize; + + /* + ssize_t kernelx_ext4_get_inode_size( + struct ext4_inode_ref *inode_ref + ) + */ + fn kernelx_ext4_get_inode_size( + inode_ref: *mut c_void + ) -> isize; + + /* + int kernelx_ext4_inode_mkdir( + struct ext4_inode_ref *parent, + const char *name + ) + */ + fn kernelx_ext4_inode_mkdir( + parent: *mut c_void, + name: *const c_char + ) -> i32; + + /* + int kernelx_ext4_inode_create( + struct ext4_inode_ref *parent, + const char *name, + uint32_t mode + ) + */ + fn kernelx_ext4_create_inode( + parent: *mut c_void, + name: *const c_char, + mode: u32 + ) -> i32; +} + +pub enum Ext4Errno { + EOK = 0, +} + +#[inline(always)] +pub fn create_filesystem( + block_size : u32, + block_count: u64, + f_open: usize, + f_bread: usize, + f_bwrite: usize, + f_close: usize, + superblock: *mut Ext4SuperBlock, +) -> Result { + let mut fs = 0usize; + + let rc = unsafe { + kernelx_ext4_create_filesystem( + block_size, + block_count, + f_open, + f_bread, + f_bwrite, + f_close, + superblock as *mut c_void, + &mut fs + ) + }; + + if rc != Ext4Errno::EOK as i32 { + Err(Errno::from(-rc)) + } else { + Ok(fs) + } +} + +#[inline(always)] +pub fn destroy_filesystem(fs: usize) -> Result<(), Errno> { + let rc = unsafe { kernelx_ext4_destroy_filesystem(fs as *mut c_void) }; + if rc != Ext4Errno::EOK as i32 { + Err(Errno::from(-rc)) + } else { + Ok(()) + } +} + +#[inline(always)] +pub fn get_inode_handler( + fs_handler: usize, + ino: u32, +) -> Result { + let mut inode_handler = 0; + let rc = unsafe { + kernelx_ext4_get_inode(fs_handler as *mut c_void, ino, &mut inode_handler) + }; + + if rc != Ext4Errno::EOK as i32 { + Err(Errno::from(-rc)) + } else { + Ok(inode_handler) + } +} + +#[inline(always)] +pub fn put_inode_handler( + inode_handler: usize +) -> Result<(), Errno> { + let rc = unsafe { + kernelx_ext4_put_inode(inode_handler as *mut c_void) + }; + + if rc != Ext4Errno::EOK as i32 { + Err(Errno::from(-rc)) + } else { + Ok(()) + } +} + +#[inline(always)] +pub fn inode_lookup( + inode_handler: usize, + name: &str, +) -> Result { + let mut result = 0u32; + + let name = CString::new(name).unwrap(); + let r = unsafe { + kernelx_ext4_inode_lookup( + inode_handler as *mut c_void, + name.as_ptr(), + &mut result + ) + }; + + if r != Ext4Errno::EOK as i32 { + return Err(Errno::from(-r)); + } + + Ok(result) +} + +#[inline(always)] +pub fn inode_readat( + inode_handler: usize, + buf: &mut [u8], + offset: usize +) -> Result { + let r = unsafe { + kernelx_ext4_inode_readat( + inode_handler as *mut c_void, + buf.as_mut_ptr() as *mut c_void, + buf.len(), + offset + ) + }; + + if r < 0 { + Err(Errno::from(-r as i32)) + } else { + Ok(r as usize) + } +} + +#[inline(always)] +pub fn inode_writeat( + inode_handler: usize, + buf: &[u8], + offset: usize +) -> Result { + let r = unsafe { + kernelx_ext4_inode_writeat( + inode_handler as *mut c_void, + buf.as_ptr() as *const c_void, + buf.len(), + offset + ) + }; + if r < 0 { + Err(Errno::from(-r as i32)) + } else { + Ok(r as usize) + } +} + +#[inline(always)] +pub fn inode_get_size( + inode_handler: usize +) -> Result { + let r = unsafe { + kernelx_ext4_get_inode_size(inode_handler as *mut c_void) + }; + + if r < 0 { + Err(Errno::from(-r as i32)) + } else { + Ok(r as usize) + } +} + +#[inline(always)] +pub fn inode_mkdir( + parent_handler: usize, + name: &str +) -> Result<(), Errno> { + let name = CString::new(name).unwrap(); + let r = unsafe { + kernelx_ext4_inode_mkdir( + parent_handler as *mut c_void, + name.as_ptr() + ) + }; + + if r != Ext4Errno::EOK as i32 { + Err(Errno::from(-r)) + } else { + Ok(()) + } +} + +#[inline(always)] +pub fn create_inode( + parent_handler: usize, + name: &str, + mode: u32 +) -> Result<(), Errno> { + let name = CString::new(name).unwrap(); + let r = unsafe { + kernelx_ext4_create_inode( + parent_handler as *mut c_void, + name.as_ptr(), + mode + ) + }; + + if r != Ext4Errno::EOK as i32 { + Err(Errno::from(-r)) + } else { + Ok(()) + } +} + diff --git a/src/fs/ext4/filesystem.rs b/src/fs/ext4/filesystem.rs new file mode 100644 index 0000000..830c83e --- /dev/null +++ b/src/fs/ext4/filesystem.rs @@ -0,0 +1,14 @@ +use alloc::sync::Arc; + +use crate::kernel::errno::Errno; +use crate::driver::BlockDriverOps; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use super::superblock::Ext4SuperBlock; + +pub struct Ext4FileSystem; + +impl FileSystemOps for Ext4FileSystem { + fn create(&self, sno: u32, driver: Option>) -> Result, Errno> { + Ok(Ext4SuperBlock::new(sno, driver.unwrap())?) + } +} diff --git a/src/fs/ext4/inode.rs b/src/fs/ext4/inode.rs new file mode 100644 index 0000000..535ac94 --- /dev/null +++ b/src/fs/ext4/inode.rs @@ -0,0 +1,289 @@ +use core::time::Duration; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; +use lwext4_rust::{FileAttr, InodeType}; + +use crate::fs::FileType; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::FileStat; +use crate::fs::inode::{InodeOps, Mode}; +use crate::fs::file::DirResult; +use crate::klib::SpinLock; + +// use super::superblock_inner::{SuperBlockInner, SuperBlockInnerExt, map_error}; +use super::superblock::{SuperBlockInner, map_error_to_kernel}; + +pub struct Ext4Inode { + ino: u32, + sno: u32, + // meta: SpinLock>, + superblock: Arc>, + dents_cache: SpinLock>>, +} + +impl Ext4Inode { + pub fn new(sno: u32, ino: u32, superblock: Arc>) -> Self { + // let inode_ref = superblock.get_inode_ref(ino); + // Self { + // ino, + // sno, + // meta: Mutex::new(inode_ref.inode), + // superblock: superblock.clone(), + // dents_cache: Mutex::new(None), + // } + Self { + ino, + sno, + superblock, + dents_cache: SpinLock::new(None) + } + } + + // fn with_inode_ref(&self, f: F) -> R + // where + // F: FnOnce(&mut Ext4InodeRef) -> R, + // { + // let mut meta = self.meta.lock(); + // let mut inode_ref = Ext4InodeRef { + // inode: *meta, + // inode_num: self.ino, + // }; + // let r = f(&mut inode_ref); + // *meta = inode_ref.inode; + // r + // } + + // fn get_inode_ref(&self, meta: &Meta) -> Ext4InodeRef { + // Ext4InodeRef { + // inode: *meta, + // inode_num: self.ino, + // } + // } +} + +impl InodeOps for Ext4Inode { + fn get_ino(&self) -> u32 { + self.ino + } + + fn get_sno(&self) -> u32 { + self.sno + } + + fn type_name(&self) -> &'static str { + "ext4" + } + + fn create(&self, name: &str, mode: Mode) -> SysResult<()> { + if !self.mode()?.contains(Mode::S_IFDIR) { + return Err(Errno::ENOTDIR); + } + + *self.dents_cache.lock() = None; // Invalidate cache + let mut superblock = self.superblock.lock(); + + let ty = if mode.contains(Mode::S_IFDIR) { + InodeType::Directory + } else if mode.contains(Mode::S_IFREG) { + InodeType::RegularFile + } else if mode.contains(Mode::S_IFLNK) { + InodeType::Symlink + } else if mode.contains(Mode::S_IFCHR) { + InodeType::CharacterDevice + } else if mode.contains(Mode::S_IFBLK) { + InodeType::BlockDevice + } else if mode.contains(Mode::S_IFIFO) { + InodeType::Fifo + } else { + InodeType::Unknown + }; + + superblock.create(self.ino, name, ty, mode.bits() as u32).map_err(map_error_to_kernel)?; + + Ok(()) + } + + fn unlink(&self, name: &str) -> SysResult<()> { + // unlink currently not migrated to `lwext4_rust` APIs. + // Return ENOSYS until a full migration of directory APIs is implemented. + // let _ = name; + // Err(Errno::ENOSYS) + self.superblock.lock().unlink(self.ino, name).map_err(map_error_to_kernel) + } + + fn readat(&self, buf: &mut [u8], offset: usize) -> SysResult { + if self.mode()?.contains(Mode::S_IFDIR) { + return Err(Errno::EISDIR); + } + let mut sb = self.superblock.lock(); + sb.read_at(self.ino, buf, offset as u64).map_err(map_error_to_kernel) + } + + fn writeat(&self, buf: &[u8], offset: usize) -> SysResult { + if self.mode()?.contains(Mode::S_IFDIR) { + return Err(Errno::EISDIR); + } + let mut sb = self.superblock.lock(); + sb.write_at(self.ino, buf, offset as u64).map_err(map_error_to_kernel) + // kinfo!("Ext4Inode::writeat ino={} offset={} len={}", self.ino, offset, buf.len()); + // self.superblock.get_inode_ref(self.ino); + // self.superblock.write_at(self.ino, offset, buf).map_err(map_error) + } + + fn get_dent(&self, offset: usize) -> SysResult> { + // Directory enumeration not yet migrated to `lwext4_rust`. + let mut reader = self.superblock.lock().read_dir(self.ino, offset as u64).map_err(map_error_to_kernel)?; + let result = reader.current().map(|entry| { + // kinfo!("Ext4Inode::get_dent ino={} offset={} entry_ino={} name={}", self.ino, offset, entry.ino(), String::from_utf8_lossy(entry.name())); + DirResult { + ino: entry.ino(), + name: String::from_utf8_lossy(entry.name()).into_owned(), + file_type: match entry.inode_type() { + InodeType::Directory => FileType::Directory, + InodeType::RegularFile => FileType::Regular, + InodeType::Symlink => FileType::Symlink, + InodeType::CharacterDevice => FileType::CharDevice, + InodeType::BlockDevice => FileType::BlockDevice, + InodeType::Fifo => FileType::FIFO, + _ => FileType::Unknown, + }, + // len: entry.len() + } + }); + + // if let Some(ref dent) = result { + // if dent.ino == 0 { + // return Ok(None); + // } + // } + + if let Some(r) = &result { + reader.step().map_err(map_error_to_kernel)?; + let next_offset = reader.offset() as usize; + Ok(Some((r.clone(), next_offset))) + } else { + Ok(None) + } + } + + fn lookup(&self, name: &str) -> SysResult { + let mut result = self.superblock.lock().lookup(self.ino, name).map_err(map_error_to_kernel)?; + Ok(result.entry().ino()) + } + + fn rename(&self, old_name: &str, new_parent: &Arc, new_name: &str) -> SysResult<()> { + // Rename not yet migrated to `lwext4_rust` directory APIs. + let _ = (old_name, new_parent, new_name); + Err(Errno::ENOSYS) + } + + fn size(&self) -> SysResult { + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + Ok(inode_ref.size()) + }).map_err(map_error_to_kernel) + } + + fn mode(&self) -> SysResult { + // Mode::from_bits_truncate(self.meta.lock().mode()) + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + Ok(Mode::from_bits_truncate(inode_ref.mode())) + }).map_err(map_error_to_kernel) + } + + fn chmod(&self, mode: Mode) -> SysResult<()> { + debug_assert!(mode.bits() <= 0o777); + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + let current_mode = inode_ref.mode(); + let new_mode = (current_mode & !0o777) | (mode.bits() as u32 & 0o777); + inode_ref.set_mode(new_mode); + Ok(()) + }).map_err(map_error_to_kernel)?; + Ok(()) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + // let meta = self.meta.lock(); + + // kstat.st_ino = self.ino as u64; + // kstat.st_size = meta.size() as i64; + // kstat.st_nlink = meta.links_count() as u32; + // kstat.st_mode = meta.mode() as u32; + // // TODO: utime tests + // // kstat.st_atime_sec = meta.atime() as i64; + // kstat.st_atime_sec = 0; + // kstat.st_atime_nsec = 0; + // // kstat.st_mtime_sec = meta.mtime() as i64; + // kstat.st_mtime_sec = 0; + // kstat.st_mtime_nsec = 0; + // // kstat.st_ctime_sec = meta.ctime() as i64; + // kstat.st_ctime_sec = 0; + // kstat.st_ctime_nsec = 0; + + let mut superblock = self.superblock.lock(); + + kstat.st_ino = self.ino as u64; + + superblock.with_inode_ref(self.ino, |inode_ref| { + let mut attr = FileAttr::default(); + inode_ref.get_attr(&mut attr); + + kstat.st_size = attr.size as i64; + kstat.st_nlink = attr.nlink as u32; + kstat.st_mode = attr.mode as u32; + kstat.st_uid = attr.uid as u32; + kstat.st_gid = attr.gid as u32; + kstat.st_blksize = attr.block_size as i32; + kstat.st_blocks = attr.blocks as u64; + + kstat.st_atime_sec = attr.atime.as_secs() as i64; + kstat.st_atime_nsec = attr.atime.subsec_nanos() as i64; + kstat.st_mtime_sec = attr.mtime.as_secs() as i64; + kstat.st_mtime_nsec = attr.mtime.subsec_nanos() as i64; + kstat.st_ctime_sec = attr.ctime.as_secs() as i64; + kstat.st_ctime_nsec = attr.ctime.subsec_nanos() as i64; + + Ok(()) + }).map_err(map_error_to_kernel)?; + + Ok(kstat) + } + + fn truncate(&self, new_size: u64) -> SysResult<()> { + self.superblock.lock().set_len(self.ino, new_size).map_err(map_error_to_kernel) + } + + fn update_atime(&self, time: &Duration) -> SysResult<()> { + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + inode_ref.set_atime(time); + Ok(()) + }).map_err(map_error_to_kernel) + } + + fn update_mtime(&self, time: &Duration) -> SysResult<()> { + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + inode_ref.set_mtime(time); + Ok(()) + }).map_err(map_error_to_kernel) + } + + fn update_ctime(&self, time: &Duration) -> SysResult<()> { + self.superblock.lock().with_inode_ref(self.ino, |inode_ref| { + inode_ref.set_ctime(time); + Ok(()) + }).map_err(map_error_to_kernel) + } + + fn sync(&self) -> SysResult<()> { + // sync not implemented in new API yet + self.superblock.lock().flush().map_err(map_error_to_kernel) + } +} + +impl Drop for Ext4Inode { + fn drop(&mut self) { + // No-op drop until inode lifecycle is migrated to lwext4_rust. + self.superblock.lock().flush().map_err(map_error_to_kernel).unwrap(); + } +} diff --git a/src/fs/ext4/mod.rs b/src/fs/ext4/mod.rs new file mode 100644 index 0000000..5600ce5 --- /dev/null +++ b/src/fs/ext4/mod.rs @@ -0,0 +1,6 @@ +mod filesystem; +mod superblock; +// mod superblock_inner; +mod inode; + +pub use filesystem::Ext4FileSystem; diff --git a/src/fs/ext4/superblock.rs b/src/fs/ext4/superblock.rs new file mode 100644 index 0000000..7f66e04 --- /dev/null +++ b/src/fs/ext4/superblock.rs @@ -0,0 +1,157 @@ +use alloc::sync::Arc; +use alloc::boxed::Box; +use lwext4_rust::{BlockDevice, DummyHal, Ext4Error, Ext4Filesystem, Ext4Result, FsConfig}; +use lwext4_rust::EXT4_DEV_BSIZE; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::Statfs; +use crate::klib::SpinLock; +use crate::fs::ext4::inode::Ext4Inode; +use crate::fs::filesystem::SuperBlockOps; +use crate::fs::InodeOps; +use crate::driver::BlockDriverOps; + +pub(super) fn map_error_to_ext4(e: Errno, context: &'static str) -> Ext4Error { + Ext4Error { + code: e as i32, + context: Some(context) + } +} + +pub(super) fn map_error_to_kernel(e: Ext4Error) -> Errno { + // if e.context.is_some() { + // kwarn!("{:?}", e); + // } + + Errno::try_from(e.code).expect("unexpected code") +} + +pub(super) struct BlockDeviceImpls { + driver: Arc +} + +impl BlockDeviceImpls { + fn new(driver: Arc) -> Self { + Self { driver } + } +} + +impl BlockDevice for BlockDeviceImpls { + fn num_blocks(&self) -> Ext4Result { + Ext4Result::Ok(self.driver.get_block_size() as u64 * self.driver.get_block_count() / EXT4_DEV_BSIZE as u64) + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Ext4Result { + self.driver.read_at(block_id as usize * EXT4_DEV_BSIZE as usize, buf).map_err(|_| map_error_to_ext4(Errno::EIO, "read_block"))?; + Ext4Result::Ok(buf.len()) + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Ext4Result { + self.driver.write_at(block_id as usize * EXT4_DEV_BSIZE, buf).map_err(|_| map_error_to_ext4(Errno::EIO, "write_block"))?; + Ok(buf.len()) + } +} + +pub(super) type SuperBlockInner = Ext4Filesystem; + +pub struct Ext4SuperBlock { + sno: u32, + // superblock: Arc, + superblock: Arc> +} + +// struct Disk { +// pub driver: Arc +// } + +// impl BlockDevice for Disk { +// fn read_offset(&self, offset: usize) -> Vec { +// let mut buf = vec![0u8; BLOCK_SIZE as usize]; +// if self.driver.read_at(offset, &mut buf).is_err() { +// panic!("Failed to read at offset {}", offset); +// } + +// // kinfo!("Disk::read_offset offset={:x} buf={:?}", offset, &buf); + +// buf +// } + +// fn write_offset(&self, offset: usize, buf: &[u8]) { +// self.driver.write_at(offset, buf).expect("Failed to write at offset"); +// } +// } + +// unsafe impl Send for Disk {} +// unsafe impl Sync for Disk {} + +impl Ext4SuperBlock { + pub fn new(sno: u32, driver: Arc) -> SysResult> { + // let superblock = SuperBlockInner::open(Arc::new(Disk{ driver })); + + // Ok(Arc::new(Ext4SuperBlock { + // sno, + // superblock: Arc::new(superblock), + // })) + let superblock = Ext4Filesystem::new(BlockDeviceImpls::new(driver), FsConfig::default()).map_err(map_error_to_kernel)?; + + Ok(Arc::new(Self { + sno, + superblock: Arc::new(SpinLock::new(superblock)) + })) + } +} + +unsafe impl Send for Ext4SuperBlock {} +unsafe impl Sync for Ext4SuperBlock {} + +impl SuperBlockOps for Ext4SuperBlock { + fn get_inode(&self, ino: u32) -> SysResult> { + Ok(Box::new(Ext4Inode::new(self.sno, ino, self.superblock.clone()))) + } + + fn get_root_ino(&self) -> u32 { + 2 + } + + fn unmount(&self) -> SysResult<()> { + // destroy_filesystem(self.fs_handler) + Ok(()) + } + + fn statfs(&self) -> SysResult { + // let statfs = Statfs { + // f_type: 0xEF53, // EXT4 magic number + // f_bsize: self.superblock.super_block.block_size() as u64, + // f_blocks: self.superblock.super_block.blocks_count() as u64, + // f_bfree: self.superblock.super_block.free_blocks_count() as u64, + // f_bavail: self.superblock.super_block.free_blocks_count() as u64, + // f_files: self.superblock.super_block.total_inodes() as u64, + // f_ffree: self.superblock.super_block.free_inodes_count() as u64, + // f_fsid: 0, + // f_namelen: 255, + // f_frsize: self.superblock.super_block.block_size() as u64, + // f_flag: 0, + // f_spare: [0; 4], + // }; + let stat = self.superblock.lock().stat().map_err(map_error_to_kernel)?; + let statfs = Statfs { + f_type: 0xEF53, // EXT4 magic number + f_bsize: stat.block_size as u64, + f_blocks: stat.blocks_count as u64, + f_bfree: stat.free_blocks_count as u64, + f_bavail: stat.free_blocks_count as u64, + f_files: stat.inodes_count as u64, + f_ffree: stat.free_inodes_count as u64, + f_fsid: 0, + f_namelen: 255, + f_frsize: stat.block_size as u64, + f_flag: 0, + f_spare: [0; 4], + }; + Ok(statfs) + } + + fn sync(&self) -> SysResult<()> { + self.superblock.lock().flush().map_err(map_error_to_kernel) + } +} diff --git a/src/fs/file/charfile.rs b/src/fs/file/charfile.rs new file mode 100644 index 0000000..567d407 --- /dev/null +++ b/src/fs/file/charfile.rs @@ -0,0 +1,97 @@ +use alloc::sync::Arc; + +use crate::kernel::errno::{SysResult, Errno}; +use crate::kernel::uapi::FileStat; +use crate::kernel::event::{PollEvent, PollEventSet}; +use crate::driver::CharDriverOps; +use crate::fs::file::FileOps; +use crate::fs::{InodeOps, Mode}; + +use super::SeekWhence; + +pub struct CharFile { + driver: Arc, +} + +impl CharFile { + pub fn new(driver: Arc) -> Self { + CharFile { driver } + } +} + +impl FileOps for CharFile { + fn read(&self, buf: &mut [u8]) -> SysResult { + let mut count = 0; + for byte in buf.iter_mut() { + if let Some(c) = self.driver.getchar() { + *byte = c; + count += 1; + } else { + break; + } + } + Ok(count) + } + + fn pread(&self, _: &mut [u8], _: usize) -> SysResult { + Err(Errno::EPIPE) + } + + fn write(&self, buf: &[u8]) -> crate::kernel::errno::SysResult { + for &c in buf { + self.driver.putchar(c); + } + Ok(buf.len()) + } + + fn pwrite(&self, _: &[u8], _: usize) -> SysResult { + Err(Errno::EPIPE) + } + + fn readable(&self) -> bool { + true + } + + fn writable(&self) -> bool { + true + } + + fn seek(&self, _offset: isize, _whence: SeekWhence) -> SysResult { + Err(Errno::ESPIPE) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + + kstat.st_mode = Mode::S_IFCHR.bits() as u32; + + Ok(kstat) + } + + fn fsync(&self) -> SysResult<()> { + Ok(()) + } + + fn get_dentry(&self) -> Option<&Arc> { + None + } + + fn get_inode(&self) -> Option<&Arc> { + None + } + + fn ioctl(&self, _request: usize, _arg: usize) -> SysResult { + Err(Errno::ENOSYS) + } + + fn poll(&self, waker: usize, event: PollEventSet) -> SysResult> { + self.driver.poll(waker, event) + } + + fn type_name(&self) -> &'static str { + "CharFile" + } +} + +unsafe impl Send for CharFile {} +unsafe impl Sync for CharFile {} diff --git a/src/fs/file/dirresult.rs b/src/fs/file/dirresult.rs new file mode 100644 index 0000000..5772a7f --- /dev/null +++ b/src/fs/file/dirresult.rs @@ -0,0 +1,11 @@ +use alloc::string::String; + +use crate::fs::FileType; + +#[derive(Clone)] +pub struct DirResult { + pub name: String, + pub ino: u32, + pub file_type: FileType, + // pub len: u16, +} diff --git a/src/fs/file/file.rs b/src/fs/file/file.rs new file mode 100644 index 0000000..e6f38f6 --- /dev/null +++ b/src/fs/file/file.rs @@ -0,0 +1,173 @@ +use alloc::sync::Arc; +use spin::Mutex; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::FileStat; +use crate::fs::file::DirResult; +use crate::fs::InodeOps; +use crate::fs::vfs::Dentry; + +use super::{FileOps, SeekWhence}; + +#[derive(Clone, Copy)] +pub struct FileFlags { + pub readable: bool, + pub writable: bool, + pub blocked: bool +} + +impl FileFlags { + pub const fn dontcare() -> Self { + FileFlags { readable: true, writable: true, blocked: true } + } + + pub const fn readonly() -> Self { + FileFlags { readable: true, writable: false, blocked: true } + } +} + +pub struct File { + inode: Arc, + dentry: Arc, + pos: Mutex, + + pub flags: FileFlags, +} + +impl File { + pub fn new(dentry: &Arc, flags: FileFlags) -> Self { + File { + inode: dentry.get_inode().clone(), + dentry: dentry.clone(), + pos: Mutex::new(0), + flags + } + } + + pub fn new_inode(inode: Arc, dentry: Arc, flags: FileFlags) -> Self { + File { + inode, + dentry, + pos: Mutex::new(0), + flags + } + } + + pub fn read_at(&self, buf: &mut [u8], offset: usize) -> SysResult { + let len = self.inode.readat(buf, offset)?; + Ok(len) + } + + pub fn write_at(&self, buf: &[u8], offset: usize) -> SysResult { + if !self.flags.writable { + return Err(Errno::EPERM); + } + let len = self.inode.writeat(buf, offset)?; + Ok(len) + } + + pub fn ftruncate(&self, new_size: u64) -> SysResult<()> { + self.inode.truncate(new_size) + } + + /// Return the dirent and the old file pos. + pub fn get_dent(&self) -> SysResult> { + let mut pos = self.pos.lock(); + let old_pos = *pos; + let (dent, next_pos) = match self.inode.get_dent(*pos)? { + Some(d) => d, + None => return Ok(None), + }; + *pos = next_pos; + + Ok(Some((dent, old_pos))) + } +} + +impl FileOps for File { + fn read(&self, buf: &mut [u8]) -> Result { + let mut pos = self.pos.lock(); + let len = self.inode.readat(buf, *pos)?; + *pos += len; + + Ok(len) + } + + fn pread(&self, buf: &mut [u8], offset: usize) -> SysResult { + let len = self.inode.readat(buf, offset)?; + Ok(len) + } + + fn write(&self, buf: &[u8]) -> Result { + let mut pos = self.pos.lock(); + let len = self.inode.writeat(buf, *pos)?; + *pos += len; + + Ok(len) + } + + fn pwrite(&self, buf: &[u8], offset: usize) -> SysResult { + let len = self.inode.writeat(buf, offset)?; + Ok(len) + } + + fn readable(&self) -> bool { + self.flags.readable + } + + fn writable(&self) -> bool { + self.flags.writable + } + + fn seek(&self, offset: isize, whence: SeekWhence) -> SysResult { + let mut pos = self.pos.lock(); + let new_pos; + match whence { + SeekWhence::BEG => { + if offset < 0 { + return Err(Errno::EINVAL); + } + new_pos = offset; + } + SeekWhence::CUR => { + if offset < 0 && (*pos as isize + offset) < 0 { + return Err(Errno::EINVAL); + } + new_pos = *pos as isize + offset; + } + SeekWhence::END => { + let size = self.inode.size()?; + if offset > 0 && (size as isize + offset) < 0 { + return Err(Errno::EINVAL); + } + new_pos = size as isize + offset; + } + } + if new_pos < 0 { + return Err(Errno::EINVAL); + } + *pos = new_pos as usize; + + Ok(*pos) + } + + fn ioctl(&self, _request: usize, _arg: usize) -> SysResult { + Err(Errno::ENOSYS) // Placeholder for unimplemented ioctl commands + } + + fn fstat(&self) -> SysResult { + self.inode.fstat() + } + + fn fsync(&self) -> SysResult<()> { + self.inode.sync() + } + + fn get_inode(&self) -> Option<&Arc> { + Some(&self.inode) + } + + fn get_dentry(&self) -> Option<&Arc> { + Some(&self.dentry) + } +} diff --git a/src/fs/file/fileop.rs b/src/fs/file/fileop.rs new file mode 100644 index 0000000..cc5f1d9 --- /dev/null +++ b/src/fs/file/fileop.rs @@ -0,0 +1,43 @@ +use alloc::sync::Arc; +use downcast_rs::{DowncastSync, impl_downcast}; + +use crate::kernel::event::{PollEvent, PollEventSet}; +use crate::kernel::errno::SysResult; +use crate::kernel::uapi::FileStat; +use crate::fs::{Dentry, InodeOps}; + +pub enum SeekWhence { + BEG, + CUR, + END, +} + +pub trait FileOps: DowncastSync { + fn read(&self, buf: &mut [u8]) -> SysResult; + fn pread(&self, buf: &mut [u8], offset: usize) -> SysResult; + fn write(&self, buf: &[u8]) -> SysResult; + fn pwrite(&self, buf: &[u8], offset: usize) -> SysResult; + + fn readable(&self) -> bool; + fn writable(&self) -> bool; + + fn seek(&self, offset: isize, whence: SeekWhence) -> SysResult; + fn ioctl(&self, request: usize, arg: usize) -> SysResult; + fn fstat(&self) -> SysResult; + fn fsync(&self) -> SysResult<()>; + // fn get_dent(&self) -> SysResult>; + + fn get_inode(&self) -> Option<&Arc>; + fn get_dentry(&self) -> Option<&Arc>; + + fn poll(&self, _waker: usize, _event: PollEventSet) -> SysResult> { + Ok(None) + } + fn poll_cancel(&self) {} + + fn type_name(&self) -> &'static str { + "unknown" + } +} + +impl_downcast!(sync FileOps); diff --git a/src/fs/file/mod.rs b/src/fs/file/mod.rs new file mode 100644 index 0000000..b55ef51 --- /dev/null +++ b/src/fs/file/mod.rs @@ -0,0 +1,9 @@ +mod fileop; +mod charfile; +mod file; +mod dirresult; + +pub use fileop::{FileOps, SeekWhence}; +pub use file::*; +pub use charfile::CharFile; +pub use dirresult::DirResult; \ No newline at end of file diff --git a/src/fs/filesystem.rs b/src/fs/filesystem.rs new file mode 100644 index 0000000..6a4e46e --- /dev/null +++ b/src/fs/filesystem.rs @@ -0,0 +1,38 @@ +use alloc::sync::Arc; +use alloc::boxed::Box; +use core::option::Option; + +use crate::fs::Mode; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::Statfs; +use crate::driver::BlockDriverOps; + +use super::InodeOps; + +pub trait FileSystemOps: Send + Sync { + fn create(&self, fsno: u32, driver: Option>) -> SysResult>; +} + +pub trait SuperBlockOps: Send + Sync { + fn get_root_ino(&self) -> u32; + + fn get_inode(&self, ino: u32) -> SysResult>; + + fn create_temp(&self, _mode: Mode) -> SysResult> { + Err(Errno::EOPNOTSUPP) + } + + fn unmount(&self) -> SysResult<()> { + // Default implementation does nothing, can be overridden by specific filesystems + Ok(()) + } + + fn statfs(&self) -> SysResult { + Err(Errno::EOPNOTSUPP) + } + + fn sync(&self) -> SysResult<()> { + // Default implementation does nothing, can be overridden by specific filesystems + Ok(()) + } +} diff --git a/src/fs/init.rs b/src/fs/init.rs new file mode 100644 index 0000000..7a38c45 --- /dev/null +++ b/src/fs/init.rs @@ -0,0 +1,37 @@ +use crate::fs::vfs; +use crate::driver; +use crate::fs::Mode; +use crate::kinfo; + +#[unsafe(link_section = ".text.init")] +pub fn init() { + kinfo!("Initializing file system..."); + + vfs::init(); + + kinfo!("File system initialized successfully."); +} + +#[unsafe(link_section = ".text.init")] +pub fn mount_init_fs(device_name: &str, fs_type: &str) { + let blk_dev = driver::get_block_driver(device_name).unwrap(); + vfs::mount("/", fs_type, Some(blk_dev)).unwrap(); + + // Mount devfs at /dev + let _ = vfs::load_dentry("/").unwrap().create("dev", Mode::S_IFDIR); + vfs::mount("/dev", "devfs", None).unwrap(); + + // Mount tmpfs at /tmp + let _ = vfs::load_dentry("/").unwrap().create("tmp", Mode::S_IFDIR); + vfs::mount("/tmp", "tmpfs", None).unwrap(); + + // Try to access /dev/null and /dev/zero to ensure they are working + vfs::load_dentry("/dev/null").unwrap(); + vfs::load_dentry("/dev/zero").unwrap(); + + kinfo!("Init filesystem mounted successfully!"); +} + +pub fn fini() { + vfs::unmount_all().unwrap(); +} diff --git a/src/fs/inode/cache.rs b/src/fs/inode/cache.rs new file mode 100644 index 0000000..3be8e67 --- /dev/null +++ b/src/fs/inode/cache.rs @@ -0,0 +1,55 @@ +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use spin::Mutex; + +use crate::kernel::config; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kinfo; + +use super::{InodeOps, Index}; + +pub struct Cache { + cache: Mutex>>, +} + +impl Cache { + pub const fn new() -> Self { + Self { + cache: Mutex::new(BTreeMap::new()), + } + } + + pub fn find(&self, index: &Index) -> Option> { + self.cache.lock().get(index).cloned() + } + + pub fn insert(&self, index: &Index, inode: Arc) -> SysResult<()> { + let mut cache = self.cache.lock(); + + if cache.len() >= config::INODE_CACHE_SIZE { + let initial_size = cache.len(); + cache.retain(|_, inode| { + kinfo!("strong count {}", Arc::strong_count(inode)); + Arc::strong_count(inode) > 1 + }); + let final_size = cache.len(); + kinfo!("Uncached {} unused inodes, {} remaining", initial_size - final_size, final_size); + + if final_size >= config::INODE_CACHE_SIZE { + return Err(Errno::ENOSPC); + } + } + + cache.insert(*index, inode); + + Ok(()) + } + + pub fn sync(&self) -> SysResult<()> { + let cache = self.cache.lock(); + for (_, inode) in cache.iter() { + inode.sync()?; + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/fs/inode/index.rs b/src/fs/inode/index.rs new file mode 100644 index 0000000..8872e32 --- /dev/null +++ b/src/fs/inode/index.rs @@ -0,0 +1,15 @@ +#[derive(Eq, PartialEq, PartialOrd, Hash, Clone, Copy, Debug)] +pub struct Index { + pub sno: u32, + pub ino: u32, +} + +impl Ord for Index { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + if self.sno != other.sno { + self.sno.cmp(&other.sno) + } else { + self.ino.cmp(&other.ino) + } + } +} diff --git a/src/fs/inode/inode.rs b/src/fs/inode/inode.rs new file mode 100644 index 0000000..e37c46d --- /dev/null +++ b/src/fs/inode/inode.rs @@ -0,0 +1,116 @@ +use core::time::Duration; + +use alloc::string::String; +use alloc::sync::Arc; +use downcast_rs::{DowncastSync, impl_downcast}; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::{FileStat, Uid}; +use crate::fs::perm::Perm; +use crate::fs::file::DirResult; + +use super::{Mode, FileType}; + +pub trait InodeOps: DowncastSync { + fn get_ino(&self) -> u32; + + fn get_sno(&self) -> u32; + + fn type_name(&self) -> &'static str; + + fn create(&self, _name: &str, _mode: Mode) -> SysResult<()> { + Err(Errno::EOPNOTSUPP) + } + + fn unlink(&self, _name: &str) -> SysResult<()> { + Err(Errno::EOPNOTSUPP) + } + + fn readat(&self, _buf: &mut [u8], _offset: usize) -> Result { + unimplemented!() + } + + fn writeat(&self, _buf: &[u8], _offset: usize) -> Result { + unimplemented!() + } + + fn get_dent(&self, _index: usize) -> SysResult> { + Err(Errno::ENOSYS) + } + + fn lookup(&self, _name: &str) -> SysResult { + Err(Errno::ENOTDIR) + } + + fn rename(&self, _old_name: &str, _new_parent: &Arc, _new_name: &str) -> SysResult<()> { + Err(Errno::EOPNOTSUPP) + } + + fn size(&self) -> SysResult { + unimplemented!("{}", self.type_name()) + } + + fn mode(&self) -> SysResult { + Ok(Mode::empty()) + } + + fn chmod(&self, _mode: Mode) -> SysResult<()> { + Err(Errno::EOPNOTSUPP) + } + + fn owner(&self) -> SysResult<(Uid, Uid)> { + Ok((0, 0)) + } + + fn check_perm(&self, _perm: &Perm) -> SysResult<()> { + let (uid, gid) = self.owner()?; + let mode = self.mode()?; + if mode.check_perm(_perm, uid, gid) { + Ok(()) + } else { + Err(Errno::EACCES) + } + } + + fn inode_type(&self) -> SysResult { + self.mode().map(|inode| inode.into()) + } + + fn readlink(&self) -> SysResult { + Err(Errno::EINVAL) + } + + fn sync(&self) -> SysResult<()> { + Ok(()) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + kstat.st_ino = self.get_ino() as u64; + kstat.st_size = self.size()? as i64; + kstat.st_mode = self.mode()?.bits() as u32; + + Ok(kstat) + } + + fn truncate(&self, _new_size: u64) -> SysResult<()> { + Err(Errno::EOPNOTSUPP) + } + + fn update_atime(&self, time: &Duration) -> SysResult<()> { + let _ = time; + Ok(()) + } + + fn update_mtime(&self, time: &Duration) -> SysResult<()> { + let _ = time; + Ok(()) + } + + fn update_ctime(&self, time: &Duration) -> SysResult<()> { + let _ = time; + Ok(()) + } +} + +impl_downcast!(sync InodeOps); diff --git a/src/fs/inode/mod.rs b/src/fs/inode/mod.rs new file mode 100644 index 0000000..8362aa8 --- /dev/null +++ b/src/fs/inode/mod.rs @@ -0,0 +1,9 @@ +mod inode; +mod cache; +mod index; +mod mode; + +pub use inode::InodeOps; +pub use index::Index; +pub use cache::Cache; +pub use mode::{Mode, FileType}; diff --git a/src/fs/inode/mode.rs b/src/fs/inode/mode.rs new file mode 100644 index 0000000..bd544fd --- /dev/null +++ b/src/fs/inode/mode.rs @@ -0,0 +1,79 @@ +use bitflags::bitflags; + +use crate::{fs::perm::{Perm, PermFlags}, kernel::uapi::Uid}; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct Mode: u32 { + const S_IFMT = 0o170000; // bit mask for the file type bit field + + const S_IFSOCK = 0o140000; // socket + const S_IFLNK = 0o120000; // symbolic link + const S_IFREG = 0o100000; // regular file + const S_IFBLK = 0o060000; // block device + const S_IFDIR = 0o040000; // directory + const S_IFCHR = 0o020000; // character device + const S_IFIFO = 0o010000; // FIFO + + const S_ISUID = 0o4000; // set-user-ID bit + const S_ISGID = 0o2000; // set-group-ID bit + const S_ISVTX = 0o1000; // sticky bit + + const S_IRUSR = 0o0400; // owner has read permission + const S_IWUSR = 0o0200; // owner has write permission + const S_IXUSR = 0o0100; // owner has execute permission + + const S_IRGRP = 0o0040; // group has read permission + const S_IWGRP = 0o0020; // group has write permission + const S_IXGRP = 0o0010; // group has execute permission + + const S_IROTH = 0o0004; // others have read permission + const S_IWOTH = 0o0002; // others have write permission + const S_IXOTH = 0o0001; // others have execute permission + } +} + +impl Mode { + pub fn check_perm(&self, perm: &Perm, uid: Uid, gid: Uid) -> bool { + let (read_bit, write_bit, exec_bit) = if perm.uid == uid { + (Mode::S_IRUSR, Mode::S_IWUSR, Mode::S_IXUSR) + } else if perm.gid == gid { + (Mode::S_IRGRP, Mode::S_IWGRP, Mode::S_IXGRP) + } else { + (Mode::S_IROTH, Mode::S_IWOTH, Mode::S_IXOTH) + }; + + // (a & b) | ~a = ~a | b + + (!perm.flags.contains(PermFlags::R) || self.contains(read_bit)) && + (!perm.flags.contains(PermFlags::W) || self.contains(write_bit)) && + (!perm.flags.contains(PermFlags::X) || self.contains(exec_bit)) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FileType { + Regular, + Directory, + Symlink, + CharDevice, + BlockDevice, + FIFO, + Socket, + Unknown, +} + +impl Into for Mode { + fn into(self) -> FileType { + match self & Mode::S_IFMT { + Mode::S_IFREG => FileType::Regular, + Mode::S_IFDIR => FileType::Directory, + Mode::S_IFLNK => FileType::Symlink, + Mode::S_IFCHR => FileType::CharDevice, + Mode::S_IFBLK => FileType::BlockDevice, + Mode::S_IFIFO => FileType::FIFO, + Mode::S_IFSOCK => FileType::Socket, + _ => FileType::Unknown, + } + } +} diff --git a/src/fs/memfs/filesystem.rs b/src/fs/memfs/filesystem.rs new file mode 100644 index 0000000..5ec59b3 --- /dev/null +++ b/src/fs/memfs/filesystem.rs @@ -0,0 +1,27 @@ +use alloc::sync::Arc; +use alloc::boxed::Box; + +use crate::kernel::errno::Errno; +use crate::fs::filesystem::{FileSystem, SuperBlock}; +use crate::driver::block::BlockDriver; + +use super::superblock::MemoryFileSystemSuperBlock; + +pub struct MemoryFileSystem; + +impl MemoryFileSystem { + pub fn new() -> Self { + MemoryFileSystem {} + } +} + +unsafe extern "C" { + static __bin_resource_start: u8; + static __bin_resource_end : u8; +} + +impl FileSystem for MemoryFileSystem { + fn create(&self, sno: u32, _driver: Option>) -> Result, Errno> { + Ok(MemoryFileSystemSuperBlock::new(sno)) + } +} diff --git a/src/fs/memfs/inode.rs b/src/fs/memfs/inode.rs new file mode 100644 index 0000000..22cc860 --- /dev/null +++ b/src/fs/memfs/inode.rs @@ -0,0 +1,51 @@ +use alloc::sync::Weak; + +use super::superblock::MemoryFileSystemSuperBlock; +use crate::fs::inode::Inode; +use crate::kernel::errno::{Errno, SysResult}; + +#[derive(Clone)] +pub struct MemoryFileSystemInode { + pub ino: u32, + pub superblock: Weak, + + pub start: *mut u8, + pub size : usize, +} + +unsafe impl Send for MemoryFileSystemInode {} +unsafe impl Sync for MemoryFileSystemInode {} + +impl Inode for MemoryFileSystemInode { + fn get_ino(&self) -> u32 { + self.ino + } + + fn get_sno(&self) -> u32 { + self.superblock.upgrade().expect("MemoryFileSystemInode: superblock is gone").get_fsno() + } + + fn type_name(&self) -> &'static str { + "memfs" + } + + fn readat(&self, buf: &mut [u8], offset: usize) -> Result { + if self.size <= offset { + return Ok(0); + } + let len = core::cmp::min(self.size - offset, buf.len()); + buf[..len].copy_from_slice(unsafe { + core::slice::from_raw_parts(self.start.add(offset), len) + }); + Ok(len) + } + + fn writeat(&self, _buf: &[u8], _offset: usize) -> SysResult { + Ok(0) + } + + fn lookup(&self, name: &str) -> SysResult { + let superblock = self.superblock.upgrade().ok_or(Errno::ENOENT)?; + superblock.lookup(self.ino, name).ok_or(Errno::ENOENT) + } +} \ No newline at end of file diff --git a/src/fs/memfs/mod.rs b/src/fs/memfs/mod.rs new file mode 100644 index 0000000..d228735 --- /dev/null +++ b/src/fs/memfs/mod.rs @@ -0,0 +1,5 @@ +mod filesystem; +mod superblock; +mod inode; + +pub use filesystem::MemoryFileSystem; diff --git a/src/fs/memfs/superblock.rs b/src/fs/memfs/superblock.rs new file mode 100644 index 0000000..43ff5d1 --- /dev/null +++ b/src/fs/memfs/superblock.rs @@ -0,0 +1,91 @@ +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::Mutex; + +use super::inode::MemoryFileSystemInode; +use crate::fs::inode::Inode; +use crate::fs::filesystem::SuperBlock; +use crate::kernel::errno::Errno; + +struct Entry { + pub inode: MemoryFileSystemInode, + pub entries: BTreeMap, +} + +pub struct MemoryFileSystemSuperBlock { + sno: u32, + entries: Mutex>, +} + +unsafe extern "C" { + static __bin_resource_start: u8; + static __bin_resource_end : u8; +} + +impl MemoryFileSystemSuperBlock { + pub fn new(sno: u32) -> Arc { + // let this = Arc::new(MemoryFileSystemSuperBlock { + // sno, + // entries: Mutex::new(Vec::new()), + // }); + + // let mut entries = this.entries.lock(); + + // entries.push(Entry { + // inode: MemoryFileSystemInode { + // ino: 0, + // superblock: Arc::downgrade(&this), + // start: 0 as *mut u8, + // size: 0, + // }, + // entries: BTreeMap::new(), + // }); + // unsafe { + // entries.push(Entry { + // inode: MemoryFileSystemInode { + // ino: 1, + // superblock: Arc::downgrade(&this), + // start: &__bin_resource_start as *const u8 as *mut u8, + // size: &__bin_resource_end as *const u8 as usize - &__bin_resource_start as *const u8 as usize, + // }, + // entries: BTreeMap::new(), + // }); + // } + + // entries[0].entries.insert(String::from("init"), 1); + + // drop(entries); + + // this + + unimplemented!() + } + + pub fn get_fsno(&self) -> u32 { + self.sno + } + + pub fn lookup(&self, ino: u32, name: &str) -> Option { + let entries = self.entries.lock(); + if let Some(entry) = entries.get(ino as usize) { + entry.entries.get(name).cloned() + } else { + None + } + } +} + +impl SuperBlock for MemoryFileSystemSuperBlock { + fn get_root_ino(&self) -> u32 { + 0 + } + + fn get_inode(&self, ino: u32) -> Result, Errno> { + self.entries.lock().get(ino as usize).map(|entry| { + Box::new(entry.inode.clone()) as Box + }).ok_or(Errno::ENOENT) + } +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..c314fec --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,16 @@ +pub mod file; +pub mod vfs; +pub mod inode; +mod init; + +mod perm; +mod filesystem; +mod ext4; +mod devfs; +mod rootfs; +mod tmpfs; + +pub use init::{init, mount_init_fs, fini}; +pub use inode::{InodeOps, Mode, FileType}; +pub use perm::{Perm, PermFlags}; +pub use vfs::Dentry; diff --git a/src/fs/mount/manager.rs b/src/fs/mount/manager.rs new file mode 100644 index 0000000..bb4757c --- /dev/null +++ b/src/fs/mount/manager.rs @@ -0,0 +1,29 @@ +use alloc::collections::BTreeMap; + +use crate::fs::inode; + +pub struct Manager { + mounts: BTreeMap, +} + +impl Manager { + pub const fn new() -> Self { + Manager { + mounts: BTreeMap::new(), + } + } + + pub fn add_mount(&mut self, sno: u32, ino: u32, mount_sno: u32, mount_ino: u32) { + self.mounts.insert( + inode::Index { sno, ino }, + inode::Index { + sno: mount_sno, + ino: mount_ino, + }, + ); + } + + pub fn get_mount(&self, sno: u32, ino: u32) -> Option { + self.mounts.get(&inode::Index { sno, ino }).cloned() + } +} diff --git a/src/fs/mount/mod.rs b/src/fs/mount/mod.rs new file mode 100644 index 0000000..99d5fa7 --- /dev/null +++ b/src/fs/mount/mod.rs @@ -0,0 +1,3 @@ +pub mod manager; + +pub use manager::Manager; diff --git a/src/fs/perm.rs b/src/fs/perm.rs new file mode 100644 index 0000000..cc2db13 --- /dev/null +++ b/src/fs/perm.rs @@ -0,0 +1,29 @@ +use bitflags::bitflags; + +use crate::kernel::uapi::Uid; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct PermFlags: u8 { + const R = 1 << 0; + const W = 1 << 1; + const X = 1 << 2; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Perm { + pub uid: Uid, + pub gid: Uid, + pub flags: PermFlags, +} + +impl Perm { + pub fn new(flags: PermFlags) -> Self { + Self { uid: 0, gid: 0, flags } + } + + pub fn dontcare() -> Self { + Self { uid: 0, gid: 0, flags: PermFlags::empty() } + } +} diff --git a/src/fs/rootfs/mod.rs b/src/fs/rootfs/mod.rs new file mode 100644 index 0000000..28845f8 --- /dev/null +++ b/src/fs/rootfs/mod.rs @@ -0,0 +1,3 @@ +mod superblock; + +pub use superblock::*; diff --git a/src/fs/rootfs/superblock.rs b/src/fs/rootfs/superblock.rs new file mode 100644 index 0000000..4b95d67 --- /dev/null +++ b/src/fs/rootfs/superblock.rs @@ -0,0 +1,71 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; + +use crate::kernel::errno::Errno; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use crate::fs::InodeOps; +use crate::driver::BlockDriverOps; + +#[derive(Debug, Clone)] +pub struct RootInode; + +unsafe impl Send for RootInode {} +unsafe impl Sync for RootInode {} + +impl RootInode { + pub const fn new() -> Self { + RootInode + } +} + +impl InodeOps for RootInode { + fn get_ino(&self) -> u32 { + 0 + } + + fn get_sno(&self) -> u32 { + 0 + } + + fn type_name(&self) -> &'static str { + "rootfs" + } + + fn readat(&self, _buf: &mut [u8], _offset: usize) -> Result { + Ok(0) + } + + fn writeat(&self, _buf: &[u8], _offset: usize) -> Result { + Ok(0) + } + + fn lookup(&self, _name: &str) -> Result { + Err(Errno::ENOENT) + } +} + +pub struct RootFileSystem; + +pub struct RootFileSystemSuperBlock; + +impl RootFileSystemSuperBlock { + pub const fn new() -> Self { + RootFileSystemSuperBlock {} + } +} + +impl SuperBlockOps for RootFileSystemSuperBlock { + fn get_root_ino(&self) -> u32 { + 0 + } + + fn get_inode(&self, _ino: u32) -> Result, Errno> { + Ok(Box::new(RootInode::new())) + } +} + +impl FileSystemOps for RootFileSystem { + fn create(&self, _fsno: u32, _driver: Option>) -> Result, Errno> { + Ok(Arc::new(RootFileSystemSuperBlock::new())) + } +} diff --git a/src/fs/tmpfs/filesystem.rs b/src/fs/tmpfs/filesystem.rs new file mode 100644 index 0000000..0dc0731 --- /dev/null +++ b/src/fs/tmpfs/filesystem.rs @@ -0,0 +1,15 @@ +use alloc::sync::Arc; + +use crate::driver::BlockDriverOps; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use crate::kernel::errno::SysResult; + +use super::superblock::SuperBlock; + +pub struct FileSystem; + +impl FileSystemOps for FileSystem { + fn create(&self, sno: u32, _driver: Option>) -> SysResult> { + Ok(Arc::new(SuperBlock::new(sno))) + } +} diff --git a/src/fs/tmpfs/inode.rs b/src/fs/tmpfs/inode.rs new file mode 100644 index 0000000..aac20d3 --- /dev/null +++ b/src/fs/tmpfs/inode.rs @@ -0,0 +1,272 @@ +use alloc::sync::Arc; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; +use core::time::Duration; + +use crate::kernel::errno::{SysResult, Errno}; +use crate::fs::InodeOps; +use crate::fs::inode::Mode; +use crate::kernel::mm::PhysPageFrame; +use crate::kernel::uapi::{FileStat, Uid}; +use crate::klib::SpinLock; +use crate::arch; + +use super::superblock::SuperBlockInner; + +struct Timespec { + tv_sec: u64, + tv_nsec: u64, +} + +impl Default for Timespec { + fn default() -> Self { + Timespec { tv_sec: 0, tv_nsec: 0 } + } +} + +struct FileMeta { + pages: Vec, + filesize: usize, +} + +impl FileMeta { + fn new() -> Self { + Self { + pages: Vec::new(), + filesize: 0, + } + } +} + +enum Meta { + File (FileMeta), + Directory(BTreeMap) +} + +pub struct InodeMeta { + meta: Meta, + mode: Mode, + owner: (Uid, Uid), + mtime: Timespec, + atime: Timespec, + ctime: Timespec, +} + +impl InodeMeta { + pub fn new(mode: Mode) -> Self { + let meta = if mode.contains(Mode::S_IFDIR) { + Meta::Directory(BTreeMap::new()) + } else { + Meta::File(FileMeta::new()) + }; + Self { + meta, + mode, + owner: (0, 0), + mtime: Timespec::default(), + atime: Timespec::default(), + ctime: Timespec::default(), + } + } +} + +pub struct Inode { + ino: u32, + sno: u32, + meta: Arc>, + superblock: Arc>, +} + +impl Inode { + pub fn new(ino: u32, sno: u32, meta: Arc>, superblock: Arc>) -> Self { + Self { + ino, + sno, + meta, + superblock, + } + + } +} + +impl InodeOps for Inode { + fn create(&self, name: &str, mode: Mode) -> SysResult<()> { + if let Meta::Directory(ref mut children) = self.meta.lock().meta { + if children.contains_key(name) { + return Err(Errno::EEXIST); + } + + let ino = self.superblock.lock().alloc_inode(mode); + children.insert(name.into(), ino); + + Ok(()) + } else { + Err(Errno::ENOTDIR) + } + } + + fn get_ino(&self) -> u32 { + self.ino + } + + fn get_sno(&self) -> u32 { + self.sno + } + + fn lookup(&self, name: &str) -> SysResult { + if let Meta::Directory(ref children) = self.meta.lock().meta { + if let Some(&ino) = children.get(name) { + Ok(ino) + } else { + Err(Errno::ENOENT) + } + } else { + Err(Errno::ENOTDIR) + } + } + + fn readat(&self, buf: &mut [u8], offset: usize) -> SysResult { + if let Meta::File(ref file_meta) = self.meta.lock().meta { + if offset >= file_meta.filesize { + return Ok(0); + } + + let mut total_read = 0; + let mut current_offset = offset; + let mut left = core::cmp::min(buf.len(), file_meta.filesize - offset); + + while left > 0 { + let page_index = current_offset / arch::PGSIZE; + let page_offset = current_offset % arch::PGSIZE; + + if page_index >= file_meta.pages.len() { + break; + } + + let page = &file_meta.pages[page_index]; + let to_read = core::cmp::min(left, 4096 - page_offset); + + page.copy_to_slice(page_offset, &mut buf[total_read..total_read+to_read]); + + left -= to_read; + total_read += to_read; + current_offset += to_read; + } + + Ok(total_read) + } else { + Err(Errno::EISDIR) + } + } + + fn writeat(&self, buf: &[u8], offset: usize) -> Result { + if let Meta::File ( ref mut meta ) = self.meta.lock().meta { + let mut written_bytes = 0; + let mut current_offset = offset; + + while written_bytes < buf.len() { + let page_index = current_offset / arch::PGSIZE; + let page_offset = current_offset % arch::PGSIZE; + + while page_index >= meta.pages.len() { + meta.pages.push(PhysPageFrame::alloc()); + } + + let page = &meta.pages[page_index]; + let to_write = core::cmp::min(buf.len() - written_bytes, 4096 - page_offset); + + page.copy_from_slice(page_offset, &buf[written_bytes..written_bytes+to_write]); + + written_bytes += to_write; + current_offset += to_write; + } + + meta.filesize = core::cmp::max(meta.filesize, offset + written_bytes); + + Ok(written_bytes) + } else { + Err(Errno::EISDIR) + } + } + + fn unlink(&self, name: &str) -> SysResult<()> { + let mut meta = self.meta.lock(); + if let Meta::Directory(children) = &mut meta.meta { + let ino = children.remove(name).ok_or(Errno::ENOENT)?; + self.superblock.lock().remove_inode(ino); + Ok(()) + } else { + Err(Errno::ENOTDIR) + } + } + + fn size(&self) -> SysResult { + let size = match self.meta.lock().meta { + Meta::File(ref meta) => meta.filesize, + Meta::Directory(_) => arch::PGSIZE, + }; + Ok(size as u64) + } + + fn mode(&self) -> SysResult { + Ok(self.meta.lock().mode) + } + + fn owner(&self) -> SysResult<(Uid, Uid)> { + Ok(self.meta.lock().owner) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::default(); + + let meta = self.meta.lock(); + kstat.st_ino = self.ino as u64; + kstat.st_mode = meta.mode.bits() as u32; + kstat.st_blksize = arch::PGSIZE as i32; + kstat.st_nlink = 1; + kstat.st_atime_sec = meta.atime.tv_sec as i64; + kstat.st_atime_nsec = meta.atime.tv_nsec as i64; + kstat.st_mtime_sec = meta.mtime.tv_sec as i64; + kstat.st_mtime_nsec = meta.mtime.tv_nsec as i64; + kstat.st_ctime_sec = meta.ctime.tv_sec as i64; + kstat.st_ctime_nsec = meta.ctime.tv_nsec as i64; + + match meta.meta { + Meta::File(ref meta) => { + kstat.st_size = meta.filesize as i64; + kstat.st_blocks = meta.pages.len() as u64; + } + Meta::Directory(_) => { + kstat.st_size = arch::PGSIZE as i64; + kstat.st_blocks = 1; + } + } + + Ok(kstat) + } + fn update_atime(&self, time: &Duration) -> SysResult<()> { + let mut meta = self.meta.lock(); + meta.atime.tv_sec = time.as_secs(); + meta.atime.tv_nsec = time.subsec_nanos() as u64; + Ok(()) + } + + fn update_mtime(&self, time: &Duration) -> SysResult<()> { + let mut meta = self.meta.lock(); + meta.mtime.tv_sec = time.as_secs(); + meta.mtime.tv_nsec = time.subsec_nanos() as u64; + Ok(()) + } + + fn update_ctime(&self, time: &Duration) -> SysResult<()> { + let mut meta = self.meta.lock(); + meta.ctime.tv_sec = time.as_secs(); + meta.ctime.tv_nsec = time.subsec_nanos() as u64; + Ok(()) + } + + fn type_name(&self) -> &'static str { + "tmp" + } +} diff --git a/src/fs/tmpfs/mod.rs b/src/fs/tmpfs/mod.rs new file mode 100644 index 0000000..095ed9a --- /dev/null +++ b/src/fs/tmpfs/mod.rs @@ -0,0 +1,5 @@ +mod superblock; +mod filesystem; +mod inode; + +pub use filesystem::FileSystem; diff --git a/src/fs/tmpfs/superblock.rs b/src/fs/tmpfs/superblock.rs new file mode 100644 index 0000000..13c99b7 --- /dev/null +++ b/src/fs/tmpfs/superblock.rs @@ -0,0 +1,76 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::collections::BTreeMap; + +use crate::kernel::errno::{SysResult, Errno}; +use crate::fs::filesystem::SuperBlockOps; +use crate::fs::{InodeOps, Mode}; +use crate::klib::SpinLock; + +use super::inode::{InodeMeta, Inode}; + +pub struct SuperBlockInner { + inode_map: BTreeMap>>, + max_inode: u32 +} + +impl SuperBlockInner { + pub fn new() -> Self { + let mut inode_map = BTreeMap::new(); + inode_map.insert(0, Arc::new(SpinLock::new(InodeMeta::new(Mode::S_IFDIR)))); + Self { + inode_map, + max_inode: 1 + } + } + + pub fn alloc_inode_number(&mut self) -> u32 { + let ino = self.max_inode; + self.max_inode += 1; + ino + } + + pub fn alloc_inode(&mut self, inode_mode: Mode) -> u32 { + let ino = self.alloc_inode_number(); + self.inode_map.insert(ino, Arc::new(SpinLock::new(InodeMeta::new(inode_mode)))); + + ino + } + + pub fn remove_inode(&mut self, ino: u32) { + self.inode_map.remove(&ino); + } +} + +pub struct SuperBlock { + sno: u32, + inner: Arc>, +} + +impl SuperBlock { + pub fn new(sno: u32) -> Self { + Self { + sno, + inner: Arc::new(SpinLock::new(SuperBlockInner::new())), + } + } +} + +impl SuperBlockOps for SuperBlock { + fn get_root_ino(&self) -> u32 { + 0 + } + + fn get_inode(&self, ino: u32) -> SysResult> { + let meta = self.inner.lock().inode_map.get(&ino) + .ok_or(Errno::ENOENT)? + .clone(); + Ok(Box::new(Inode::new(ino, self.sno, meta, self.inner.clone()))) + } + + fn create_temp(&self, mode: Mode) -> SysResult> { + let mut inner = self.inner.lock(); + let ino = inner.alloc_inode_number(); + Ok(Box::new(Inode::new(ino, self.sno, Arc::new(SpinLock::new(InodeMeta::new(mode))), self.inner.clone()))) + } +} diff --git a/src/fs/vfs/dentry.rs b/src/fs/vfs/dentry.rs new file mode 100644 index 0000000..df7d00d --- /dev/null +++ b/src/fs/vfs/dentry.rs @@ -0,0 +1,199 @@ +use core::fmt::Debug; + +use alloc::sync::{Arc, Weak}; +use alloc::string::String; +use alloc::collections::BTreeMap; + +use crate::kernel::errno::{SysResult, Errno}; +use crate::fs::inode::{Index, InodeOps, Mode}; +use crate::klib::SpinLock; + +use super::vfs; + +pub struct Dentry { + inode_index: Index, + name: String, + parent: SpinLock>>, + children: SpinLock>>, + inode: SpinLock>, + mount_to: SpinLock>>, +} + +impl Dentry { + pub fn new(name: &str, parent: &Arc, inode: &Arc) -> Self { + Self { + inode_index: Index { sno: inode.get_sno(), ino: inode.get_ino() }, + name: name.into(), + parent: SpinLock::new(Some(parent.clone())), + children: SpinLock::new(BTreeMap::new()), + inode: SpinLock::new(Arc::downgrade(inode)), + mount_to: SpinLock::new(None), + } + } + + pub fn root(inode: &Arc) -> Self { + Self { + inode_index: Index { sno: inode.get_sno(), ino: inode.get_ino() }, + name: "/".into(), + parent: SpinLock::new(None), + children: SpinLock::new(BTreeMap::new()), + inode: SpinLock::new(Arc::downgrade(inode)), + mount_to: SpinLock::new(None), + } + } + + pub fn sno(&self) -> u32 { + self.inode_index.sno + } + + pub fn ino(&self) -> u32 { + self.inode_index.ino + } + + pub fn get_inode_index(&self) -> Index { + self.inode_index + } + + pub fn get_inode(&self) -> Arc { + let inode = self.inode.lock(); + match inode.upgrade() { + None => { + drop(inode); + let inode = vfs().load_inode(self.sno(), self.ino()).expect("Failed to load inode"); + *self.inode.lock() = Arc::downgrade(&inode); + inode + } + Some(inode) => inode, + } + } + + pub fn get_parent(&self) -> Option> { + (*self.parent.lock()).clone() + } + + pub fn lookup(self: &Arc, name: &str) -> SysResult> { + let mut children = self.children.lock(); + + if let Some(child) = children.get(name) { + if let Some(child) = child.upgrade() { + return Ok(child); + } + } + let lookup_ino = self.get_inode().lookup(name)?; + let lookup_sno = self.sno(); + let inode = vfs().load_inode(lookup_sno, lookup_ino)?; + + let new_child = Arc::new(Self::new(name, self, &inode)); + children.insert(name.into(), Arc::downgrade(&new_child)); + + Ok(new_child) + } + + pub fn get_mount_to(self: Arc) -> Arc { + if let Some(mount_to) = &*self.mount_to.lock() { + mount_to.clone() + } else { + self + } + } + + pub fn walk_link(self: Arc) -> SysResult> { + let parent = self.parent.lock(); + if let Some(p) = &*parent { + let inode = self.get_inode(); + if inode.mode()?.contains(Mode::S_IFLNK) { + let mut buffer = [0u8; 255]; + let count = inode.readat(&mut buffer, 0)?; + let link_path = core::str::from_utf8(&buffer[..count]).map_err(|_| Errno::EINVAL)?; + let r = vfs().lookup_dentry(&p, &link_path)?; + Ok(r) + } else { + drop(parent); + Ok(self) + } + } else { + drop(parent); + Ok(self) + } + } + + pub fn mount(self: &Arc, mount_to: &Arc) { + *self.mount_to.lock() = Some(Arc::new( + Dentry { + inode_index: Index { sno: mount_to.get_sno(), ino: mount_to.get_ino() }, + name: self.name.clone(), + parent: SpinLock::new(self.parent.lock().clone()), + children: SpinLock::new(BTreeMap::new()), + inode: SpinLock::new(Arc::downgrade(mount_to)), + mount_to: SpinLock::new(None), + } + )); + } + + pub fn get_path(&self) -> String { + let parent = self.parent.lock(); + if let Some(parent) = &*parent { + let mut path = parent.get_path(); + if !path.ends_with('/') { + path.push('/'); + } + if self.name != "/" { + path.push_str(&self.name); + } + path + } else { + self.name.clone() + } + } + + pub fn create(self: &Arc, name: &str, mode: Mode) -> SysResult<()> { + if let Ok(_) = self.lookup(name) { + return Err(Errno::EEXIST); + } + + let inode = self.get_inode(); + + inode.create(name, mode) + } + + pub fn unlink(self: &Arc, name: &str) -> SysResult<()> { + self.get_inode().unlink(name)?; + + self.children.lock().remove(name); + + Ok(()) + } + + pub fn rename(self: &Arc, old_name: &str, new_parent: &Arc, new_name: &str) -> SysResult<()> { + assert!(self.sno() == new_parent.sno()); + assert!(old_name != "." && old_name != ".."); + assert!(new_name != "." && new_name != ".."); + + let old_parent_inode = self.get_inode(); + let new_parent_inode = new_parent.get_inode(); + old_parent_inode.rename(old_name, &new_parent_inode, new_name)?; + + self.children.lock().remove(old_name); + + Ok(()) + } + + pub fn readlink(&self) -> SysResult { + // let mut inner = self.inner.lock(); + + // if let Some(mount_to) = &inner.mount_to { + // return mount_to.readlink(); + // } + + // let inode = self.get_inode_inner(&mut inner)?; + + // inode.readlink() + Ok("".into()) // TODO + } +} + +impl Debug for Dentry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Dentry {{ sno: {}, ino: {}, name: {} }}", self.sno(), self.ino(), self.name) + } +} diff --git a/src/fs/vfs/fileop.rs b/src/fs/vfs/fileop.rs new file mode 100644 index 0000000..4a21537 --- /dev/null +++ b/src/fs/vfs/fileop.rs @@ -0,0 +1,64 @@ +use alloc::sync::Arc; + +use crate::fs::inode::{InodeOps, Mode}; +use crate::fs::perm::Perm; +use crate::fs::vfs::dentry::Dentry; +use crate::fs::file::{File, FileFlags}; +use crate::kernel::errno::{Errno, SysResult}; + +use super::vfs; + +fn new_file(dentry: &Arc, flags: FileFlags, perm: &Perm) -> SysResult { + let inode = dentry.get_inode(); + let mode = inode.mode()?; + if mode.contains(Mode::S_IFIFO) { + unimplemented!() // return Pipe::new_fifo(...); + } + + // let (uid, gid) = inode.owner()?; + // if !mode.check_perm(perm, uid, gid) { + // return Err(Errno::EACCES); + // } + + Ok(File::new(dentry, flags)) +} + +pub fn load_dentry(path: &str) -> Result, Errno> { + vfs().lookup_dentry(vfs().get_root(), path) +} + +pub fn load_parent_dentry<'a>(path: &'a str) -> SysResult<(Arc, &'a str)> { + vfs().lookup_parent_dentry(vfs().get_root(), path) +} + +pub fn open_file(path: &str, flags: FileFlags, perm: &Perm) -> SysResult { + let dentry = vfs().lookup_dentry(vfs().get_root(), path)?; + new_file(&dentry, flags, perm) +} + +pub fn load_dentry_at(dir: &Arc, path: &str) -> SysResult> { + vfs().lookup_dentry(dir, path) +} + +pub fn load_parent_dentry_at<'a>(dir: &Arc, path: &'a str) -> SysResult<(Arc, &'a str)> { + vfs().lookup_parent_dentry(dir, path) +} + +pub fn openat_file(dir: &Arc, path: &str, flags: FileFlags, perm: &Perm) -> SysResult { + let dentry = vfs().lookup_dentry(dir, path)?; + new_file(&dentry, flags, perm) +} + +pub fn load_inode(sno: u32, ino: u32) -> SysResult> { + vfs().load_inode(sno, ino) +} + +pub fn create_temp(dentry: &Arc, flags: FileFlags, mode: Mode) -> SysResult { + let superblock = vfs().superblock_table.lock().get(dentry.sno()).ok_or(Errno::ENOENT)?; + let inode = superblock.create_temp(mode)?; + + let inode: Arc = Arc::from(inode); + let dentry = Arc::new(Dentry::new("", dentry, &inode)); + + Ok(File::new_inode(inode, dentry, flags)) +} diff --git a/src/fs/vfs/fsop.rs b/src/fs/vfs/fsop.rs new file mode 100644 index 0000000..dd41c83 --- /dev/null +++ b/src/fs/vfs/fsop.rs @@ -0,0 +1,74 @@ +use alloc::sync::Arc; + +use crate::fs::vfs::vfs::VirtualFileSystem; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::Statfs; +use crate::driver::BlockDriverOps; + +use super::vfs; +use super::Dentry; + +impl VirtualFileSystem { + pub(super) fn register_filesystem(&mut self, name: &'static str, fs: &'static dyn FileSystemOps) { + self.fstype_map.insert(name, fs); + } + + fn get_superblock(&self, sno: u32) -> SysResult> { + let superblock_table = self.superblock_table.lock(); + let superblock = superblock_table.get(sno).ok_or(Errno::EINVAL)?; + + Ok(superblock) + } + + fn mount(&self, path: &str, fstype_name: &str, device: Option>) -> SysResult<()> { + let dentry = self.lookup_dentry(self.get_root(), path)?; + + let fstype = self.fstype_map.get(fstype_name).ok_or(Errno::ENOENT)?; + + let (sno, root_ino) = { + let mut superblock_table = self.superblock_table.lock(); + let sno = superblock_table.alloc(*fstype, device)?; + (sno, superblock_table.get(sno).unwrap().get_root_ino()) + }; + + let root_inode = self.load_inode(sno, root_ino)?; + + dentry.mount(&root_inode); + + self.mountpoint.lock().push(dentry); + + Ok(()) + } + + fn sync_all(&self) -> SysResult<()> { + self.cache.sync()?; + self.superblock_table.lock().sync_all()?; + Ok(()) + } +} + +pub fn mount(path: &str, fstype_name: &str, device: Option>) -> Result<(), Errno> { + vfs().mount(path, fstype_name, device) +} + +pub fn get_root_dentry() -> &'static Arc { + vfs().get_root() +} + +pub fn statfs(sno: u32) -> SysResult { + let superblock = vfs().get_superblock(sno).unwrap(); + + superblock.statfs() +} + +pub fn sync_all() -> Result<(), Errno> { + vfs().sync_all() +} + +pub fn unmount_all() -> SysResult<()> { + let superblock_table = vfs().superblock_table.lock(); + superblock_table.unmount_all()?; + + Ok(()) +} diff --git a/src/fs/vfs/init.rs b/src/fs/vfs/init.rs new file mode 100644 index 0000000..7ba7ea4 --- /dev/null +++ b/src/fs/vfs/init.rs @@ -0,0 +1,22 @@ +use alloc::sync::Arc; + +use crate::fs::devfs::DevFileSystem; +use crate::fs::ext4::Ext4FileSystem; +use crate::fs::tmpfs; +use crate::fs::rootfs::RootFileSystem; +use crate::fs::vfs::VFS; +use crate::fs::vfs::vfs::VirtualFileSystem; +use crate::fs::Dentry; + +#[unsafe(link_section = ".text.init")] +pub fn init() { + let mut vfs = VirtualFileSystem::new(); + vfs.register_filesystem("devfs", &DevFileSystem); + vfs.register_filesystem("ext4", &Ext4FileSystem); + vfs.register_filesystem("tmpfs", &tmpfs::FileSystem); + + vfs.superblock_table.lock().alloc(&RootFileSystem, None).unwrap(); + vfs.root.init(Arc::new(Dentry::root(&vfs.load_inode(0, 0).unwrap()))); + + VFS.init(vfs); +} \ No newline at end of file diff --git a/src/fs/vfs/mod.rs b/src/fs/vfs/mod.rs new file mode 100644 index 0000000..7a0d9ea --- /dev/null +++ b/src/fs/vfs/mod.rs @@ -0,0 +1,22 @@ +mod vfs; +mod fileop; +mod fsop; +mod dentry; +mod superblock_table; +mod init; + +use superblock_table::SuperBlockTable; + +pub use dentry::Dentry; +pub use fileop::*; +pub use fsop::*; +pub use init::init; + +use vfs::VirtualFileSystem; +use crate::klib::InitedCell; + +static VFS: InitedCell = InitedCell::uninit(); + +pub(super) fn vfs() -> &'static VirtualFileSystem { + &VFS +} diff --git a/src/fs/vfs/superblock_table.rs b/src/fs/vfs/superblock_table.rs new file mode 100644 index 0000000..9f3eb1d --- /dev/null +++ b/src/fs/vfs/superblock_table.rs @@ -0,0 +1,49 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; + +use crate::kernel::errno::SysResult; +use crate::fs::filesystem::{FileSystemOps, SuperBlockOps}; +use crate::driver::BlockDriverOps; + +pub struct SuperBlockTable { + table: Vec>> +} + +impl SuperBlockTable { + pub const fn new() -> Self { + SuperBlockTable { + table: Vec::new(), + } + } + + pub fn alloc(&mut self, fs: &'static dyn FileSystemOps, driver: Option>) -> SysResult { + let sno = self.table.len(); + let superblock = fs.create(sno as u32, driver)?; + self.table.push(Some(superblock)); + Ok(sno as u32) + } + + pub fn get(&self, sno: u32) -> Option> { + let fs = self.table.get(sno as usize)?; + fs.clone() + } + + pub fn unmount_all(&self) -> SysResult<()> { + for fs in &self.table { + if let Some(sb) = fs { + sb.sync()?; + sb.unmount()?; + } + } + Ok(()) + } + + pub fn sync_all(&self) -> SysResult<()> { + for fs in &self.table { + if let Some(sb) = fs { + let _ = sb.sync(); + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/fs/vfs/vfs.rs b/src/fs/vfs/vfs.rs new file mode 100644 index 0000000..6214708 --- /dev/null +++ b/src/fs/vfs/vfs.rs @@ -0,0 +1,105 @@ +use alloc::sync::Arc; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use spin::mutex::Mutex; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::fs::inode::InodeOps; +use crate::fs::inode; +use crate::fs::filesystem::FileSystemOps; +use crate::klib::InitedCell; + +use super::dentry::Dentry; +use super::SuperBlockTable; + +pub struct VirtualFileSystem { + pub(super) cache: inode::Cache, + pub(super) mountpoint: Mutex>>, + pub superblock_table: Mutex, + pub(super) fstype_map: BTreeMap<&'static str, &'static dyn FileSystemOps>, + pub(super) root: InitedCell>, +} + +impl VirtualFileSystem { + pub fn new() -> Self { + let vfs = VirtualFileSystem { + cache: inode::Cache::new(), + mountpoint: Mutex::new(Vec::new()), + superblock_table: Mutex::new(SuperBlockTable::new()), + fstype_map: BTreeMap::new(), + root: InitedCell::uninit(), + }; + + vfs + } + + pub fn get_root(&self) -> &Arc { + &self.root + } + + pub fn lookup_dentry(&self, dir: &Arc, path: &str) -> SysResult> { + let mut current = match path.chars().next() { + Some('/') => self.get_root().clone(), + _ => dir.clone() + }; + + // TODO: Link to + current = current.get_mount_to(); + current = current.walk_link()?; + + path.split('/').filter(|s| !(s.is_empty() || *s == ".")).try_for_each(|part| { + let next = current.lookup(part)?; + current = next.get_mount_to().walk_link()?; + + Ok(()) + })?; + + Ok(current) + } + + pub fn lookup_parent_dentry<'a>(&self, dir: &Arc, path: &'a str) -> SysResult<(Arc, &'a str)> { + let mut current = match path.chars().next() { + Some('/') => self.get_root().clone(), + _ => dir.clone(), + }; + current = current.get_mount_to().walk_link()?; + + let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); + + // if parts.is_empty() { + // return current.get_parent() + // .ok_or(Errno::ENOENT) + // .map(|p| (p, String::from("/"))); + // } + if parts.is_empty() { + return current.get_parent() + .ok_or(Errno::ENOENT) + .map(|p| (p, "/")); + } + + for part in &parts[0..parts.len()-1] { + let next = current.lookup(part)?; + current = next.get_mount_to().walk_link()?; + } + + Ok((current, parts[parts.len()-1])) + } + + pub fn load_inode(&self, sno: u32, ino: u32) -> SysResult> { + let index = inode::Index { sno, ino }; + if let Some(inode) = self.cache.find(&index) { + Ok(inode) + } else { + let superblock_table = self.superblock_table.lock(); + let superblock = superblock_table.get(sno).ok_or(Errno::ENOENT)?; + + let inode: Arc = Arc::from(superblock.get_inode(ino)?); + self.cache.insert(&index, inode.clone())?; + + Ok(inode) + } + } +} + +unsafe impl Sync for VirtualFileSystem {} + \ No newline at end of file diff --git a/src/kernel/config.rs b/src/kernel/config.rs new file mode 100644 index 0000000..25b01a5 --- /dev/null +++ b/src/kernel/config.rs @@ -0,0 +1,31 @@ +pub const USER_STACK_TOP: usize = 1 << 38; // Example user stack top address +pub const USER_STACK_PAGE_COUNT_MAX: usize = 2048; // Example user stack page count + +pub const USER_BRK_BASE: usize = 0x1_0000_0000; // Base address for user brk + +pub const USER_MAP_BASE: usize = 0x2_0000_0000; // Base address for user mappings + +pub const USER_EXEC_ADDR_BASE: usize = 0x1_0000; +pub const USER_LINKER_ADDR_BASE: usize = 0x4000_0000; // Base address for the dynamic linker +pub const USER_RANDOM_ADDR_BASE: usize = 0x1000; + +pub const VDSO_BASE: usize = 0x20_0000_0000; // Base address for vDSO mapping + +pub const UTASK_KSTACK_PAGE_COUNT: usize = 16; // Kernel stack page count for user tasks +pub const KTASK_KSTACK_PAGE_COUNT: usize = 16; // Kernel stack page count for kernel tasks +pub const KERNEL_HEAP_SIZE: usize = 0x4000000; +pub const KERNEL_PAGE_SHRINK_WATERLEVEL_LOW : usize = 70; // Threshold for kernel page shrinker, /1024 +pub const KERNEL_PAGE_SHRINK_WATERLEVEL_HIGH: usize = 85; // Threshold for kernel page shrinker, /1024 + +pub const INODE_CACHE_SIZE: usize = 32768; // Inode cache size + +pub const MAX_FD: usize = 1024; // Maximum number of file descriptors per process + +pub const PIPE_CAPACITY: usize = 4096; // Capacity of the pipe buffer + +/* ------ BOOT ARGS ------- */ +pub const DEFAULT_BOOT_ROOT_FSTYPE: &str = "ext4"; +pub const DEFAULT_BOOT_ROOT: &str = "virtio_block0"; +pub const DEFAULT_INITPATH: &str = "/init"; +pub const DEFAULT_INITCWD: &str = "/"; +/* ------ BOOT ARGS ------- */ diff --git a/src/kernel/errno.rs b/src/kernel/errno.rs new file mode 100644 index 0000000..8524349 --- /dev/null +++ b/src/kernel/errno.rs @@ -0,0 +1,33 @@ +use num_enum::TryFromPrimitive; + +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +pub enum Errno { + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // Input/output error + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file descriptor + ECHILD = 10, // No child processes + EAGAIN = 11, // Try again + ENOMEM = 12, // Out of memory + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + EMFILE = 24, // Too many open files + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EPIPE = 32, // Broken pipe + ENOSYS = 38, // Function not implemented + EOPNOTSUPP = 95, // Operation not supported on transport endpoint + ETIMEDOUT = 110, // Connection timed out +} + +pub type SysResult = Result; diff --git a/src/kernel/event/event.rs b/src/kernel/event/event.rs new file mode 100644 index 0000000..d1deea8 --- /dev/null +++ b/src/kernel/event/event.rs @@ -0,0 +1,17 @@ +use crate::kernel::ipc::SignalNum; +use crate::kernel::scheduler::Tid; + +use super::PollEvent; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Event { + Poll { event: PollEvent, waker: usize }, + PipeReadReady, + PipeWriteReady, + Timeout, + Futex, + Process { child: Tid }, + WaitSignal { signum: SignalNum }, + Signal, + VFork, +} diff --git a/src/kernel/event/mod.rs b/src/kernel/event/mod.rs new file mode 100644 index 0000000..7f35810 --- /dev/null +++ b/src/kernel/event/mod.rs @@ -0,0 +1,8 @@ +mod waitqueue; +mod poll; +mod event; +pub mod timer; + +pub use waitqueue::WaitQueue; +pub use poll::*; +pub use event::*; diff --git a/src/kernel/event/poll.rs b/src/kernel/event/poll.rs new file mode 100644 index 0000000..3c21dcd --- /dev/null +++ b/src/kernel/event/poll.rs @@ -0,0 +1,20 @@ +use bitflags::bitflags; + +bitflags! { + pub struct PollEventSet: i16 { + const POLLIN = 0x0001; // There is data to read. + const POLLPRI = 0x0002; // There is urgent data to read. + const POLLOUT = 0x0004; // Writing now will not block. + const POLLERR = 0x0008; // Error condition. + const POLLHUP = 0x0010; // Hung up. + const POLLNVAL = 0x0020; // Invalid request: fd not open. + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PollEvent { + ReadReady, + WriteReady, + Priority, + HangUp, +} diff --git a/src/kernel/event/timer/event.rs b/src/kernel/event/timer/event.rs new file mode 100644 index 0000000..c2c96f7 --- /dev/null +++ b/src/kernel/event/timer/event.rs @@ -0,0 +1,31 @@ +// use alloc::sync::Arc; +use alloc::boxed::Box; + +// use crate::kernel::scheduler::Task; + +pub struct TimerEvent { + pub time: u64, + // pub task: Arc, + pub expired_func: Box, +} + +impl PartialEq for TimerEvent { + fn eq(&self, other: &Self) -> bool { + // self.time == other.time && Arc::ptr_eq(&self.task, &other.task) + self.time == other.time + } +} + +impl Eq for TimerEvent {} + +impl Ord for TimerEvent { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.time.cmp(&other.time) + } +} + +impl PartialOrd for TimerEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/src/kernel/event/timer/mod.rs b/src/kernel/event/timer/mod.rs new file mode 100644 index 0000000..1d7780c --- /dev/null +++ b/src/kernel/event/timer/mod.rs @@ -0,0 +1,4 @@ +mod event; +mod timer; + +pub use timer::*; diff --git a/src/kernel/event/timer/timer.rs b/src/kernel/event/timer/timer.rs new file mode 100644 index 0000000..dd8a4e5 --- /dev/null +++ b/src/kernel/event/timer/timer.rs @@ -0,0 +1,102 @@ +use alloc::boxed::Box; +use alloc::collections::BinaryHeap; +use alloc::sync::Arc; +use core::cmp::Reverse; +use core::time::Duration; +use spin::Mutex; + +use crate::kernel::event::Event; +use crate::kernel::scheduler::{self, Task}; +use crate::arch; + +use super::event::TimerEvent; + +struct Timer { + wait_queue: Mutex>>, +} + +impl Timer { + const fn new() -> Self { + Self { + wait_queue: Mutex::new(BinaryHeap::new()), + } + } + + pub fn add_timer(&self, time: Duration, expired_func: Box) { + let time = arch::get_time_us() + time.as_micros() as u64; + // self.wait_queue.lock().push(Reverse(TimerEvent { time, task: task, expired_func })); + self.wait_queue.lock().push(Reverse(TimerEvent { time, expired_func })); + } + + pub fn wakeup_expired(&self, current_time: u64) { + let mut wait_queue = self.wait_queue.lock(); + while let Some(Reverse(event)) = wait_queue.peek() { + if event.time <= current_time { + let event = wait_queue.pop().unwrap().0; + (event.expired_func)(); + // scheduler::wakeup_task(event.tcb.clone(), Event::Timeout); + // event.tcb.wakeup(Event::Timeout); + } else { + break; + } + } + } +} + +unsafe impl Sync for Timer {} + +static TIMER: Timer = Timer::new(); + +#[unsafe(link_section = ".text.init")] +pub fn init() { + arch::set_next_time_event_us(10000); // Set first timer interrupt in 10ms + arch::enable_timer_interrupt(); +} + +pub fn now() -> Duration { + Duration::from_micros(arch::get_time_us()) +} + +pub fn add_timer(task: Arc, time: Duration) { + TIMER.add_timer(time, Box::new(move || { + scheduler::wakeup_task(task, Event::Timeout); + })); +} + +pub fn add_timer_with_func(time: Duration, func: Box) { + TIMER.add_timer(time, func); +} + +pub fn interrupt() { + let current_time = arch::get_time_us(); + TIMER.wakeup_expired(current_time); + + arch::set_next_time_event_us(10000); // Set next timer interrupt in 10ms +} + +pub fn wait_until(dur: Duration, mut f: impl FnMut() -> bool) -> bool { + let start_time = arch::get_time_us(); + let us = dur.as_micros() as u64; + loop { + if f() { + return true; + } + let current_time = arch::get_time_us(); + if current_time - start_time >= us { + break; + } + } + + f() +} + +pub fn spin_delay(dur: Duration) { + let start_time = arch::get_time_us(); + let us = dur.as_micros() as u64; + loop { + let current_time = arch::get_time_us(); + if current_time - start_time >= us { + break; + } + } +} diff --git a/src/kernel/event/waitqueue.rs b/src/kernel/event/waitqueue.rs new file mode 100644 index 0000000..c419132 --- /dev/null +++ b/src/kernel/event/waitqueue.rs @@ -0,0 +1,76 @@ +use alloc::sync::Arc; +use alloc::collections::VecDeque; + +use crate::kernel::scheduler; +use crate::kernel::scheduler::current; +use crate::kernel::scheduler::Task; + +use super::Event; + +struct WaitQueueItem { + task: Arc, + arg: T, +} + +pub struct WaitQueue { + waiters: VecDeque>, +} + +impl WaitQueueItem { + fn new(task: Arc, arg: T) -> Self { + Self { task, arg } + } + + fn wakeup(self, e: Event) { + // self.task.wakeup(e); + scheduler::wakeup_task(self.task, e); + } +} + +impl WaitQueue { + pub fn new() -> Self { + Self { + waiters: VecDeque::new(), + } + } + + pub fn wait(&mut self, task: Arc, arg: T) { + self.waiters.push_back(WaitQueueItem::new(task, arg)); + } + + pub fn wait_current(&mut self, arg: T) { + let current = current::task(); + current.block("waitqueue"); + self.wait(current.clone(), arg); + } + + // pub fn wake_one(&mut self, e: Event) -> Option> { + // pub fn wake_one(&mut self, e: Event) { + // let r = self.waiters.pop_front(); + // match r { + // Some(item) => { + // item.wakeup(e); + // // Some(item.task) + // } + // // None => None, + // None => {} + // } + // } + + // pub fn wake_all(&mut self, map_arg_to_event: impl Fn(T) -> Event) -> Vec> { + pub fn wake_all(&mut self, map_arg_to_event: impl Fn(T) -> Event) { + // self.waiters.iter().for_each(|i| i.wakeup(map_arg_to_event(i.arg))); + self.waiters.drain(..).for_each(|item| { + let arg = item.arg; + item.wakeup(map_arg_to_event(arg)); + // item.task + }); + // }).collect() + } + + pub fn remove(&mut self, task: &Arc) { + if let Some(pos) = self.waiters.iter().position(|item| Arc::ptr_eq(&item.task, task)) { + self.waiters.remove(pos); + } + } +} diff --git a/src/kernel/ipc/mod.rs b/src/kernel/ipc/mod.rs new file mode 100644 index 0000000..01b6b14 --- /dev/null +++ b/src/kernel/ipc/mod.rs @@ -0,0 +1,6 @@ +pub mod pipe; +pub mod signal; +pub mod shm; + +pub use pipe::Pipe; +pub use signal::*; diff --git a/src/kernel/ipc/pipe/inner.rs b/src/kernel/ipc/pipe/inner.rs new file mode 100644 index 0000000..35bdd6f --- /dev/null +++ b/src/kernel/ipc/pipe/inner.rs @@ -0,0 +1,160 @@ +use alloc::collections::VecDeque; +use spin::Mutex; + +use crate::kernel::errno::SysResult; +use crate::kernel::event::{Event, PollEvent, PollEventSet, WaitQueue}; +use crate::kernel::scheduler::current; + +pub struct PipeInner { + buffer: Mutex>, + read_waiter: Mutex>, + write_waiter: Mutex>, + capacity: Mutex, + // reader_count: Mutex, + writer_count: Mutex, +} + +impl PipeInner { + pub fn new(capacity: usize) -> Self { + Self { + buffer: Mutex::new(VecDeque::with_capacity(capacity)), + read_waiter: Mutex::new(WaitQueue::new()), + write_waiter: Mutex::new(WaitQueue::new()), + capacity: Mutex::new(capacity), + // reader_count: Mutex::new(0), + writer_count: Mutex::new(0), + } + } + + pub fn read(&self, buf: &mut [u8]) -> SysResult { + if buf.len() == 0 { + return Ok(0); + } + + let mut total_read = 0; + + { + let mut fifo = self.buffer.lock(); + match fifo.pop_front() { + Some(byte) => { + buf[0] = byte; + total_read += 1; + }, + None => { + if *self.writer_count.lock() == 0 { + return Ok(0); // No writers left, return 0 to indicate EOF + } else { + drop(fifo); + self.read_waiter.lock().wait_current(Event::PipeReadReady); + current::schedule(); + } + } + } + } + + while total_read < buf.len() { + let mut fifo = self.buffer.lock(); + if fifo.is_empty() { + break; + } + + let to_read = core::cmp::min(buf.len() - total_read, fifo.len()); + for _ in 0..to_read { + if let Some(byte) = fifo.pop_front() { + buf[total_read] = byte; + total_read += 1; + } + } + + self.write_waiter.lock().wake_all(|e| e); // Wake up writers waiting for space + } + + Ok(total_read) + } + + pub fn write(&self, buf: &[u8]) -> SysResult { + let mut total_written = 0; + // kinfo!("Pipe write: buffer size {}, capacity {}", self.buffer.len(), self.capacity); + while total_written < buf.len() { + let mut fifo = self.buffer.lock(); + let capacity = self.capacity.lock(); + + if fifo.len() == *capacity { + drop(capacity); + drop(fifo); + + // If buffer is full, wait for space + self.write_waiter.lock().wait_current(Event::PipeWriteReady); + current::schedule(); + } else { + let to_write = core::cmp::min(buf.len() - total_written, *capacity - fifo.len()); + for i in 0..to_write { + fifo.push_back(buf[total_written + i]); + } + total_written += to_write; + self.read_waiter.lock().wake_all(|e| e); // Wake up readers waiting for data + } + } + + Ok(total_written) + } + + pub fn poll(&self, waker: usize, event: PollEventSet, writable: bool) -> SysResult> { + if event.contains(PollEventSet::POLLIN) && writable { + return Ok(None); + } + + if event.contains(PollEventSet::POLLOUT) && !writable { + return Ok(None); + } + + let buffer = self.buffer.lock(); + if event.contains(PollEventSet::POLLIN) { + if buffer.len() > 0 && !writable { + return Ok(Some(PollEvent::ReadReady)); + } else { + if *self.writer_count.lock() == 0 { + return Ok(Some(PollEvent::HangUp)); // No writers left, indicate EOF + } + self.read_waiter.lock().wait(current::task().clone(), Event::Poll{event: PollEvent::ReadReady, waker}); + } + } + + if event.contains(PollEventSet::POLLOUT) { + if buffer.len() < *self.capacity.lock() { + return Ok(Some(PollEvent::WriteReady)); + } else { + self.write_waiter.lock().wait(current::task().clone(), Event::Poll{event: PollEvent::WriteReady, waker}); + } + } + + Ok(None) + } + + pub fn poll_cancel(&self) { + self.read_waiter.lock().remove(current::task()); + self.write_waiter.lock().remove(current::task()); + } + + // pub fn increment_reader_count(&self) { + // *self.reader_count.lock() += 1; + // } + + pub fn increment_writer_count(&self) { + *self.writer_count.lock() += 1; + } + + pub fn decrement_writer_count(&self) { + let mut writer_count = self.writer_count.lock(); + assert!(*writer_count > 0); + *writer_count -= 1; + if *writer_count == 0 { + self.read_waiter.lock().wake_all(|e| { + match e { + Event::Poll{ event: PollEvent::ReadReady, waker } => {Event::Poll{event: PollEvent::HangUp, waker} }, + _ => e + } + }); // Wake up readers to notify them of EOF + } + } +} \ No newline at end of file diff --git a/src/kernel/ipc/pipe/manager.rs b/src/kernel/ipc/pipe/manager.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/kernel/ipc/pipe/mod.rs b/src/kernel/ipc/pipe/mod.rs new file mode 100644 index 0000000..2a1aa74 --- /dev/null +++ b/src/kernel/ipc/pipe/mod.rs @@ -0,0 +1,6 @@ +mod inner; +mod pipe; +mod manager; + +use inner::*; +pub use pipe::*; diff --git a/src/kernel/ipc/pipe/pipe.rs b/src/kernel/ipc/pipe/pipe.rs new file mode 100644 index 0000000..9a870b9 --- /dev/null +++ b/src/kernel/ipc/pipe/pipe.rs @@ -0,0 +1,117 @@ +use alloc::sync::Arc; + +use crate::kernel::event::{PollEvent, PollEventSet}; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::uapi::FileStat; +use crate::fs::{Dentry, InodeOps}; +use crate::fs::file::{FileOps, SeekWhence}; + +use super::PipeInner; + +struct Meta { + // inode: Arc, + dentry: Arc, +} + +pub struct Pipe { + inner: Arc, + meta: Option, + writable: bool, + _blocked: bool, +} + +impl Pipe { + pub fn new(inner: Arc, writable: bool) -> Self { + if writable { + inner.increment_writer_count(); + } + Self { + inner, + meta: None, + writable, + _blocked: true, + } + } + + pub fn create(capacity: usize) -> (Self, Self) { + let inner = Arc::new(PipeInner::new(capacity)); + let read_end = Pipe::new(inner.clone(), false); + let write_end = Pipe::new(inner, true); + (read_end, write_end) + } +} + +impl FileOps for Pipe { + fn read(&self, buf: &mut [u8]) -> SysResult { + self.inner.read(buf) + } + + fn pread(&self, _: &mut [u8], _: usize) -> SysResult { + Err(Errno::ESPIPE) + } + + fn write(&self, buf: &[u8]) -> SysResult { + self.inner.write(buf) + } + + fn pwrite(&self, _: &[u8], _: usize) -> SysResult { + Err(Errno::EPIPE) + } + + fn readable(&self) -> bool { + !self.writable + } + + fn writable(&self) -> bool { + self.writable + } + + fn seek(&self, _: isize, _: SeekWhence) -> SysResult { + Err(Errno::ESPIPE) + } + + fn fstat(&self) -> SysResult { + let mut kstat = FileStat::empty(); + kstat.st_mode = 0o100666; // Regular file with rw-rw-rw- permissions + kstat.st_nlink = 1; + + Ok(kstat) + } + + fn fsync(&self) -> SysResult<()> { + Ok(()) + } + + fn ioctl(&self, _request: usize, _arg: usize) -> SysResult { + Err(Errno::ENOSYS) // Placeholder for unimplemented ioctl commands + } + + fn get_inode(&self) -> Option<&Arc> { + // self.meta.as_ref().map(|m| &m.inode) + unimplemented!() + } + + fn get_dentry(&self) -> Option<&Arc> { + self.meta.as_ref().map(|m| &m.dentry) + } + + fn poll(&self, waker: usize, event: PollEventSet) -> SysResult> { + self.inner.poll(waker, event, self.writable) + } + + fn poll_cancel(&self) { + self.inner.poll_cancel(); + } + + fn type_name(&self) -> &'static str { + "pipe" + } +} + +impl Drop for Pipe { + fn drop(&mut self) { + if self.writable { + self.inner.decrement_writer_count(); + } + } +} diff --git a/src/kernel/ipc/shm/frame.rs b/src/kernel/ipc/shm/frame.rs new file mode 100644 index 0000000..7eb793e --- /dev/null +++ b/src/kernel/ipc/shm/frame.rs @@ -0,0 +1,24 @@ +use alloc::vec::Vec; + +use crate::kernel::mm::PhysPageFrame; +use crate::klib::SpinLock; + +pub struct ShmFrames { + pub frames: SpinLock> +} + +impl ShmFrames { + pub fn new(page_count: usize) -> Self { + let mut frames = Vec::new(); + for _ in 0..page_count { + frames.push(PhysPageFrame::alloc()); + } + ShmFrames { + frames: SpinLock::new(frames) + } + } + + pub fn page_count(&self) -> usize { + self.frames.lock().len() + } +} diff --git a/src/kernel/ipc/shm/manager.rs b/src/kernel/ipc/shm/manager.rs new file mode 100644 index 0000000..a8f78e1 --- /dev/null +++ b/src/kernel/ipc/shm/manager.rs @@ -0,0 +1,216 @@ +use alloc::collections::BTreeMap; +use alloc::boxed::Box; +use alloc::sync::Arc; +use bitflags::bitflags; + +use crate::kernel::mm::{MapPerm, AddrSpace}; +use crate::kernel::mm::maparea::ShmArea; +use crate::arch::PGSIZE; +use crate::kernel::errno::{Errno, SysResult}; +use crate::klib::SpinLock; + +use super::frame::ShmFrames; + +pub const IPC_PRIVATE: usize = 0; + +bitflags! { + pub struct IpcGetFlag: usize { + const IPC_CREAT = 0o1000; + const IPC_EXCL = 0o2000; + const IPC_NOWAIT = 0o4000; + } +} + +pub const IPC_RMID: usize = 0; +pub const IPC_SET: usize = 1; +pub const IPC_STAT: usize = 2; +pub const IPC_INFO: usize = 3; + +bitflags! { + pub struct ShmFlag: usize { + const SHM_RDONLY = 0o10000; + const SHM_RND = 0o20000; + const SHM_REMAP = 0o40000; + const SHM_EXEC = 0o100000; + } +} + +#[derive(Clone, Copy, Debug)] +pub struct ShmidDs { + pub key: usize, + pub size: usize, + pub mode: u32, + pub ctime: usize, // Creation time (placeholder) + pub atime: usize, // Last attach time + pub dtime: usize, // Last detach time +} + +pub struct ShmIdentifier { + pub ds: ShmidDs, + pub frames: Arc, + pub ref_count: usize, + pub deleted: bool, +} + +pub struct ShmManager { + shms: BTreeMap, + next_shmid: usize, +} + +impl ShmManager { + const fn new() -> Self { + Self { + shms: BTreeMap::new(), + next_shmid: 1, + } + } + + fn get_or_create(&mut self, key: usize, size: usize, flags: IpcGetFlag) -> Result { + if key != IPC_PRIVATE { + // Try to find existing + let mut found_id = None; + for (id, shm) in &self.shms { + if !shm.deleted && shm.ds.key == key { + found_id = Some(*id); + break; + } + } + + if let Some(id) = found_id { + if flags.contains(IpcGetFlag::IPC_CREAT | IpcGetFlag::IPC_EXCL) { + return Err(Errno::EEXIST); + } + let shm = self.shms.get(&id).unwrap(); + if size > shm.ds.size { + return Err(Errno::EINVAL); + } + return Ok(id); + } + } + + if key != IPC_PRIVATE && !flags.contains(IpcGetFlag::IPC_CREAT) { + return Err(Errno::ENOENT); + } + + // Create new + if size == 0 { + return Err(Errno::EINVAL); + } + + let page_count = (size + PGSIZE - 1) / PGSIZE; + let frames = Arc::new(ShmFrames::new(page_count)); + let id = self.next_shmid; + self.next_shmid += 1; + + let shm = ShmIdentifier { + ds: ShmidDs { + key, + size, + mode: (flags.bits() & 0o777) as u32, + ctime: 0, // TODO: get time + atime: 0, + dtime: 0, + }, + frames, + ref_count: 0, + deleted: false, + }; + + self.shms.insert(id, shm); + Ok(id) + } + + fn get(&mut self, shmid: usize) -> Option<&mut ShmIdentifier> { + self.shms.get_mut(&shmid) + } + + // Called on shmat + pub fn attach(&mut self, shmid: usize, addrspace: &AddrSpace, shmaddr: usize, shmflg: ShmFlag) -> SysResult { + let shm = self.shms.get_mut(&shmid).ok_or(Errno::EINVAL)?; + let page_count = shm.frames.page_count(); + + // Permissions + let mut perm = MapPerm::R | MapPerm::U; + if !shmflg.contains(ShmFlag::SHM_RDONLY) { + perm |= MapPerm::W; + } + if shmflg.contains(ShmFlag::SHM_EXEC) { + perm |= MapPerm::X; + } + + addrspace.with_map_manager_mut(|map_manager| { + // Determine address + let uaddr = if shmaddr == 0 { + map_manager.find_mmap_ubase(page_count).ok_or(Errno::ENOMEM)? + } else { + let aligned_addr = (shmaddr + PGSIZE - 1) & !(PGSIZE - 1); + if map_manager.is_map_range_overlapped(aligned_addr, page_count * PGSIZE) { + return Err(Errno::EINVAL); + } + aligned_addr + }; + + let shm_area = Box::new(ShmArea::new(uaddr, shm.frames.clone(), perm)); + shm.ref_count += 1; + shm.ds.atime = 0; // TODO: update time + + map_manager.map_area(uaddr, shm_area); + + + Ok(uaddr) + }) + } + + // Called on shmdt + fn detach(&mut self, shmid: usize) -> SysResult<()> { + let should_remove = if let Some(shm) = self.shms.get_mut(&shmid) { + if shm.ref_count > 0 { + shm.ref_count -= 1; + shm.ds.dtime = 0; // TODO: update time + shm.deleted && shm.ref_count == 0 + } else { + return Err(Errno::EINVAL); + } + } else { + return Err(Errno::EINVAL); + }; + + if should_remove { + self.shms.remove(&shmid); + } + Ok(()) + } + + // Called on shmctl(IPC_RMID) + fn mark_remove(&mut self, shmid: usize) -> SysResult<()> { + let should_remove = if let Some(shm) = self.shms.get_mut(&shmid) { + shm.deleted = true; + shm.ref_count == 0 + } else { + return Err(Errno::EINVAL); + }; + + if should_remove { + self.shms.remove(&shmid); + } + Ok(()) + } +} + +static SHM_MANAGER: SpinLock = SpinLock::new(ShmManager::new()); + +pub fn get_or_create_shm(key: usize, size: usize, flags: IpcGetFlag) -> SysResult { + SHM_MANAGER.lock().get_or_create(key, size, flags) +} + +pub fn attach_shm(shmid: usize, addr_space: &AddrSpace, shmaddr: usize, shmflg: ShmFlag) -> SysResult { + SHM_MANAGER.lock().attach(shmid, addr_space, shmaddr, shmflg) +} + +pub fn detach_shm(shmid: usize) -> SysResult<()> { + SHM_MANAGER.lock().detach(shmid) +} + +pub fn mark_remove_shm(shmid: usize) -> SysResult<()> { + SHM_MANAGER.lock().mark_remove(shmid) +} diff --git a/src/kernel/ipc/shm/mod.rs b/src/kernel/ipc/shm/mod.rs new file mode 100644 index 0000000..5eb4508 --- /dev/null +++ b/src/kernel/ipc/shm/mod.rs @@ -0,0 +1,5 @@ +mod frame; +mod manager; + +pub use manager::*; +pub use frame::ShmFrames; diff --git a/src/kernel/ipc/signal/action.rs b/src/kernel/ipc/signal/action.rs new file mode 100644 index 0000000..74c1caf --- /dev/null +++ b/src/kernel/ipc/signal/action.rs @@ -0,0 +1,71 @@ +use bitflags::bitflags; + +use crate::kernel::errno::SysResult; +use crate::kernel::ipc::SignalNum; + +use super::SignalSet; + +const SIG_DFL: usize = 0; +const SIG_IGN: usize = 1; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct SignalActionFlags: u32 { + const SA_NOCLDSTOP = 0x1; + const SA_NOCLDWAIT = 0x2; + const SA_SIGINFO = 0x4; + const SA_RESTORER = 0x04000000; + const SA_ONSTACK = 0x08000000; + const SA_RESTART = 0x10000000; + const SA_NODEFER = 0x40000000; + const SA_RESETHAND = 0x80000000; + } +} + +#[derive(Clone, Copy)] +pub struct SignalAction { + pub handler: usize, + pub mask: SignalSet, + pub flags: SignalActionFlags, +} + +impl SignalAction { + pub const fn empty() -> Self { + SignalAction { + handler: SIG_DFL, + mask: SignalSet::empty(), + flags: SignalActionFlags::empty(), + } + } + + pub const fn is_default(&self) -> bool { + self.handler == SIG_DFL + } + + pub const fn is_ignore(&self) -> bool { + self.handler == SIG_IGN + } +} + +pub struct SignalActionTable { + pub actions: [SignalAction; 63], +} + +impl SignalActionTable { + pub fn new() -> Self { + SignalActionTable { + actions: [SignalAction::empty(); 63], + } + } + + pub fn get(&self, signum: SignalNum) -> SignalAction { + let index: usize = signum.into(); + self.actions[index - 1] + } + + pub fn set(&mut self, signum: SignalNum, action: &SignalAction) -> SysResult<()> { + let index: usize = signum.into(); + self.actions[index - 1] = *action; + Ok(()) + } +} diff --git a/src/kernel/ipc/signal/frame.rs b/src/kernel/ipc/signal/frame.rs new file mode 100644 index 0000000..c1d5725 --- /dev/null +++ b/src/kernel/ipc/signal/frame.rs @@ -0,0 +1,50 @@ +use crate::kernel::ipc::SignalSet; +use crate::arch::SigContext; + +use super::siginfo::SigInfo; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SignalStack { + pub ss_sp: usize, + pub ss_flags: i32, + pub ss_size: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SignalUContext { + pub _uc_flags: usize, // 8 + pub _uc_link: usize, // 16 + pub _uc_stack: SignalStack, // 16 + 24 = 40 + pub uc_sigmask: SignalSet, // 48 + pub __unused: [u8; 128 - core::mem::size_of::()], + pub uc_mcontext: SigContext, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SigFrame { + pub info: SigInfo, + pub ucontext: SignalUContext, +} + +impl SigFrame { + pub fn empty() -> Self { + SigFrame { + info: SigInfo::empty(), + ucontext: SignalUContext { + _uc_flags: 0, + _uc_link: 0, + _uc_stack: SignalStack { + ss_sp: 0, + ss_flags: 0, + ss_size: 0, + }, + uc_sigmask: SignalSet::empty(), + __unused: [0; 128 - core::mem::size_of::()], + uc_mcontext: SigContext::empty(), + }, + } + } +} diff --git a/src/kernel/ipc/signal/handle.rs b/src/kernel/ipc/signal/handle.rs new file mode 100644 index 0000000..6e47007 --- /dev/null +++ b/src/kernel/ipc/signal/handle.rs @@ -0,0 +1,184 @@ +use alloc::sync::Arc; + +use crate::arch::UserContextTrait; +use crate::kernel::{config, scheduler}; +use crate::kernel::event::Event; +use crate::kernel::ipc::signal::frame::SigFrame; +use crate::kernel::ipc::{KSiFields, SiCode, SignalSet}; +use crate::kernel::mm::vdso; +use crate::kernel::task::{PCB, TCB}; +use crate::kernel::scheduler::{Tid, current}; +use crate::kernel::errno::{SysResult, Errno}; + +use super::{SignalNum, PendingSignal, SignalDefaultAction, SignalActionFlags}; + +impl TCB { + pub fn handle_signal(&self) { + let mut state = self.state().lock(); + let signal = match state.pending_signal.take() { + Some(sig) => sig, + None => return, + }; + drop(state); + + let signum = signal.signum; + if signum.is_empty() { + return; + } + + if signum.is_kill() { + self.parent.exit(128 + signum.num() as u8); + current::schedule(); + + unreachable!(); + } + + let action = self.parent.signal_actions().lock().get(signal.signum); + if action.is_default() { + match signum.default_action() { + SignalDefaultAction::Term | SignalDefaultAction::Stop | SignalDefaultAction::Core => { + self.parent.exit(128 + signum.num() as u8); + current::schedule(); + + unreachable!(); + }, + _ => return + } + } else if action.is_ignore() { + return; + } + + let old_mask = self.get_signal_mask(); + let mut new_mask = old_mask | action.mask; + if !action.flags.contains(SignalActionFlags::SA_NODEFER) { + new_mask |= signum.to_mask_set(); + } + self.set_signal_mask(new_mask); + + let mut sigframe = SigFrame::empty(); + sigframe.info.si_signo = Into::::into(signum) as i32; + sigframe.info.si_code = signal.si_code; + sigframe.info.fields = signal.fields.into(); + sigframe.info.si_errno = 0; + sigframe.ucontext.uc_sigmask = old_mask; + sigframe.ucontext.uc_mcontext = (*self.user_context()).into(); + + let mut stack_top = self.user_context().get_user_stack_top(); + stack_top -= core::mem::size_of::(); + stack_top &= !0xf; // Align to 16 bytes + self.get_addrspace().copy_to_user(stack_top, sigframe).expect("Failed to copy sigframe to user stack"); + + self.user_context() + .set_sigaction_restorer(vdso::addr_of("sigreturn_trampoline") + config::VDSO_BASE) + .set_arg(0, signum.into()) + .set_user_entry(action.handler) + .set_user_stack_top(stack_top); + + // kinfo!("flags={:?}, signum={}, ucontext={:?}", action.flags, signum.num(), sigframe.ucontext); + + if action.flags.contains(SignalActionFlags::SA_SIGINFO) { + let siginfo_uaddr = stack_top + core::mem::offset_of!(SigFrame, info); + let ucontext_uaddr = stack_top + core::mem::offset_of!(SigFrame, ucontext); + self.user_context() + .set_arg(1, siginfo_uaddr) + .set_arg(2, ucontext_uaddr); + } + } + + pub fn return_from_signal(&self) { + let sigframe = self.get_addrspace() + .copy_from_user::(self.user_context().get_user_stack_top()) + .expect("Failed to copy sigframe from user stack"); + // kinfo!("return_from_signal: sigframe.ucontext={:?}", sigframe.ucontext); + self.user_context().restore_from_signal(&sigframe.ucontext.uc_mcontext); + } + + pub fn try_recive_pending_signal(self: &Arc, pending: PendingSignal) -> bool { + let mut state = self.state().lock(); + + let signum = pending.signum; + + let waiting = state.signal_to_wait; + if waiting.contains(signum) { + state.pending_signal = Some(pending); + state.signal_to_wait = SignalSet::empty(); + drop(state); + + scheduler::wakeup_task(self.clone(), Event::WaitSignal { signum }); + + return true; + } + + if state.pending_signal.is_some() { + return false; + } + + if signum.is_unignorable() { + state.pending_signal = Some(pending); + drop(state); + + scheduler::wakeup_task(self.clone(), Event::Signal); + + return true; + } + + let mask = self.get_signal_mask(); + if !signum.is_masked(mask) { + state.pending_signal = Some(pending); + drop(state); + + // self.wakeup(Event::Signal); + scheduler::wakeup_task(self.clone(), Event::Signal); + + return true; + } else { + return false; + } + } + + pub fn recive_pending_signal_from_parent(&self) { + let mut state = self.state().lock(); + + if state.pending_signal.is_some() { + drop(state); + return; + } + + if let Some(signal) = self.parent.pending_signals().lock().pop_pending(*self.signal_mask.lock(), self.tid) { + state.pending_signal = Some(signal); + } + } +} + +impl PCB { + pub fn send_signal(&self, signum: SignalNum, si_code: SiCode, fields: KSiFields, dest: Option) -> SysResult<()> { + let pending = PendingSignal { + signum, + si_code, + fields, + dest, + }; + + if let Some(dest) = dest { + let tasks = self.tasks.lock(); + if let Some(task) = tasks.iter().find(|t| t.tid == dest).cloned() { + if !task.try_recive_pending_signal(pending) { + self.pending_signals().lock().add_pending(pending)?; + } + return Ok(()); + } else { + return Err(Errno::ESRCH); + } + } + + for task in self.tasks.lock().iter() { + if task.try_recive_pending_signal(pending) { + return Ok(()) + } + } + + self.pending_signals().lock().add_pending(pending)?; + + Ok(()) + } +} diff --git a/src/kernel/ipc/signal/mod.rs b/src/kernel/ipc/signal/mod.rs new file mode 100644 index 0000000..6bceb6d --- /dev/null +++ b/src/kernel/ipc/signal/mod.rs @@ -0,0 +1,11 @@ +mod action; +mod pending; +mod signalnum; +mod handle; +mod frame; +mod siginfo; + +pub use action::*; +pub use signalnum::*; +pub use pending::*; +pub use siginfo::*; diff --git a/src/kernel/ipc/signal/pending.rs b/src/kernel/ipc/signal/pending.rs new file mode 100644 index 0000000..40443cf --- /dev/null +++ b/src/kernel/ipc/signal/pending.rs @@ -0,0 +1,45 @@ +use alloc::vec::Vec; + +use crate::kernel::ipc::{KSiFields, SiCode, SignalNum, SignalSet}; +use crate::kernel::scheduler::Tid; +use crate::kernel::errno::SysResult; + +#[derive(Clone, Copy, Debug)] +pub struct PendingSignal { + pub signum: SignalNum, + pub si_code: SiCode, + pub fields: KSiFields, + pub dest: Option, +} + +pub struct PendingSignalQueue { + pending: Vec +} + +impl PendingSignalQueue { + pub fn new() -> Self { + PendingSignalQueue { + pending: Vec::new(), + } + } + + pub fn add_pending(&mut self, pending: PendingSignal) -> SysResult<()> { + self.pending.push(pending); + Ok(()) + } + + pub fn pop_pending(&mut self, mask: SignalSet, tid: Tid) -> Option { + let mut index = None; + for (i, signal) in self.pending.iter().enumerate() { + if !signal.signum.is_masked(mask) || signal.signum.is_unignorable() && (signal.dest == Some(tid) || signal.dest.is_none()) { + index = Some(i); + break; + } + } + if let Some(i) = index { + Some(self.pending.remove(i)) + } else { + None + } + } +} diff --git a/src/kernel/ipc/signal/siginfo.rs b/src/kernel/ipc/signal/siginfo.rs new file mode 100644 index 0000000..57a96b2 --- /dev/null +++ b/src/kernel/ipc/signal/siginfo.rs @@ -0,0 +1,108 @@ +use crate::kernel::task::Pid; +use crate::kernel::uapi::uid_t; + +const SI_PAD_SIZE: usize = 128 - 3 * core::mem::size_of::(); + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SiKill { + pub si_pid: Pid, // Sending process ID + pub si_uid: uid_t, // Real user ID of sending process +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SiTimer { + pub si_tid: i32, // Timer ID + pub si_overrun: i32, // Overrun count + pub si_sigval: usize, // Signal value +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SiSigChld { + pub si_pid: Pid, // Child process ID + pub si_uid: uid_t, // Real user ID of sending process + pub si_status: i32, // Exit value or signal + pub si_utime: usize, // User time consumed + pub si_stime: usize, // System time consumed +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SiSigFault { + pub si_addr: usize, // Faulting instruction/memory reference + pub si_addr_lsb: i16, // Valid LSBs of si_addr +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SiCode(pub i32); + +impl SiCode { + pub const EMPTY: Self = Self(0); + pub const SI_USER: Self = Self(0); + pub const SI_KERNEL:Self = Self(0x80); + pub const SI_QUEUE: Self = Self(-1); + pub const SI_TIMER: Self = Self(-2); + pub const SI_TKILL: Self = Self(-6); +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub union USiFields { + _pad: [i32; SI_PAD_SIZE / core::mem::size_of::()], + kill: SiKill, + timer: SiTimer, + sigchld: SiSigChld, + sigfault: SiSigFault, +} + +#[derive(Clone, Copy, Debug)] +pub enum KSiFields { + Empty, + Kill(SiKill), + Timer(SiTimer), + SigChld(SiSigChld), + SigFault(SiSigFault), +} + +impl KSiFields { + pub fn kill(pid: Pid, uid: uid_t) -> Self { + Self::Kill(SiKill { si_pid: pid, si_uid: uid }) + } +} + +impl Into for KSiFields { + fn into(self) -> USiFields { + match self { + KSiFields::Empty => USiFields { _pad: [0; SI_PAD_SIZE / core::mem::size_of::()] }, + KSiFields::Kill(kill) => USiFields { kill }, + KSiFields::SigChld(sigchld) => USiFields { sigchld }, + KSiFields::SigFault(sigfault) => USiFields { sigfault }, + KSiFields::Timer(timer) => USiFields { timer } + } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SigInfo { + pub si_signo: i32, // Signal number + pub si_errno: i32, // An errno value + pub si_code: SiCode, // Signal code + __pad0: i32, + pub fields: USiFields, +} + +impl SigInfo { + pub fn empty() -> Self { + SigInfo { + si_signo: 0, + si_errno: 0, + si_code: SiCode::EMPTY, + __pad0: 0, + fields: USiFields { _pad: [0; SI_PAD_SIZE / core::mem::size_of::()] }, + } + } +} diff --git a/src/kernel/ipc/signal/signalnum.rs b/src/kernel/ipc/signal/signalnum.rs new file mode 100644 index 0000000..579b340 --- /dev/null +++ b/src/kernel/ipc/signal/signalnum.rs @@ -0,0 +1,178 @@ +use core::ops::{BitAnd, BitOr, BitOrAssign, Not}; + +use crate::kernel::{errno::{Errno, SysResult}, syscall::UserStruct}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SignalDefaultAction { + Core, // Create core dump and terminate + Term, // Terminate + Ign, // Ignore + Cont, // Continue if stopped + Stop, // Stop the process +} + +pub mod signum { + use super::SignalNum; + + pub const SIGHUP: SignalNum = SignalNum(1); + pub const SIGINT: SignalNum = SignalNum(2); + pub const SIGQUIT: SignalNum = SignalNum(3); + pub const SIGILL: SignalNum = SignalNum(4); + pub const SIGTRAP: SignalNum = SignalNum(5); + pub const SIGABRT: SignalNum = SignalNum(6); + pub const SIGBUS: SignalNum = SignalNum(7); + pub const SIGFPE: SignalNum = SignalNum(8); + pub const SIGKILL: SignalNum = SignalNum(9); + pub const SIGUSR1: SignalNum = SignalNum(10); + pub const SIGSEGV: SignalNum = SignalNum(11); + pub const SIGUSR2: SignalNum = SignalNum(12); + pub const SIGPIPE: SignalNum = SignalNum(13); + pub const SIGALRM: SignalNum = SignalNum(14); + pub const SIGTERM: SignalNum = SignalNum(15); + pub const SIGSTKFLT: SignalNum = SignalNum(16); + pub const SIGCHLD: SignalNum = SignalNum(17); + pub const SIGCONT: SignalNum = SignalNum(18); + pub const SIGSTOP: SignalNum = SignalNum(19); + pub const SIGTSTP: SignalNum = SignalNum(20); + pub const SIGTTIN: SignalNum = SignalNum(21); + pub const SIGTTOU: SignalNum = SignalNum(22); + pub const SIGURG: SignalNum = SignalNum(23); + pub const SIGXCPU: SignalNum = SignalNum(24); + pub const SIGXFSZ: SignalNum = SignalNum(25); + pub const SIGVTALRM: SignalNum = SignalNum(26); + pub const SIGPROF: SignalNum = SignalNum(27); + pub const SIGWINCH: SignalNum = SignalNum(28); + pub const SIGIO: SignalNum = SignalNum(29); + pub const SIGPWR: SignalNum = SignalNum(30); + pub const SIGSYS: SignalNum = SignalNum(31); +} + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SignalNum(u32); + +use signum::*; + +impl SignalNum { + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + + pub fn is_kill(&self) -> bool { + *self == SIGKILL || *self == SIGSTOP + } + + pub fn is_unignorable(&self) -> bool { + *self == SIGKILL || *self == SIGSTOP + } + + pub fn to_mask(&self) -> usize { + 1 << (self.0 - 1) + } + + pub fn to_mask_set(&self) -> SignalSet { + SignalSet(self.to_mask()) + } + + pub fn is_masked(&self, set: SignalSet) -> bool { + (set.0 & self.to_mask()) != 0 + } + + pub fn default_action(&self) -> SignalDefaultAction { + match *self { + SIGQUIT | SIGILL | SIGABRT | SIGFPE | SIGSEGV | + SIGBUS | SIGSYS | SIGTRAP | SIGXCPU | SIGXFSZ => SignalDefaultAction::Core, + + SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => SignalDefaultAction::Stop, + + SIGCONT => SignalDefaultAction::Cont, + + SIGCHLD | SIGURG | SIGWINCH => SignalDefaultAction::Ign, + + _ => SignalDefaultAction::Term, + } + } + + pub fn num(&self) -> u32 { + self.0 + } +} + +impl Into for SignalNum { + fn into(self) -> u32 { + self.0 + } +} + +impl TryFrom for SignalNum { + type Error = Errno; + fn try_from(value: u32) -> SysResult { + if value > 63 { + Err(Errno::EINVAL) + } else { + Ok(SignalNum(value)) + } + } +} + +impl Into for SignalNum { + fn into(self) -> usize { + self.0 as usize + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct SignalSet(usize); + +impl SignalSet { + pub const fn empty() -> Self { + SignalSet(0) + } + + pub fn contains(&self, num: SignalNum) -> bool { + num.is_masked(*self) + } + + pub fn bits(&self) -> usize { + self.0 + } +} + +impl BitOr for SignalSet { + type Output = SignalSet; + + fn bitor(self, rhs: SignalSet) -> SignalSet { + SignalSet(self.0 | rhs.0) + } +} + +impl BitOrAssign for SignalSet { + fn bitor_assign(&mut self, rhs: SignalSet) { + self.0 |= rhs.0; + } +} + +impl Not for SignalSet { + type Output = SignalSet; + + fn not(self) -> SignalSet { + SignalSet(!self.0) + } +} + +impl BitAnd for SignalSet { + type Output = SignalSet; + + fn bitand(self, rhs: SignalSet) -> SignalSet { + SignalSet(self.0 & rhs.0) + } +} + +impl Into for SignalSet { + fn into(self) -> usize { + self.0 + } +} + +impl UserStruct for SignalSet {} diff --git a/src/kernel/kthread/mod.rs b/src/kernel/kthread/mod.rs new file mode 100644 index 0000000..e2e4f80 --- /dev/null +++ b/src/kernel/kthread/mod.rs @@ -0,0 +1,3 @@ +pub mod task; + +pub use task::*; diff --git a/src/kernel/kthread/task.rs b/src/kernel/kthread/task.rs new file mode 100644 index 0000000..6ee0016 --- /dev/null +++ b/src/kernel/kthread/task.rs @@ -0,0 +1,121 @@ +use alloc::sync::Arc; + +use crate::kernel::event::Event; +use crate::kernel::scheduler::{Task, TaskState, Tid, KernelStack, tid, current}; +use crate::kernel::scheduler; +use crate::kernel::task::TCB; +use crate::klib::SpinLock; +use crate::arch::KernelContext; + +pub struct KThread { + tid: Tid, + kcontext: KernelContext, + kstack: KernelStack, + state: SpinLock, + wakeup_event: SpinLock>, +} + +impl KThread { + fn new(tid: Tid, entry: fn()) -> Self { + let kstack = KernelStack::new(crate::kernel::config::KTASK_KSTACK_PAGE_COUNT); + let mut kcontext = KernelContext::new(&kstack); + kcontext.set_entry(entry as usize); + Self { + tid, + kcontext, + kstack, + state: SpinLock::new(TaskState::Ready), + wakeup_event: SpinLock::new(None), + } + } +} + +impl Task for KThread { + fn tid(&self) -> Tid { + self.tid + } + + fn tcb(&self) -> &TCB { + unreachable!("KThread is not a TCB") + } + + fn kstack(&self) -> &KernelStack { + &self.kstack + } + + fn get_kcontext_ptr(&self) -> *mut crate::arch::KernelContext { + &self.kcontext as *const _ as *mut _ + } + + fn run_if_ready(&self) -> bool { + let mut state = self.state.lock(); + if *state != TaskState::Ready { + return false; + } + *state = TaskState::Running; + true + } + + fn state_running_to_ready(&self) -> bool { + let mut state = self.state.lock(); + if *state != TaskState::Running { + return false; + } + *state = TaskState::Ready; + true + } + + fn block(&self, _reason: &str) -> bool { + debug_assert!(current::tid() == self.tid); + let mut state = self.state.lock(); + match *state { + TaskState::Ready | TaskState::Running => {}, + _ => return false, + } + *state = TaskState::Blocked; + true + } + + fn block_uninterruptible(&self, _reason: &str) -> bool { + debug_assert!(current::tid() == self.tid); + let mut state = self.state.lock(); + match *state { + TaskState::Ready | TaskState::Running => {}, + _ => return false, + } + *state = TaskState::BlockedUninterruptible; + true + } + + fn wakeup(&self, event: Event) -> bool { + let mut state = self.state.lock(); + if *state != TaskState::Blocked { + return false; + } + *state = TaskState::Ready; + *self.wakeup_event.lock() = Some(event); + true + } + + fn wakeup_uninterruptible(&self, event: Event) { + let mut state = self.state.lock(); + match *state { + TaskState::Blocked | TaskState::BlockedUninterruptible => {}, + _ => return, + } + *state = TaskState::Ready; + *self.wakeup_event.lock() = Some(event); + } + + fn take_wakeup_event(&self) -> Option { + self.wakeup_event.lock().take() + } +} + +pub fn spawn(entry: fn()) -> Arc { + let tid = tid::alloc(); + let kthread = KThread::new(tid, entry); + let task = Arc::new(kthread); + scheduler::push_task(task.clone()); + task +} diff --git a/src/kernel/main.rs b/src/kernel/main.rs new file mode 100644 index 0000000..277105b --- /dev/null +++ b/src/kernel/main.rs @@ -0,0 +1,104 @@ +use alloc::collections::BTreeMap; + +use crate::kernel::event::timer; +use crate::kernel::config; +use crate::kernel::mm; +use crate::kernel::scheduler; +use crate::kernel::task; +use crate::arch; +use crate::fs; +use crate::driver; +use crate::klib::{kalloc, InitedCell}; +use crate::kinfo; + +#[allow(dead_code)] +fn free_init() { + unsafe extern "C" { + static __init_start: u8; + static __init_end: u8; + } + + let kstart = core::ptr::addr_of!(__init_start) as usize; + let kend = core::ptr::addr_of!(__init_end) as usize; + debug_assert!(kstart % arch::PGSIZE == 0); + debug_assert!(kend % arch::PGSIZE == 0); + + let mut kaddr = core::ptr::addr_of!(__init_start) as usize; + while kaddr < kend { + mm::page::free(kaddr); + kaddr += arch::PGSIZE; + } + + kinfo!("Freed init section memory {:#x} bytes", kend - kstart); +} + +static BOOT_ARGS: InitedCell> = InitedCell::uninit(); + +#[unsafe(link_section = ".text.init")] +pub fn parse_boot_args(bootargs: &'static str) { + let mut bootargs_map = BTreeMap::new(); + for arg in bootargs.split_whitespace() { + if let Some((key, value)) = arg.split_once('=') { + bootargs_map.insert(key, value); + kinfo!("bootarg: {}={}", key, value); + } else { + bootargs_map.insert(arg, ""); + kinfo!("bootarg: {}", arg); + } + } + + BOOT_ARGS.init(bootargs_map); +} + +#[unsafe(no_mangle)] +extern "C" fn main(hartid: usize, heap_start: usize, memory_top: usize) -> ! { + kinfo!("Welcome to KernelX!"); + + kinfo!("Initializing KernelX..."); + + kalloc::init(heap_start, config::KERNEL_HEAP_SIZE); + mm::init(heap_start + config::KERNEL_HEAP_SIZE, memory_top); + driver::init(); + arch::init(); + arch::scan_device(); + + #[cfg(feature = "swap-memory")] + mm::swappable::init(); + + kinfo!("Welcome to KernelX!"); + + kinfo!("Frame space: {:#x} - {:#x}, total {:#x}", heap_start + config::KERNEL_HEAP_SIZE, memory_top, memory_top - (heap_start + config::KERNEL_HEAP_SIZE)); + + fs::init(); + fs::mount_init_fs( + BOOT_ARGS.get("root").unwrap_or(&config::DEFAULT_BOOT_ROOT), + BOOT_ARGS.get("rootfstype").unwrap_or(&config::DEFAULT_BOOT_ROOT_FSTYPE) + ); + + task::create_initprocess( + BOOT_ARGS.get("init").unwrap_or(&config::DEFAULT_INITPATH), + BOOT_ARGS.get("initcwd").unwrap_or(&config::DEFAULT_INITCWD) + ); + + timer::init(); + + #[cfg(feature = "swap-memory")] + { + crate::kernel::mm::swappable::spawn_kswapd(); + } + + // free_init(); + + kinfo!("KernelX initialized successfully!"); + + scheduler::run_tasks(hartid as u8); +} + +pub fn exit() -> ! { + fs::fini(); + + #[cfg(feature = "swap-memory")] + crate::kernel::mm::swappable::fini(); + + driver::chosen::kpmu::shutdown(); +} diff --git a/src/kernel/mm/addrspace.rs b/src/kernel/mm/addrspace.rs new file mode 100644 index 0000000..f1c8d9e --- /dev/null +++ b/src/kernel/mm/addrspace.rs @@ -0,0 +1,305 @@ +use alloc::boxed::Box; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; +use spin::{Lazy, Mutex, RwLock}; + +use crate::safe_page_write; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::mm::{maparea, PhysPageFrame}; +use crate::kernel::mm::maparea::Auxv; +use crate::kernel::config::USER_RANDOM_ADDR_BASE; +use crate::arch::{PageTable, PageTableTrait, UserContext, TRAMPOLINE_BASE}; +use crate::arch; + +use super::{MemAccessType, MapPerm}; +use super::vdso; + +cfg_if::cfg_if! { + if #[cfg(feature="swap-memory")] { + use alloc::collections::LinkedList; + use crate::klib::SpinLock; + } +} + +unsafe extern "C"{ + static __trampoline_start: u8; +} + +static RANDOM_PAGE: Lazy = Lazy::new(|| { + PhysPageFrame::alloc() +}); + +fn create_pagetable() -> PageTable { + let mut pagetable = PageTable::new(); + pagetable.create(); + pagetable.mmap( + TRAMPOLINE_BASE, + core::ptr::addr_of!(__trampoline_start) as usize, + MapPerm::R | MapPerm::X + ); + pagetable.mmap( + USER_RANDOM_ADDR_BASE, + RANDOM_PAGE.get_page(), + MapPerm::R | MapPerm::U + ); + + vdso::map_to_pagetale(&mut pagetable); + + pagetable +} + +#[cfg(feature = "swap-memory")] +use crate::kernel::mm::swappable::AddrSpaceFamilyChain; + +pub struct AddrSpace { + map_manager: Mutex, + pagetable: RwLock, + usercontext_frames: Mutex>, + + #[cfg(feature = "swap-memory")] + family_chain: AddrSpaceFamilyChain, +} + +impl AddrSpace { + pub fn new() -> Arc { + let addrspace = Arc::new(AddrSpace { + map_manager: Mutex::new(maparea::Manager::new()), + pagetable: RwLock::new(create_pagetable()), + usercontext_frames: Mutex::new(Vec::new()), + + #[cfg(feature = "swap-memory")] + family_chain: AddrSpaceFamilyChain::new(SpinLock::new(LinkedList::new())), + }); + + #[cfg(feature = "swap-memory")] + addrspace.family_chain.lock().push_back(Arc::downgrade(&addrspace)); + + addrspace + } + + pub fn fork(self: &Arc) -> Arc { + let new_pagetable = RwLock::new(create_pagetable()); + + let new_map_manager = self.map_manager.lock().fork(&self.pagetable, &new_pagetable); + + let addrspace = Arc::new(AddrSpace { + map_manager: Mutex::new(new_map_manager), + pagetable: new_pagetable, + usercontext_frames: Mutex::new(Vec::new()), + + #[cfg(feature = "swap-memory")] + family_chain: AddrSpaceFamilyChain::new(SpinLock::new(LinkedList::new())), + }); + + #[cfg(feature = "swap-memory")] + { + let weak = Arc::downgrade(&addrspace); + addrspace.family_chain.lock().push_back(weak.clone()); + self.family_chain.lock().push_back(weak); + } + + addrspace + } + + #[cfg(feature = "swap-memory")] + pub fn family_chain(&self) -> &AddrSpaceFamilyChain { + &self.family_chain + } + + pub fn alloc_usercontext_page(&self) -> (usize, *mut UserContext) { + let mut frames = self.usercontext_frames.lock(); + let frame = PhysPageFrame::alloc_zeroed(); + + let uaddr = TRAMPOLINE_BASE - (frames.len() + 1) * arch::PGSIZE; + let kaddr = frame.get_page(); + let user_context_ptr = kaddr as *mut UserContext; + + // Map the user context page in the pagetable + self.pagetable.write().mmap(uaddr, kaddr, MapPerm::R | MapPerm::W); + + frames.push(frame); + + (uaddr, user_context_ptr) + } + + pub fn create_user_stack(&self, argv: &[&str], envp: &[&str], auxv: &Auxv) -> Result { + // self.user_stack.create(argv, envp, aux, &mut self.map_manager) + let mut map_manager = self.map_manager.lock(); + map_manager.create_user_stack(argv, envp, auxv, self) + } + + pub fn map_area(&self, uaddr: usize, area: Box) -> Result<(), Errno> { + let mut map_manager = self.map_manager.lock(); + map_manager.map_area(uaddr, area); + + Ok(()) + } + + pub fn set_area_perm(&self, uaddr: usize, page_count: usize, perm: MapPerm) -> Result<(), Errno> { + let mut map_manager = self.map_manager.lock(); + map_manager.set_map_area_perm(uaddr, page_count, perm, &self.pagetable) + } + + pub fn increase_userbrk(&self, ubrk: usize) -> Result { + let mut map_manager = self.map_manager.lock(); + map_manager.increase_userbrk(ubrk) + } + + pub fn translate_write(self: &Arc, uaddr: usize) -> SysResult { + self.map_manager.lock().translate_write(uaddr, self).ok_or(Errno::EFAULT) + } + + pub fn copy_to_user_buffer(self: &Arc, mut uaddr: usize, buffer: &[u8]) -> Result<(), Errno> { + let mut left = buffer.len(); + let mut copied: usize = 0; + + let mut map_manager = self.map_manager.lock(); + + while left > 0 { + let kaddr = map_manager.translate_write(uaddr, self).ok_or(Errno::EFAULT)?; + + let page_offset = uaddr & (arch::PGSIZE - 1); + let write_len = core::cmp::min(left, arch::PGSIZE - page_offset); + + safe_page_write!(kaddr, &buffer[copied..copied + write_len]); + + copied += write_len; + left -= write_len; + uaddr += write_len; + } + + Ok(()) + } + + pub fn copy_to_user(self: &Arc, uaddr: usize, value: T) -> Result<(), Errno> { + let buffer = unsafe { + core::slice::from_raw_parts((&value as *const T) as *const u8, core::mem::size_of::()) + }; + self.copy_to_user_buffer(uaddr, buffer) + } + + /// Copy a slice to user space + pub fn copy_to_user_slice(self: &Arc, uaddr: usize, slice: &[T]) -> SysResult<()> { + let buffer = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice)) }; + self.copy_to_user_buffer(uaddr, buffer) + } + + pub fn copy_from_user_buffer(self: &Arc, mut uaddr: usize, buffer: &mut [u8]) -> Result<(), Errno> { + let mut left = buffer.len(); + let mut copied: usize = 0; + + let mut map_manager = self.map_manager.lock(); + + while left > 0 { + let kaddr = map_manager.translate_read(uaddr, self).ok_or(Errno::EFAULT)?; + + let page_offset = uaddr & (arch::PGSIZE - 1); + let read_len = core::cmp::min(left, arch::PGSIZE - page_offset); + + let src = unsafe { core::slice::from_raw_parts(kaddr as *const u8, read_len) }; + buffer[copied..copied + read_len].copy_from_slice(src); + + copied += read_len; + left -= read_len; + uaddr += read_len; + } + + Ok(()) + } + + pub fn copy_from_user(self: &Arc, uaddr: usize) -> Result { + let mut value: T = unsafe { core::mem::zeroed() }; + let buffer = unsafe { + core::slice::from_raw_parts_mut( + &mut value as *mut T as *mut u8, + core::mem::size_of::() + ) + }; + self.copy_from_user_buffer(uaddr, buffer)?; + Ok(value) + } + + pub fn get_user_string(self: &Arc, mut uaddr: usize) -> Result { + let mut map_manager = self.map_manager.lock(); + + let mut result = String::new(); + + const MAXSIZE: usize = 255; + + loop { + let page_offset = uaddr & arch::PGMASK; + let to_read = arch::PGSIZE - page_offset; + let kaddr = map_manager.translate_read(uaddr, self).ok_or(Errno::EFAULT)?; + + let slice = unsafe { core::slice::from_raw_parts(kaddr as *const u8, to_read) }; + if let Some(pos) = slice.iter().position(|&b| b == 0) { + result.push_str(&String::from_utf8(slice[..pos].to_vec()).map_err(|_| Errno::EINVAL)?); + break; + } else { + result.push_str(&String::from_utf8(slice.to_vec()).map_err(|_| Errno::EINVAL)?); + if result.len() > MAXSIZE { + return Err(Errno::EINVAL); + } + } + + uaddr += to_read; + } + + Ok(result) + } + + pub fn copy_from_user_slice(self: &Arc, uaddr: usize, slice: &mut [T]) -> SysResult<()> { + let buffer = unsafe { core::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, core::mem::size_of_val(slice)) }; + self.copy_from_user_buffer(uaddr, buffer) + } + + pub fn with_pagetable(&self, f: F) -> R + where + F: FnOnce(&PageTable) -> R, + { + f(&self.pagetable.read()) + } + + pub fn pagetable(&self) -> &RwLock { + &self.pagetable + } + + pub fn with_map_manager_mut(&self, f: F) -> R + where + F: FnOnce(&mut maparea::Manager) -> R, + { + f(&mut self.map_manager.lock()) + } + + pub fn try_to_fix_memory_fault(self: &Arc, uaddr: usize, access_type: MemAccessType) -> bool { + let map_manager = &mut self.map_manager.lock(); + if !map_manager.try_to_fix_memory_fault(uaddr, access_type, self) { + map_manager.print_all_areas(); + false + } else { + true + } + } + + #[cfg(feature = "swap-memory")] + pub fn unmap_swap_page(&self, uaddr: usize, kaddr: usize) { + self.pagetable.write().munmap_with_check(uaddr, kaddr); + } + + #[cfg(feature = "swap-memory")] + pub fn take_page_access_dirty_bit(&self, uaddr: usize) -> Option<(bool, bool)> { + self.pagetable.write().take_access_dirty_bit(uaddr) + } +} + +impl Drop for AddrSpace { + fn drop(&mut self) { + let frames = self.usercontext_frames.lock(); + let mut pagetable = self.pagetable.write(); + for i in 0..frames.len() { + let uaddr = TRAMPOLINE_BASE - (i + 1) * arch::PGSIZE; + pagetable.munmap(uaddr); + } + } +} diff --git a/src/kernel/mm/elf/def.rs b/src/kernel/mm/elf/def.rs new file mode 100644 index 0000000..dadb8fb --- /dev/null +++ b/src/kernel/mm/elf/def.rs @@ -0,0 +1,183 @@ +use core::fmt::Display; + +pub type Elf64Half = u16; +pub type Elf64Word = u32; +pub type Elf64Xword = u64; +pub type Elf64Addr = u64; +pub type Elf64Off = u64; + +pub const EI_NIDENT: usize = 16; + +pub const ELFMAG0: u8 = 0x7f; +pub const ELFMAG1: u8 = b'E'; +pub const ELFMAG2: u8 = b'L'; +pub const ELFMAG3: u8 = b'F'; + +pub const EI_MAG0: usize = 0; +pub const EI_MAG1: usize = 1; +pub const EI_MAG2: usize = 2; +pub const EI_MAG3: usize = 3; +pub const EI_CLASS: usize = 4; +pub const EI_DATA: usize = 5; + +pub const ELFCLASSNONE: u8 = 0; +pub const ELFCLASS32: u8 = 1; +pub const ELFCLASS64: u8 = 2; + +pub const ELFDATANONE: u8 = 0; +pub const ELFDATA2LSB: u8 = 1; +pub const ELFDATA2MSB: u8 = 2; + +pub const ET_NONE: u16 = 0; +pub const ET_REL: u16 = 1; +pub const ET_EXEC: u16 = 2; +pub const ET_DYN: u16 = 3; +pub const ET_CORE: u16 = 4; + +// e_machine constants +pub const EM_RISCV: u16 = 243; // RISC-V + +// ELF64文件头 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Elf64Ehdr { + pub e_ident: [u8; EI_NIDENT], // Magic number and other info + pub e_type: Elf64Half, // Object file type + pub e_machine: Elf64Half, // Architecture + pub e_version: Elf64Word, // Object file version + pub e_entry: Elf64Addr, // Entry point virtual address + pub e_phoff: Elf64Off, // Program header table file offset + pub e_shoff: Elf64Off, // Section header table file offset + pub e_flags: Elf64Word, // Processor-specific flags + pub e_ehsize: Elf64Half, // ELF header size in bytes + pub e_phentsize: Elf64Half, // Program header table entry size + pub e_phnum: Elf64Half, // Program header table entry count + pub e_shentsize: Elf64Half, // Section header table entry size + pub e_shnum: Elf64Half, // Section header table entry count + pub e_shstrndx: Elf64Half, // Section header string table index +} + +pub const PT_NULL: u32 = 0; +pub const PT_LOAD: u32 = 1; +pub const PT_DYNAMIC: u32 = 2; +pub const PT_INTERP: u32 = 3; +pub const PT_PHDR: u32 = 6; + +pub const PF_X: u32 = 1 << 0; +pub const PF_W: u32 = 1 << 1; +pub const PF_R: u32 = 1 << 2; + +// ELF64程序头 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Elf64Phdr { + pub p_type : Elf64Word , // Segment type + pub p_flags : Elf64Word , // Segment flags + pub p_offset: Elf64Off , // Segment file offset + pub p_vaddr : Elf64Addr , // Segment virtual address + pub p_paddr : Elf64Addr , // Segment physical address + pub p_filesz: Elf64Xword, // Segment size in file + pub p_memsz : Elf64Xword, // Segment size in memory + pub p_align : Elf64Xword, // Segment alignment +} + +// // 段头类型 (sh_type) +// pub const SHT_NULL: u32 = 0; +// pub const SHT_PROGBITS: u32 = 1; +// pub const SHT_SYMTAB: u32 = 2; +// pub const SHT_STRTAB: u32 = 3; +// pub const SHT_RELA: u32 = 4; +// pub const SHT_HASH: u32 = 5; +// pub const SHT_DYNAMIC: u32 = 6; +// pub const SHT_NOTE: u32 = 7; +// pub const SHT_NOBITS: u32 = 8; +// pub const SHT_REL: u32 = 9; +// pub const SHT_SHLIB: u32 = 10; +// pub const SHT_DYNSYM: u32 = 11; + +// // 段头标志 (sh_flags) +// pub const SHF_WRITE: u64 = 1 << 0; // 可写 +// pub const SHF_ALLOC: u64 = 1 << 1; // 占用内存 +// pub const SHF_EXECINSTR: u64 = 1 << 2; // 可执行 + +// ELF64段头 +// #[repr(C)] +// #[derive(Debug, Clone, Copy)] +// pub struct Elf64Shdr { +// pub sh_name: Elf64Word, // Section name (string tbl index) +// pub sh_type: Elf64Word, // Section type +// pub sh_flags: Elf64Xword, // Section flags +// pub sh_addr: Elf64Addr, // Section virtual addr at execution +// pub sh_offset: Elf64Off, // Section file offset +// pub sh_size: Elf64Xword, // Section size in bytes +// pub sh_link: Elf64Word, // Link to another section +// pub sh_info: Elf64Word, // Additional section information +// pub sh_addralign: Elf64Xword, // Section alignment +// pub sh_entsize: Elf64Xword, // Entry size if section holds table +// } + +impl Elf64Ehdr { + pub fn is_valid_elf(&self) -> bool { + self.e_ident[EI_MAG0] == ELFMAG0 && + self.e_ident[EI_MAG1] == ELFMAG1 && + self.e_ident[EI_MAG2] == ELFMAG2 && + self.e_ident[EI_MAG3] == ELFMAG3 + } + + pub fn is_64bit(&self) -> bool { + self.e_ident[EI_CLASS] == ELFCLASS64 + } + + pub fn is_little_endian(&self) -> bool { + self.e_ident[EI_DATA] == ELFDATA2LSB + } + + pub fn is_riscv(&self) -> bool { + self.e_machine == EM_RISCV + } + + pub fn is_executable(&self) -> bool { + self.e_type == ET_EXEC + } + + pub fn is_dynamic(&self) -> bool { + self.e_type == ET_DYN + } +} + +impl Elf64Phdr { + pub const fn is_load(&self) -> bool { + self.p_type == PT_LOAD + } + + pub const fn is_dynamic(&self) -> bool { + self.p_type == PT_DYNAMIC + } + + pub const fn is_interp(&self) -> bool { + self.p_type == PT_INTERP + } + + pub const fn is_phdr(&self) -> bool { + self.p_type == PT_PHDR + } + + pub const fn is_readable(&self) -> bool { + (self.p_flags & PF_R) != 0 + } + + pub const fn is_writable(&self) -> bool { + (self.p_flags & PF_W) != 0 + } + + pub const fn is_executable(&self) -> bool { + (self.p_flags & PF_X) != 0 + } +} + +impl Display for Elf64Phdr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Elf64Phdr {{ p_type: {}, p_flags: {}, p_offset: {}, p_vaddr: {}, p_paddr: {}, p_filesz: {}, p_memsz: {}, p_align: {} }}", + self.p_type, self.p_flags, self.p_offset, self.p_vaddr, self.p_paddr, self.p_filesz, self.p_memsz, self.p_align) + } +} diff --git a/src/kernel/mm/elf/loaddyn.rs b/src/kernel/mm/elf/loaddyn.rs new file mode 100644 index 0000000..2a63388 --- /dev/null +++ b/src/kernel/mm/elf/loaddyn.rs @@ -0,0 +1,103 @@ +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec; + +use crate::fs::file::{File, FileOps, FileFlags, SeekWhence}; +use crate::fs::vfs; +use crate::kernel::errno::Errno; +use crate::kernel::mm::AddrSpace; +use crate::kernel::config; +use crate::{kinfo, ktrace}; + +use super::def::*; +use super::loader::*; + +#[derive(Debug, Clone, Copy)] +pub struct DynInfo { + pub user_entry: usize, + pub interpreter_base: usize, + pub phdr_addr: usize, + pub phent: u16, + pub phnum: u16, +} + +pub fn load_dyn(ehdr: &Elf64Ehdr, file: &Arc, addrspace: &mut AddrSpace) -> Result<(usize, DynInfo), Errno> { + let ph_offset = ehdr.e_phoff as usize; + let ph_num = ehdr.e_phnum as usize; + + ktrace!("PHDR offset: {:#x}, number of entries: {}", ph_offset, ph_num); + + let addr_base = config::USER_EXEC_ADDR_BASE; + let mut interpreter_path: Option = None; + let mut phdr_addr: Option = None; + + for i in 0..ph_num { + file.seek((ph_offset + i * core::mem::size_of::()) as isize, SeekWhence::BEG)?; + let phdr = read_phdr(file)?; + + if phdr.is_load() { + load_program_from_file(&phdr, file, addrspace, addr_base)?; + } else if phdr.is_phdr() { + phdr_addr = Some(phdr.p_vaddr as usize + addr_base); + } else if phdr.is_interp() { + file.seek(phdr.p_offset as isize, SeekWhence::BEG)?; + let mut buffer = vec![0u8; phdr.p_filesz as usize]; + file.read(&mut buffer)?; + + if let Some(null_pos) = buffer.iter().position(|&x| x == 0) { + buffer.truncate(null_pos); + } + + if let Ok(path) = String::from_utf8(buffer) { + interpreter_path = Some(path); + } else { + return Err(Errno::ENOEXEC); + } + } + } + + let phdr_addr = phdr_addr.ok_or(Errno::ENOEXEC)?; + + let interpreter_path = interpreter_path.ok_or(Errno::ENOEXEC)?; + let (interpreter_base, interpreter_entry) = load_interpreter(&interpreter_path, addrspace)?; + + let dyn_info = DynInfo { + user_entry: ehdr.e_entry as usize + addr_base, + interpreter_base, + phdr_addr, + phent: ehdr.e_phentsize as u16, + phnum: ehdr.e_phnum as u16, + }; + + Ok((interpreter_entry, dyn_info)) +} + +fn load_interpreter(path: &str, addrspace: &mut AddrSpace) -> Result<(usize, usize), Errno> { + let file_flags = FileFlags::readonly(); + let file = vfs::open_file(path, file_flags).map_err(|_| { + Errno::ENOENT + })?; + let file = Arc::new(file); + + let ehdr = read_ehdr(&file)?; + + if !ehdr.is_valid_elf() || !ehdr.is_64bit() || !ehdr.is_riscv() { + return Err(Errno::ENOEXEC); + } + + if !ehdr.is_dynamic() { + return Err(Errno::ENOEXEC); + } + + let addr_base = config::USER_LINKER_ADDR_BASE; + + load_loadable_phdr( + ehdr.e_phoff as usize, + ehdr.e_phnum as usize, + &file, + addrspace, + addr_base + )?; + + Ok((addr_base, ehdr.e_entry as usize + addr_base)) +} diff --git a/src/kernel/mm/elf/loader.rs b/src/kernel/mm/elf/loader.rs new file mode 100644 index 0000000..044b36d --- /dev/null +++ b/src/kernel/mm/elf/loader.rs @@ -0,0 +1,213 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec; +use alloc::string::String; + +use crate::fs::file::{File, FileOps, FileFlags, SeekWhence}; +use crate::fs::{Perm, PermFlags, vfs}; +use crate::kernel::errno::Errno; +// use crate::kernel::mm::elf::loaddyn::DynInfo; +use crate::kernel::mm::{maparea, AddrSpace, MapPerm}; +use crate::kernel::config; +use crate::{arch, ktrace}; +use crate::println; + +use super::def::*; + +#[derive(Debug, Clone, Copy)] +pub struct DynInfo { + pub user_entry: usize, + pub interpreter_base: usize, + pub phdr_addr: usize, + pub phent: u16, + pub phnum: u16, +} + +pub fn read_ehdr(file: &Arc) -> Result { + let mut header = [0u8; core::mem::size_of::()]; + file.read(&mut header)?; + + let ehdr = unsafe { + &*(header.as_ptr() as *const Elf64Ehdr) + }; + + Ok(*ehdr) +} + +pub fn read_phdr(file: &Arc) -> Result { + let mut ph_buf = [0u8; core::mem::size_of::()]; + file.read(&mut ph_buf)?; + + let phdr = unsafe { + &*(ph_buf.as_ptr() as *const Elf64Phdr) + }; + + Ok(*phdr) +} + +pub fn load_elf(file: &Arc, addrspace: &AddrSpace) -> Result<(usize, Option), Errno> { + let ehdr = read_ehdr(file)?; + + if !ehdr.is_valid_elf() { + ktrace!("Invalid ELF header: {:?}", ehdr.e_ident); + return Err(Errno::ENOEXEC); + } + + if !ehdr.is_64bit() { + println!("Unsupported ELF format: not 64-bit"); + return Err(Errno::ENOEXEC); + } + + if !ehdr.is_little_endian() { + return Err(Errno::ENOEXEC); + } + + if !ehdr.is_riscv() { + println!("Unsupported ELF format: not RISC-V"); + return Err(Errno::ENOEXEC); + } + + if !(ehdr.is_dynamic() || ehdr.is_executable()) { + println!("Unsupported ELF type: e_type={:#x}", ehdr.e_type); + return Err(Errno::ENOEXEC); + } + + let addr_base = if ehdr.is_executable() { + 0 + } else if ehdr.is_dynamic() { + config::USER_EXEC_ADDR_BASE + } else { + return Err(Errno::ENOEXEC); + }; + + let ph_offset = ehdr.e_phoff as usize; + let ph_num = ehdr.e_phnum as usize; + + ktrace!("PHDR offset: {:#x}, number of entries: {}", ph_offset, ph_num); + + let mut interpreter_path: Option = None; + let mut phdr_addr: Option = None; + + for i in 0..ph_num { + file.seek((ph_offset + i * core::mem::size_of::()) as isize, SeekWhence::BEG)?; + let phdr = read_phdr(file)?; + + if phdr.is_load() { + load_program_from_file(&phdr, file, addrspace, addr_base)?; + } else if phdr.is_phdr() { + phdr_addr = Some(phdr.p_vaddr as usize + addr_base); + } else if phdr.is_interp() { + file.seek(phdr.p_offset as isize, SeekWhence::BEG)?; + let mut buffer = vec![0u8; phdr.p_filesz as usize]; + file.read(&mut buffer)?; + + if let Some(null_pos) = buffer.iter().position(|&x| x == 0) { + buffer.truncate(null_pos); + } + + if let Ok(path) = String::from_utf8(buffer) { + interpreter_path = Some(path); + } else { + return Err(Errno::ENOEXEC); + } + } + } + + let phdr_addr = phdr_addr.unwrap_or(0); + + if let Some(interpreter_path) = &interpreter_path { + // crate::kinfo!("Interpreter path: {}", interpreter_path); + let (interpreter_base, interpreter_entry) = load_interpreter(&interpreter_path, addrspace)?; + + let dyn_info = DynInfo { + user_entry: ehdr.e_entry as usize + addr_base, + interpreter_base, + phdr_addr, + phent: ehdr.e_phentsize as u16, + phnum: ehdr.e_phnum as u16, + }; + + Ok((interpreter_entry, Some(dyn_info))) + } else { + // Ok((load_exec(&ehdr, file, addrspace)?, None)) + Ok((ehdr.e_entry as usize + addr_base, None)) + } +} + +pub fn load_loadable_phdr( + ph_offset: usize, + ph_num: usize, + file: &Arc, + addrspace: &AddrSpace, + addr_base: usize +) -> Result<(), Errno> { + for i in 0..ph_num { + file.seek((ph_offset + i * core::mem::size_of::()) as isize, SeekWhence::BEG)?; + let phdr = read_phdr(file)?; + + if phdr.is_load() { + load_program_from_file(&phdr, file, addrspace, addr_base)?; + } + } + + Ok(()) +} + +pub fn load_program_from_file( + phdr: &Elf64Phdr, + file: &Arc, + addrspace: &AddrSpace, + addr_base: usize +) -> Result<(), Errno> { + let mut perm = MapPerm::U | MapPerm::R; + if phdr.is_readable() { + perm |= MapPerm::R; + } + if phdr.is_writable() { + perm |= MapPerm::W; + } + if phdr.is_executable() { + perm |= MapPerm::X; + } + + ktrace!("Loading program from file, phdr.p_vaddr={:#x}, phdr.p_memsz={:#x}, phdr.p_type={:#x}, addr_base={:#x}, perm={:?}", phdr.p_vaddr, phdr.p_memsz, phdr.p_type, addr_base, perm); + + let pgoff = phdr.p_vaddr as usize % arch::PGSIZE; + let ubase = (phdr.p_vaddr as usize + addr_base) & !arch::PGMASK; + let memory_size = phdr.p_memsz as usize + pgoff; // Aligned base to page + let file_size = phdr.p_filesz as usize + pgoff; + let file_offset = phdr.p_offset as usize & !arch::PGMASK; + + let area = maparea::ELFArea::new(ubase, perm, file.clone(), file_offset, file_size, memory_size); + addrspace.map_area(ubase, Box::new(area))?; + + Ok(()) +} + +fn load_interpreter(path: &str, addrspace: &AddrSpace) -> Result<(usize, usize), Errno> { + let file_flags = FileFlags::readonly(); + let file = vfs::open_file(path, file_flags, &Perm::new(PermFlags::X))?; + let file = Arc::new(file); + + let ehdr = read_ehdr(&file)?; + + if !ehdr.is_valid_elf() || !ehdr.is_64bit() || !ehdr.is_riscv() { + return Err(Errno::ENOEXEC); + } + + if !ehdr.is_dynamic() { + return Err(Errno::ENOEXEC); + } + + let addr_base = config::USER_LINKER_ADDR_BASE; + + load_loadable_phdr( + ehdr.e_phoff as usize, + ehdr.e_phnum as usize, + &file, + addrspace, + addr_base + )?; + + Ok((addr_base, ehdr.e_entry as usize + addr_base)) +} diff --git a/src/kernel/mm/elf/mod.rs b/src/kernel/mm/elf/mod.rs new file mode 100644 index 0000000..301b97e --- /dev/null +++ b/src/kernel/mm/elf/mod.rs @@ -0,0 +1,3 @@ +pub mod def; +// mod loaddyn; +pub mod loader; diff --git a/src/kernel/mm/maparea/anonymous.rs b/src/kernel/mm/maparea/anonymous.rs new file mode 100644 index 0000000..146d989 --- /dev/null +++ b/src/kernel/mm/maparea/anonymous.rs @@ -0,0 +1,319 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::boxed::Box; +use spin::RwLock; + +use crate::kernel::mm::AddrSpace; +use crate::kernel::mm::maparea::area::Area; +use crate::kernel::mm::{MapPerm, MemAccessType}; +use crate::arch::{PageTable, PageTableTrait}; +use crate::arch; + +use super::nofilemap::FrameState; + +#[cfg(feature = "swap-memory")] +use super::nofilemap::SwappableNoFileFrame; + +pub struct AnonymousArea { + ubase: usize, + perm: MapPerm, + frames: Vec, + shared: bool +} + +impl AnonymousArea { + pub fn new(ubase: usize, perm: MapPerm, page_count: usize, shared: bool) -> Self { + // Anonymous areas should be page-aligned + debug_assert!(ubase % arch::PGSIZE == 0, "ubase should be page-aligned"); + + let frames = Vec::from_iter((0..page_count).map(|_| FrameState::Unallocated)); + Self { + ubase, + perm, + frames, + shared + } + } + + fn allocate_page(&mut self, page_index: usize, addrspace: &Arc) -> usize { + debug_assert!(page_index < self.frames.len()); + debug_assert!(self.frames[page_index].is_unallocated()); + + // Create a new zeroed page for anonymous memory + let uaddr = self.ubase + page_index * arch::PGSIZE; + let (allocated, kpage) = FrameState::allocate(uaddr, addrspace); + + addrspace.pagetable().write().mmap(uaddr, kpage, self.perm); + self.frames[page_index] = allocated; + + kpage + } + + fn copy_on_write_page(&mut self, page_index: usize, addrspace: &AddrSpace) -> usize { + debug_assert!(page_index < self.frames.len()); + debug_assert!(self.frames[page_index].is_cow()); + + let kpage = self.frames[page_index].cow_to_allocated(addrspace); + + addrspace.pagetable().write().mmap_replace(self.ubase + page_index * arch::PGSIZE, kpage, self.perm); + + kpage + } + + #[cfg(feature = "swap-memory")] + fn handle_memory_fault_on_swapped_allocated(&self, frame: &SwappableNoFileFrame, addrspace: &AddrSpace) { + let page = frame.get_page_swap_in(); + addrspace.pagetable().write().mmap(frame.uaddr(), page, self.perm); + } + + #[cfg(feature = "swap-memory")] + fn handle_cow_read_swapped_out(&self, frame: &SwappableNoFileFrame, addrspace: &AddrSpace) { + debug_assert!(frame.is_swapped_out(), "Frame is not swapped out"); + let kpage = frame.get_page_swap_in(); + addrspace.pagetable().write().mmap( + frame.uaddr(), + kpage, + self.perm - MapPerm::W + ); + } +} + +impl Area for AnonymousArea { + fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + debug_assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get(page_index) { + let page = match page_frame { + FrameState::Unallocated => { + self.allocate_page(page_index, addrspace) + } + FrameState::Allocated(frame) | FrameState::Cow(frame) => { + frame.get_page_swap_in() + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + if !self.perm.contains(MapPerm::W) { + return None; + } + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get_mut(page_index) { + let page = match page_frame { + FrameState::Unallocated => { + self.allocate_page(page_index, addrspace) + } + FrameState::Allocated(frame) => { + // frame_get_page_swapped(frame) + frame.get_page_swap_in() + } + FrameState::Cow(_) => { + // Copy-on-write: create a new copy for this process + self.copy_on_write_page(page_index, addrspace) + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Box { + let perm = self.perm - MapPerm::W; + let mut new_pagetable = new_pagetable.write(); + let frames = self.frames.iter().enumerate().map(|(page_index, frame)| { + match frame { + FrameState::Unallocated => FrameState::Unallocated, + FrameState::Allocated(frame) => { + if self.shared { + if let Some(kpage) = frame.get_page() { + new_pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + kpage, + self.perm + ); + } + FrameState::Allocated(frame.clone()) + } else { + if let Some(kpage) = frame.get_page() { + new_pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + kpage, + perm + ); + } + FrameState::Cow(frame.clone()) + } + }, + FrameState::Cow(frame) => { + debug_assert!(!self.shared, "Shared frames should not be CoW"); + if let Some(kpage) = frame.get_page() { + new_pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + kpage, + perm + ); + } + FrameState::Cow(frame.clone()) + } + } + }).collect(); + + if !self.shared { + let mut self_pagetable = self_pagetable.write(); + self.frames.iter_mut().enumerate().for_each(|(page_index, frame)| { + match frame { + FrameState::Allocated(allocated) => { + if let Some(_) = allocated.get_page() { + if self.perm.contains(MapPerm::W) { + self_pagetable.mmap_replace_perm( + self.ubase + page_index * arch::PGSIZE, + perm + ); + } + } + *frame = FrameState::Cow(allocated.clone()); + }, + _ => {} + } + }); + } + + let new_area = AnonymousArea { + ubase: self.ubase, + perm: self.perm, + frames, + shared: self.shared + }; + + Box::new(new_area) + } + + #[allow(unused_variables)] + fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + debug_assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index < self.frames.len() { + match &self.frames[page_index] { + FrameState::Unallocated => { + self.allocate_page(page_index, addrspace); + } + FrameState::Allocated(frame) => { + #[cfg(feature = "swap-memory")] + self.handle_memory_fault_on_swapped_allocated(frame, addrspace); + + #[cfg(not(feature = "swap-memory"))] + // Page is already allocated, this shouldn't happen + panic!("Memory fault on already allocated page at address: {:#x}, access_type: {:?}, perm: {:?}", uaddr, access_type, self.perm); + } + FrameState::Cow(frame) => { + if access_type != MemAccessType::Write { + #[cfg(feature = "swap-memory")] + self.handle_cow_read_swapped_out(frame, addrspace); + #[cfg(not(feature = "swap-memory"))] + panic!("Memory fault on CoW page without write access at address: {:#x}, access_type: {:?}, perm: {:?}", uaddr, access_type, self.perm); + } else { + self.copy_on_write_page(page_index, addrspace); + } + } + } + + true + } else { + false + } + } + + fn set_ubase(&mut self, ubase: usize) { + debug_assert!(ubase % arch::PGSIZE == 0, "ubase should be page-aligned"); + self.ubase = ubase; + } + + fn page_count(&self) -> usize { + self.frames.len() + } + + fn split(mut self: Box, uaddr: usize) -> (Box, Box) { + debug_assert!(uaddr % arch::PGSIZE == 0, "uaddr should be page-aligned"); + debug_assert!(uaddr >= self.ubase && uaddr < self.ubase + self.size(), "uaddr out of range for split, urange: [{:#x}, {:#x}), uaddr: {:#x}", self.ubase, self.ubase + self.size(), uaddr); + + let split_index = (uaddr - self.ubase) / arch::PGSIZE; + let new_ubase = self.ubase + split_index * arch::PGSIZE; + + let new_frames = self.frames.split_off(split_index); + let new_area = AnonymousArea { + ubase: new_ubase, + perm: self.perm, + frames: new_frames, + shared: self.shared + }; + + ( + self, + Box::new(new_area) + ) + } + + fn ubase(&self) -> usize { + self.ubase + } + + fn set_perm(&mut self, perm: MapPerm, pagetable: &RwLock) { + if perm == self.perm { + return; + } + + self.perm = perm; + + let mut pagetable = pagetable.write(); + for frame in self.frames.iter() { + if let FrameState::Allocated(frame) | FrameState::Cow(frame) = frame { + if !frame.is_swapped_out() { + pagetable.mmap_replace_perm(frame.uaddr(), perm); + } + } + // Note: We don't update COW pages here as they should maintain + // their current permission state until the next write access + } + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + for frame in self.frames.iter_mut() { + #[cfg(feature = "swap-memory")] + if let FrameState::Allocated(frame) | FrameState::Cow(frame) = frame { + if !frame.is_swapped_out() { + pagetable.munmap(frame.uaddr()); + } + } + #[cfg(not(feature = "swap-memory"))] + if let FrameState::Allocated(frame) | FrameState::Cow(frame) = frame { + pagetable.munmap(frame.uaddr()); + } + *frame = FrameState::Unallocated; + } + } + + fn type_name(&self) -> &'static str { + "anonymous" + } +} diff --git a/src/kernel/mm/maparea/area.rs b/src/kernel/mm/maparea/area.rs new file mode 100644 index 0000000..2cd809e --- /dev/null +++ b/src/kernel/mm/maparea/area.rs @@ -0,0 +1,67 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::RwLock; + +use crate::kernel::mm::{AddrSpace, MapPerm, MemAccessType}; +use crate::kernel::mm::PhysPageFrame; +use crate::arch::PageTable; + +#[derive(Debug)] +pub enum Frame { + Unallocated, + Allocated(Arc), + Cow(Arc), +} + +impl Frame { + pub fn is_unallocated(&self) -> bool { + matches!(self, Frame::Unallocated) + } + + pub fn is_cow(&self) -> bool { + matches!(self, Frame::Cow(_)) + } +} + +pub trait Area { + fn translate_read (&mut self, uaddr: usize, addrspace: &Arc) -> Option; + fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option; + + fn ubase(&self) -> usize; + + fn set_ubase(&mut self, _ubase: usize) { + unimplemented!("set_ubase not implemented for the area type: {}", self.type_name()); + } + + fn perm(&self) -> MapPerm; + + fn fork(&mut self, self_pagetable: &RwLock, fork_pagetable: &RwLock) -> Box; + + fn try_to_fix_memory_fault( + &mut self, + uaddr: usize, + access_type: MemAccessType, + addrspace: &Arc + ) -> bool; + + fn page_count(&self) -> usize; + fn size(&self) -> usize { + self.page_count() * crate::arch::PGSIZE + } + + fn split(self: Box, _uaddr: usize) -> (Box, Box) { + unimplemented!("split not implemented for the area type: {}", self.type_name()); + } + + fn set_perm(&mut self, _perm: MapPerm, _pagetable: &RwLock) { + unimplemented!("set_perm not implemented for the area type: {}", self.type_name()); + } + + fn unmap(&mut self, _pagetable: &RwLock) { + unimplemented!("unmap not implemented for the area type: {}", self.type_name()); + } + + fn type_name(&self) -> &'static str { + "Area" + } +} diff --git a/src/kernel/mm/maparea/elf.rs b/src/kernel/mm/maparea/elf.rs new file mode 100644 index 0000000..747b85c --- /dev/null +++ b/src/kernel/mm/maparea/elf.rs @@ -0,0 +1,305 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::boxed::Box; +use spin::RwLock; + +use crate::kernel::mm::{AddrSpace, PhysPageFrame}; +use crate::kernel::mm::maparea::area::Area; +use crate::kernel::mm::{MapPerm, MemAccessType}; +use crate::arch; +use crate::arch::{PageTable, PageTableTrait}; +use crate::fs::file::File; + +use super::area::Frame; + +pub struct ELFArea { + ubase: usize, + perm: MapPerm, + + file: Arc, + file_offset: usize, + + file_length: usize, + memory_size: usize, + frames: Vec, +} + +impl ELFArea { + pub fn new( + ubase: usize, + perm: MapPerm, + file: Arc, + file_offset: usize, + file_length: usize, + memory_size: usize + ) -> Self { + // We only handle cases where file_offset and ubase are page-aligned. + // The alignment should be guaranteed by the caller. + assert!(ubase % arch::PGSIZE == 0, "ubase should be page-aligned"); + assert!(file_offset % arch::PGSIZE == 0, "file_offset should be page-aligned"); + + let page_count = (memory_size + arch::PGSIZE - 1) / arch::PGSIZE; + let frames = Vec::from_iter((0..page_count).map(|_| Frame::Unallocated)); + Self { + ubase, + perm, + file, + file_offset, + file_length, + memory_size, + frames, + } + } + + fn load_page(&mut self, page_index: usize, pagetable: &RwLock) -> usize { + debug_assert!(page_index < self.frames.len()); + debug_assert!(self.frames[page_index].is_unallocated()); + + let area_offset = page_index * arch::PGSIZE; + let file_offset = self.file_offset + area_offset; + + let frame = PhysPageFrame::alloc_zeroed(); + if area_offset < self.file_length { + // Read up to a page, but not beyond the file length for this segment. + let length = core::cmp::min(self.file_length - area_offset, arch::PGSIZE); + self.file.read_at(&mut frame.slice()[..length], file_offset).expect("Failed to read file"); + } + + let page = frame.get_page(); + + pagetable.write().mmap(self.ubase + area_offset, frame.get_page(), self.perm); + self.frames[page_index] = Frame::Allocated(Arc::new(frame)); + + page + } + + fn copy_on_write_page(&mut self, page_index: usize, pagetable: &RwLock) -> usize { + assert!(page_index < self.frames.len()); + assert!(self.frames[page_index].is_cow()); + + let cow_frame = core::mem::replace(&mut self.frames[page_index], Frame::Unallocated); + let new_frame = if let Frame::Cow(frame) = cow_frame { + match Arc::try_unwrap(frame) { + Ok(only) => only, + Err(cow) => cow.copy(), + } + } else { + unreachable!(); + }; + + let page = new_frame.get_page(); + + pagetable.write().mmap_replace(self.ubase + page_index * arch::PGSIZE, page, self.perm); + self.frames[page_index] = Frame::Allocated(Arc::new(new_frame)); + + page + } +} + +impl Area for ELFArea { + fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get(page_index) { + let page = match page_frame { + Frame::Unallocated => { + self.load_page(page_index, addrspace.pagetable()) + } + Frame::Allocated(frame) => { + frame.get_page() + } + Frame::Cow(frame) => { + frame.get_page() + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get(page_index) { + let page = match page_frame { + Frame::Unallocated => { + self.load_page(page_index, addrspace.pagetable()) + } + Frame::Allocated(frame) => { + frame.get_page() + } + Frame::Cow(_) => { + self.copy_on_write_page(page_index, addrspace.pagetable()) + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Box { + let cow_perm = self.perm - MapPerm::W; + let mut new_pagetable = new_pagetable.write(); + let frames = self.frames.iter().enumerate().map(|(page_index, frame)| { + match frame { + Frame::Unallocated => Frame::Unallocated, + Frame::Allocated(frame) | Frame::Cow(frame) => { + new_pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + frame.get_page(), + cow_perm + ); + Frame::Cow(frame.clone()) + } + } + }).collect(); + + let mut self_pagetable = self_pagetable.write(); + self.frames.iter_mut().enumerate().for_each(|(page_index, frame)| { + *frame = match frame { + Frame::Unallocated => Frame::Unallocated, + Frame::Allocated(frame) | Frame::Cow(frame) => { + self_pagetable.mmap_replace_perm( + self.ubase + page_index * arch::PGSIZE, + cow_perm + ); + Frame::Cow(frame.clone()) + } + }; + }); + + let new_area = ELFArea { + ubase: self.ubase, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset, + file_length: self.file_length, + memory_size: self.memory_size, + frames, + }; + + Box::new(new_area) + } + + fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + assert!(uaddr >= self.ubase); + + if access_type == MemAccessType::Execute && !self.perm.contains(MapPerm::X) { + return false; + } + if access_type == MemAccessType::Write && !self.perm.contains(MapPerm::W) { + return false; + } + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index < self.frames.len() { + match &self.frames[page_index] { + Frame::Unallocated => { + self.load_page(page_index, addrspace.pagetable()); + } + Frame::Allocated(_) => { + panic!("Page is already allocated."); + } + Frame::Cow(_) => { + if access_type == MemAccessType::Write { + self.copy_on_write_page(page_index, addrspace.pagetable()); + } else { + panic!("Page is already allocated for read and execute access."); + } + } + } + true + } else { + false + } + } + + fn page_count(&self) -> usize { + self.frames.len() + } + + fn ubase(&self) -> usize { + self.ubase + } + + fn split(mut self: Box, uaddr: usize) -> (Box, Box) { + debug_assert!(uaddr % arch::PGSIZE == 0, "Split address must be page-aligned"); + debug_assert!(uaddr >= self.ubase && uaddr < self.ubase + self.size(), "Split address must be within area bounds"); + + let split_index = (uaddr - self.ubase) / arch::PGSIZE; + let split_offset = split_index * arch::PGSIZE; + let remaining_frames = self.frames.split_off(split_index); + + // Calculate the new file_length and memory_size for the second area + let new_file_length = if self.file_length > split_offset { + self.file_length - split_offset + } else { + 0 + }; + + let new_memory_size = self.memory_size - split_offset; + + // Update the file_length and memory_size for the first area (self) + self.file_length = core::cmp::min(self.file_length, split_offset); + self.memory_size = split_offset; + + let new_area = ELFArea { + ubase: uaddr, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset + split_offset, + file_length: new_file_length, + memory_size: new_memory_size, + frames: remaining_frames, + }; + + (self, Box::new(new_area)) + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter_mut().enumerate() { + if let Frame::Allocated(_) | Frame::Cow(_) = frame { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.munmap(uaddr); + } + *frame = Frame::Unallocated; + } + } + + fn set_perm(&mut self, perm: MapPerm, pagetable: &RwLock) { + self.perm = perm; + let cow_perm = perm - MapPerm::W; + let mut pagetable = pagetable.write(); + self.frames.iter().enumerate().for_each(|(page_index, frame)| { + // if let Frame::Allocated(_) = !frame.is_unallocated() { + // let uaddr = self.ubase + page_index * arch::PGSIZE; + // pagetable.write().mmap_replace_perm(uaddr, perm); + // } + let uaddr = self.ubase + page_index * arch::PGSIZE; + match frame { + Frame::Allocated(_) => pagetable.mmap_replace_perm(uaddr, perm), + Frame::Cow(_) => pagetable.mmap_replace_perm(uaddr, cow_perm), + Frame::Unallocated => {} + } + }); + } + + fn type_name(&self) -> &'static str { + "ELFArea" + } +} diff --git a/src/kernel/mm/maparea/filemap/mod.rs b/src/kernel/mm/maparea/filemap/mod.rs new file mode 100644 index 0000000..01de433 --- /dev/null +++ b/src/kernel/mm/maparea/filemap/mod.rs @@ -0,0 +1,5 @@ +mod shared; +mod private; + +pub use private::PrivateFileMapArea; +pub use shared::SharedFileMapArea; diff --git a/src/kernel/mm/maparea/filemap/private.rs b/src/kernel/mm/maparea/filemap/private.rs new file mode 100644 index 0000000..c21d693 --- /dev/null +++ b/src/kernel/mm/maparea/filemap/private.rs @@ -0,0 +1,321 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::boxed::Box; +use spin::RwLock; + +use crate::arch::{PageTable, PageTableTrait}; +use crate::arch; +use crate::kernel::mm::PhysPageFrame; +use crate::kernel::mm::maparea::area::Area; +use crate::kernel::mm::maparea::nofilemap::{FrameState, SwappableNoFileFrame}; +use crate::kernel::mm::{AddrSpace, MapPerm, MemAccessType}; +use crate::fs::file::File; + +pub struct PrivateFileMapArea { + ubase: usize, + perm: MapPerm, + + file: Arc, + file_offset: usize, + file_length: usize, + + frames: Vec, +} + +impl PrivateFileMapArea { + pub fn new( + ubase: usize, + perm: MapPerm, + file: Arc, + file_offset: usize, + file_length: usize + ) -> Self { + // File mapping areas should be page-aligned + debug_assert!(ubase % arch::PGSIZE == 0, "ubase should be page-aligned"); + debug_assert!(file_offset % arch::PGSIZE == 0, "file_offset should be page-aligned"); + + let page_count = (file_length + arch::PGSIZE - 1) / arch::PGSIZE; + let frames = Vec::from_iter((0..page_count).map(|_| FrameState::Unallocated)); + Self { + ubase, + perm, + file, + file_offset, + file_length, + frames, + } + } + + fn load_page(&mut self, page_index: usize, addrspace: &AddrSpace) -> usize { + debug_assert!(page_index < self.frames.len()); + debug_assert!(self.frames[page_index].is_unallocated()); + + let area_offset = page_index * arch::PGSIZE; + let file_offset = self.file_offset + area_offset; + + let uaddr = self.ubase + area_offset; + + let frame = PhysPageFrame::alloc_zeroed(); + + // Try to read from file, but only within the specified file_length + if area_offset < self.file_length { + let mut buffer = [0u8; arch::PGSIZE]; + // Calculate how much data we can read from this page: + // - Don't read beyond the file_length boundary + // - Don't read beyond one page + let length = core::cmp::min(self.file_length - area_offset, arch::PGSIZE); + + match self.file.read_at(&mut buffer[..length], file_offset) { + Ok(_) => { + frame.copy_from_slice(0, &buffer[..length]); + } + Err(_) => { + // Keep the page zeroed if file read fails + } + } + } + + let kpage = frame.get_page(); + + addrspace.pagetable().write().mmap(self.ubase + area_offset, kpage, self.perm); + self.frames[page_index] = FrameState::Allocated(Arc::new(SwappableNoFileFrame::allocated(uaddr, frame, addrspace))); + + kpage + } + + fn copy_on_write_page(&mut self, page_index: usize, addrspace: &AddrSpace) -> usize { + debug_assert!(page_index < self.frames.len()); + + debug_assert!(self.perm.contains(MapPerm::W), "Original mapping must have write permission for copy-on-write"); + + let area_offset = page_index * arch::PGSIZE; + let (frame, kpage) = match &self.frames[page_index] { + FrameState::Cow(frame) => frame.copy(addrspace), + _ => panic!("Invalid type for copy-on-write"), + }; + + addrspace.pagetable().write().mmap_replace_kaddr(self.ubase + area_offset, kpage); + self.frames[page_index] = FrameState::Allocated(Arc::new(frame)); + + kpage + } + + #[cfg(feature = "swap-memory")] + fn handle_memory_fault_on_swapped_allocated(&self, frame: &SwappableNoFileFrame, addrspace: &AddrSpace) { + debug_assert!(frame.is_swapped_out(), "FrameState is not swapped out"); + let kpage = frame.get_page_swap_in(); + addrspace.pagetable().write().mmap( + frame.uaddr(), + kpage, + self.perm, + ); + } +} + +impl Area for PrivateFileMapArea { + fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get(page_index) { + let page = match page_frame { + FrameState::Unallocated => { + // Lazy loading: load page from file on first access + self.load_page(page_index, addrspace) + } + FrameState::Allocated(frame) | FrameState::Cow(frame) => { + frame.get_page_swap_in() + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + debug_assert!(uaddr >= self.ubase); + + if !self.perm.contains(MapPerm::W) { + return None; + } + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get_mut(page_index) { + let page = match page_frame { + FrameState::Unallocated => { + // Lazy loading: load page from file on first write + self.load_page(page_index, addrspace) + } + FrameState::Allocated(frame) => { + frame.get_page_swap_in() + } + FrameState::Cow(_) => { + // Copy-on-write: create a new copy for this process + self.copy_on_write_page(page_index, addrspace) + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Box { + let cow_perm = self.perm - MapPerm::W; + + let mut pagetable = new_pagetable.write(); + let frames = self.frames.iter().enumerate().map(|(page_index, frame)| { + match frame { + FrameState::Unallocated => FrameState::Unallocated, + FrameState::Allocated(frame) | FrameState::Cow(frame) => { + if let Some(kpage) = frame.get_page() { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.mmap(uaddr, kpage, cow_perm); + } + FrameState::Cow(frame.clone()) + } + } + }).collect(); + + // Update original mapping to be COW + let mut self_pagetable = self_pagetable.write(); + self.frames.iter_mut().enumerate().for_each(|(page_index, frame)| { + if matches!(frame, FrameState::Allocated(_)) { + if let FrameState::Allocated(f) = core::mem::replace(frame, FrameState::Unallocated) { + if let Some(_) = f.get_page() { + let uaddr = self.ubase + page_index * arch::PGSIZE; + self_pagetable.mmap_replace_perm(uaddr, cow_perm); + } + *frame = FrameState::Cow(f); + } + } + }); + + let new_area = PrivateFileMapArea { + ubase: self.ubase, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset, + file_length: self.file_length, + frames, + }; + + Box::new(new_area) + } + + fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + debug_assert!(uaddr >= self.ubase); + + if !self.perm.contains(MapPerm::W) && access_type == MemAccessType::Write { + return false; + } + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index < self.frames.len() { + match &self.frames[page_index] { + FrameState::Unallocated => { + self.load_page(page_index, addrspace); + } + FrameState::Allocated(allocated) => { + // Page is already allocated, this shouldn't happen + #[cfg(feature = "swap-memory")] + self.handle_memory_fault_on_swapped_allocated(&allocated, addrspace); + #[cfg(not(feature = "swap-memory"))] + panic!("Memory fault on already allocated file page at address: {:#x}", uaddr); + } + FrameState::Cow(_) => { + assert!(access_type == MemAccessType::Write, "Memory fault on CoW file page for read access at address: {:#x}", uaddr); + self.copy_on_write_page(page_index, addrspace); + } + } + + true + } else { + false + } + } + + fn ubase(&self) -> usize { + self.ubase + } + + fn set_ubase(&mut self, ubase: usize) { + self.ubase = ubase; + } + + fn page_count(&self) -> usize { + self.frames.len() + } + + fn split(mut self: Box, uaddr: usize) -> (Box, Box) { + debug_assert!(uaddr % arch::PGSIZE == 0, "Split address must be page-aligned"); + debug_assert!(uaddr > self.ubase, "Split address must be greater than ubase"); + debug_assert!(uaddr < self.ubase + self.size(), "Split address out of bounds"); + + let split_index = (uaddr - self.ubase) / arch::PGSIZE; + let split_offset = split_index * arch::PGSIZE; + let remaining_frames = self.frames.split_off(split_index); + + // Calculate the new file_length for the second area + let new_file_length = if self.file_length > split_offset { + self.file_length - split_offset + } else { + 0 + }; + + // Update the file_length for the first area (self) + self.file_length = core::cmp::min(self.file_length, split_offset); + + let new_area = PrivateFileMapArea { + ubase: uaddr, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset + split_offset, + file_length: new_file_length, + frames: remaining_frames, + }; + + (self, Box::new(new_area)) + } + + fn set_perm(&mut self, perm: MapPerm, pagetable: &RwLock) { + self.perm = perm; + + // Update page table permissions for all allocated pages + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter().enumerate() { + if let FrameState::Allocated(frame) = frame { + if let Some(_) = frame.get_page() { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.mmap_replace_perm(uaddr, perm); + } + } + } + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter_mut().enumerate() { + if let FrameState::Allocated(_) | FrameState::Cow(_) = frame { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.munmap(uaddr); + } + *frame = FrameState::Unallocated; + } + } + + fn type_name(&self) -> &'static str { + "PrivateFileMapArea" + } +} diff --git a/src/kernel/mm/maparea/filemap/shared.rs b/src/kernel/mm/maparea/filemap/shared.rs new file mode 100644 index 0000000..82fbacb --- /dev/null +++ b/src/kernel/mm/maparea/filemap/shared.rs @@ -0,0 +1,237 @@ +// TODO: Test and verify the shared file mapping area implementation. +// TODO: Implement the swapped filed page frame + +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use alloc::boxed::Box; +use alloc::vec::Vec; +use alloc::vec; +use spin::RwLock; + +use crate::fs::InodeOps; +use crate::fs::inode::Index as InodeIndex; +use crate::kernel::mm::{MapPerm, PhysPageFrame, AddrSpace, MemAccessType}; +use crate::kernel::mm::maparea::Area; +use crate::klib::SpinLock; +use crate::arch::{PageTable, PageTableTrait}; +use crate::{arch, kinfo}; + +struct MappedFileEntry { + inode: Arc, + shared: BTreeMap, + ref_count: usize, +} + +impl MappedFileEntry { + fn get_page(&mut self, page_index: usize) -> Option { + if let Some(frame) = self.shared.get(&page_index) { + Some(frame.get_page()) + } else { + // Load from inode + let offset = page_index * arch::PGSIZE; + let frame = PhysPageFrame::alloc_zeroed(); + self.inode.readat(frame.slice(), offset).expect("Failed to read."); + let kpage = frame.get_page(); + self.shared.insert(page_index, frame); + Some(kpage) + } + } +} + +impl Drop for MappedFileEntry { + fn drop(&mut self) { + // Write back all pages + for (page_index, frame) in self.shared.iter() { + let offset = page_index * arch::PGSIZE; + self.inode.writeat(frame.slice(), offset).expect("Failed to write back."); + } + } +} + +struct Manager { + mapped: SpinLock>>>, +} + +impl Manager { + pub const fn new() -> Self { + Self { + mapped: SpinLock::new(BTreeMap::new()), + } + } + + pub fn open_mapped_file(&self, inode: Arc, index: InodeIndex) -> Arc> { + let mut mapped = self.mapped.lock(); + if let Some(entry) = mapped.get(&index) { + entry.lock().ref_count += 1; + return entry.clone(); + } else { + let entry = Arc::new(SpinLock::new(MappedFileEntry { + inode: inode, + shared: BTreeMap::new(), + ref_count: 1, + })); + mapped.insert(index, entry.clone()); + return entry; + } + } + + pub fn close_mapped_file(&self, index: InodeIndex) { + let mut mapped = self.mapped.lock(); + let mut should_remove = false; + if let Some(entry) = mapped.get(&index) { + let mut entry_lock = entry.lock(); + entry_lock.ref_count -= 1; + if entry_lock.ref_count == 0 { + should_remove = true; + } + } + if should_remove { + kinfo!("Closing mapped file {:?}", index); + mapped.remove(&index); + } + } +} + +static MANAGER: Manager = Manager::new(); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum FrameState { + Unallocated, + Allocated, +} + +pub struct SharedFileMapArea { + entry: Arc>, + ubase: usize, + offset: usize, + states: Vec, + perm: MapPerm, + inode_index: InodeIndex, +} + +impl SharedFileMapArea { + pub fn new( + ubase: usize, + perm: MapPerm, + inode: Arc, + index: InodeIndex, + offset: usize, + page_count: usize + ) -> Self { + let states = vec![FrameState::Unallocated; page_count]; + let entry = MANAGER.open_mapped_file(inode, index); + Self { + entry, + ubase, + offset, + states, + perm, + inode_index: index, + } + } + + fn translate(&self, uaddr: usize) -> Option { + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index >= self.states.len() { + return None; + } + + let kpage = self.entry.lock().get_page(page_index)?; + Some(kpage + uaddr & arch::PGSIZE) + } +} + +impl Area for SharedFileMapArea { + fn ubase(&self) -> usize { + self.ubase + } + + fn set_ubase(&mut self, ubase: usize) { + self.ubase = ubase; + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn set_perm(&mut self, perm: MapPerm, pagetable: &RwLock) { + self.perm = perm; + let mut pagetable = pagetable.write(); + self.states.iter().enumerate().for_each(|(page_index, &state)| { + if state == FrameState::Allocated { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.mmap_replace_perm(uaddr, perm); + } + }); + } + + fn page_count(&self) -> usize { + self.states.len() + } + + fn size(&self) -> usize { + self.states.len() * arch::PGSIZE + } + + fn fork(&mut self, _self_pagetable: &RwLock, _fork_pagetable: &RwLock) -> Box { + let new_area = SharedFileMapArea { + entry: self.entry.clone(), + ubase: self.ubase, + offset: self.offset, + states: vec![FrameState::Unallocated; self.states.len()], + perm: self.perm, + inode_index: self.inode_index, + }; + + Box::new(new_area) + } + + fn translate_read(&mut self, uaddr: usize, _addrspace: &Arc) -> Option { + self.translate(uaddr) + } + + fn translate_write(&mut self, uaddr: usize, _addrspace: &Arc) -> Option { + self.translate(uaddr) + } + + fn try_to_fix_memory_fault( + &mut self, + uaddr: usize, + _access_type: MemAccessType, + addrspace: &Arc + ) -> bool { + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index >= self.states.len() { + return false; + } + + if self.states[page_index] == FrameState::Unallocated { + let kpage = self.entry.lock().get_page(page_index).expect("Failed to get page in try_to_fix_memory_fault"); + let mut pagetable = addrspace.pagetable().write(); + pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + kpage, + self.perm, + ); + self.states[page_index] = FrameState::Allocated; + } + + return true; + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + self.states.iter().enumerate().for_each(|(page_index, &state)| { + if state == FrameState::Allocated { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.munmap(uaddr); + } + }); + kinfo!("Unmapping SharedFileMapArea at ubase {:#x}", self.ubase); + MANAGER.close_mapped_file(self.inode_index); + } + + fn type_name(&self) -> &'static str { + "SharedFileMapArea" + } +} diff --git a/src/kernel/mm/maparea/filemap_.rs b/src/kernel/mm/maparea/filemap_.rs new file mode 100644 index 0000000..498a286 --- /dev/null +++ b/src/kernel/mm/maparea/filemap_.rs @@ -0,0 +1,391 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::boxed::Box; +use spin::RwLock; + +use crate::{kdebug, ktrace, kwarn}; +use crate::arch::{PageTable, PageTableTrait}; +use crate::arch; +use crate::kernel::errno::Errno; +use crate::kernel::mm::PhysPageFrame; +use crate::kernel::mm::maparea::area::Area; +use crate::kernel::mm::{AddrSpace, MapPerm, MemAccessType}; +use crate::fs::file::File; + +enum Frame { + Unallocated, + Allocated(Arc), + Cow(Arc), +} + +impl Frame { + pub fn is_unallocated(&self) -> bool { + matches!(self, Frame::Unallocated) + } +} + +pub struct FileMapArea { + ubase: usize, + perm: MapPerm, + + file: Arc, + file_offset: usize, + file_length: usize, + + frames: Vec, +} + +impl FileMapArea { + pub fn new( + ubase: usize, + perm: MapPerm, + file: Arc, + file_offset: usize, + file_length: usize + ) -> Self { + // File mapping areas should be page-aligned + debug_assert!(ubase % arch::PGSIZE == 0, "ubase should be page-aligned"); + debug_assert!(file_offset % arch::PGSIZE == 0, "file_offset should be page-aligned"); + + let page_count = (file_length + arch::PGSIZE - 1) / arch::PGSIZE; + let frames = Vec::from_iter((0..page_count).map(|_| Frame::Unallocated)); + Self { + ubase, + perm, + file, + file_offset, + file_length, + frames, + } + } + + fn load_page(&mut self, page_index: usize, pagetable: &RwLock) -> usize { + debug_assert!(page_index < self.frames.len()); + debug_assert!(self.frames[page_index].is_unallocated()); + + let area_offset = page_index * arch::PGSIZE; + let file_offset = self.file_offset + area_offset; + + let frame = PhysPageFrame::alloc_zeroed(); + + // Try to read from file, but only within the specified file_length + if area_offset < self.file_length { + let mut buffer = [0u8; arch::PGSIZE]; + // Calculate how much data we can read from this page: + // - Don't read beyond the file_length boundary + // - Don't read beyond one page + let length = core::cmp::min(self.file_length - area_offset, arch::PGSIZE); + + match self.file.read_at(&mut buffer[..length], file_offset) { + Ok(_) => { + frame.copy_from_slice(0, &buffer[..length]); + // ktrace!("Loaded page {} from file at offset {:#x}, length: {}", page_index, file_offset, length); + } + Err(_) => { + kwarn!("Failed to read from file at offset {:#x}", file_offset); + // Keep the page zeroed if file read fails + } + } + } + + let page = frame.get_page(); + + pagetable.write().mmap(self.ubase + area_offset, page, self.perm); + self.frames[page_index] = Frame::Allocated(Arc::new(frame)); + + page + } + + fn copy_on_write_page(&mut self, page_index: usize, pagetable: &RwLock) -> usize { + assert!(page_index < self.frames.len()); + + // Verify that the original mapping has write permission + if !self.perm.contains(MapPerm::W) { + panic!("Attempting copy-on-write on a non-writable mapping at page index {}", page_index); + } + + let area_offset = page_index * arch::PGSIZE; + let frame = match &self.frames[page_index] { + Frame::Cow(frame) => frame.copy(), + _ => panic!("Invalid type for copy-on-write"), + }; + + let page = frame.get_page(); + + pagetable.write().mmap_replace(self.ubase + area_offset, page, self.perm); + self.frames[page_index] = Frame::Allocated(Arc::new(frame)); + + // ktrace!("Copy-on-write triggered for file mapping page {} at address {:#x}", page_index, self.ubase + area_offset); + + page + } + + fn write_back_page(&self, page_index: usize) -> Result<(), crate::kernel::errno::Errno> { + if let Frame::Allocated(frame) = &self.frames[page_index] { + let area_offset = page_index * arch::PGSIZE; + let file_offset = self.file_offset + area_offset; + + // Only write back if this page is within the file_length + if area_offset < self.file_length { + // Calculate how much data we should write back: + // - Don't write beyond the file_length boundary + // - Don't write beyond one page + let length = core::cmp::min(self.file_length - area_offset, arch::PGSIZE); + let buffer = unsafe { + core::slice::from_raw_parts(frame.get_page() as *const u8, length) + }; + + // For now, we use the inode's writeat method directly + // In a real implementation, you might want to add write_at to File + self.file.write_at(buffer, file_offset)?; + ktrace!("Wrote back page {} to file at offset {:#x}, length: {}", page_index, file_offset, length); + } + } + Ok(()) + } + + pub fn sync(&self) -> Result<(), Errno> { + if !self.file.flags.writable { + return Ok(()); + } + + for (page_index, frame) in self.frames.iter().enumerate() { + if let Frame::Allocated(_) = frame { + self.write_back_page(page_index)?; + } + } + Ok(()) + } +} + +impl Area for FileMapArea { + fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get(page_index) { + let page = match page_frame { + Frame::Unallocated => { + // Lazy loading: load page from file on first access + self.load_page(page_index, addrspace.pagetable()) + } + Frame::Allocated(frame) => { + frame.get_page() + } + Frame::Cow(frame) => { + frame.get_page() + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + assert!(uaddr >= self.ubase); + + if !self.perm.contains(MapPerm::W) { + return None; + } + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let page_offset = (uaddr - self.ubase) % arch::PGSIZE; + + if let Some(page_frame) = self.frames.get_mut(page_index) { + let page = match page_frame { + Frame::Unallocated => { + // Lazy loading: load page from file on first write + self.load_page(page_index, addrspace.pagetable()) + } + Frame::Allocated(frame) => { + frame.get_page() + } + Frame::Cow(_) => { + // Copy-on-write: create a new copy for this process + self.copy_on_write_page(page_index, addrspace.pagetable()) + } + }; + + Some(page + page_offset) + } else { + None + } + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Box { + let cow_perm = self.perm - MapPerm::W; + + let mut pagetable = new_pagetable.write(); + let frames = self.frames.iter().enumerate().map(|(page_index, frame)| { + match frame { + Frame::Unallocated => Frame::Unallocated, + Frame::Allocated(frame) => { + pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + frame.get_page(), + cow_perm + ); + Frame::Cow(frame.clone()) + } + Frame::Cow(frame) => { + pagetable.mmap( + self.ubase + page_index * arch::PGSIZE, + frame.get_page(), + cow_perm + ); + Frame::Cow(frame.clone()) + }, + } + }).collect(); + + if self.perm.contains(MapPerm::W) { + // Update original mapping to be COW if it was writable + let mut self_pagetable = self_pagetable.write(); + self.frames.iter_mut().enumerate().for_each(|(page_index, frame)| { + if let Frame::Allocated(f) = frame { + self_pagetable.mmap_replace( + self.ubase + page_index * arch::PGSIZE, + f.get_page(), + cow_perm + ); + *frame = Frame::Cow(f.clone()); + } + }); + } + + let new_area = FileMapArea { + ubase: self.ubase, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset, + file_length: self.file_length, + frames, + }; + + Box::new(new_area) + } + + fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + assert!(uaddr >= self.ubase); + + if !self.perm.contains(MapPerm::W) && access_type == MemAccessType::Write { + return false; + } + + // ktrace!("Fixing memory fault for file mapping at address: {:#x}", uaddr); + + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + if page_index < self.frames.len() { + match &self.frames[page_index] { + Frame::Unallocated => { + // ktrace!("Fixing memory fault by loading file page at address: {:#x}, page index: {}", uaddr, page_index); + self.load_page(page_index, addrspace.pagetable()); + // ktrace!("Memory fault fixed by loading page at address: {:#x}", uaddr); + } + Frame::Allocated(_) => { + // Page is already allocated, this shouldn't happen + panic!("Memory fault on already allocated file page at address: {:#x}", uaddr); + } + Frame::Cow(_) => { + assert!(access_type == MemAccessType::Write, "Memory fault on CoW file page for read access at address: {:#x}", uaddr); + self.copy_on_write_page(page_index, addrspace.pagetable()); + } + } + + true + } else { + kdebug!("Memory fault at address {:#x} for file mapping, but page index {} is out of bounds", uaddr, page_index); + false + } + } + + fn ubase(&self) -> usize { + self.ubase + } + + fn set_ubase(&mut self, ubase: usize) { + self.ubase = ubase; + } + + fn page_count(&self) -> usize { + self.frames.len() + } + + fn split(mut self: Box, uaddr: usize) -> (Box, Box) { + assert!(uaddr % arch::PGSIZE == 0, "Split address must be page-aligned"); + assert!(uaddr > self.ubase, "Split address must be greater than ubase"); + assert!(uaddr < self.ubase + self.size(), "Split address out of bounds"); + + let split_index = (uaddr - self.ubase) / arch::PGSIZE; + let split_offset = split_index * arch::PGSIZE; + let remaining_frames = self.frames.split_off(split_index); + + // Calculate the new file_length for the second area + let new_file_length = if self.file_length > split_offset { + self.file_length - split_offset + } else { + 0 + }; + + // Update the file_length for the first area (self) + self.file_length = core::cmp::min(self.file_length, split_offset); + + let new_area = FileMapArea { + ubase: uaddr, + perm: self.perm, + file: self.file.clone(), + file_offset: self.file_offset + split_offset, + file_length: new_file_length, + frames: remaining_frames, + }; + + (self, Box::new(new_area)) + } + + fn set_perm(&mut self, perm: MapPerm, pagetable: &RwLock) { + self.perm = perm; + + // Update page table permissions for all allocated pages + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter().enumerate() { + if let Frame::Allocated(frame) = frame { + let vaddr = self.ubase + page_index * arch::PGSIZE; + let paddr = frame.get_page(); + pagetable.mmap_replace(vaddr, paddr, perm); + ktrace!("Updated page table permissions for file mapping page at {:#x} to {:?}", vaddr, perm); + } + } + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter_mut().enumerate() { + if let Frame::Allocated(_) | Frame::Cow(_) = frame { + let uaddr = self.ubase + page_index * arch::PGSIZE; + pagetable.munmap(uaddr); + } + *frame = Frame::Unallocated; + } + } + + fn type_name(&self) -> &'static str { + "FileMapArea" + } +} + +impl Drop for FileMapArea { + fn drop(&mut self) { + // Write back all dirty pages when the mapping is dropped + if let Err(_) = self.sync() { + ktrace!("Failed to sync file mapping during drop"); + } + } +} diff --git a/src/kernel/mm/maparea/manager.rs b/src/kernel/mm/maparea/manager.rs new file mode 100644 index 0000000..652249c --- /dev/null +++ b/src/kernel/mm/maparea/manager.rs @@ -0,0 +1,481 @@ +use alloc::collections::BTreeMap; +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec::Vec; +use spin::RwLock; + +use crate::kernel::config; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::mm::maparea::anonymous::AnonymousArea; +use crate::kernel::mm::{AddrSpace, MapPerm, MemAccessType}; +use crate::arch::{self, PageTable}; +use crate::{ktrace, print}; + +use super::area::Area; +use super::userstack::{UserStack, Auxv}; +use super::userbrk::UserBrk; + +pub struct Manager { + areas: BTreeMap>, + userstack_ubase: usize, + userbrk: UserBrk, +} + +impl Manager { + pub fn new() -> Self { + Self { + areas: BTreeMap::new(), + userstack_ubase: 0, + userbrk: UserBrk::new() + } + } + + pub fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Manager { + let new_areas = self.areas.iter_mut().map(|(ubase, area)| { + (*ubase, area.fork(self_pagetable, new_pagetable)) + }).collect(); + + Self { + areas: new_areas, + userstack_ubase: self.userstack_ubase, + userbrk: self.userbrk.clone() + } + } + + /// Find a suitable virtual address for mmap allocation + /// + /// Searches for a contiguous region of virtual memory that can accommodate + /// the requested number of pages, starting from USER_MAP_BASE. + /// + /// # Arguments + /// * `page_count` - Number of pages to allocate + /// + /// # Returns + /// * `Some(usize)` - Base address for the allocation if found + /// * `None` - If no suitable address space is available + pub fn find_mmap_ubase(&self, page_count: usize) -> Option { + if page_count == 0 { + return None; + } + + let required_size = page_count * arch::PGSIZE; + let mut candidate_addr = config::USER_MAP_BASE; + + // Ensure candidate address is page-aligned + candidate_addr = (candidate_addr + arch::PGSIZE - 1) & !(arch::PGSIZE - 1); + + // Iterate through existing areas in ascending order to find a gap + for (&area_base, area) in &self.areas { + let area_end = area_base + area.size(); + + // Check if candidate address is before this area and has enough space + if candidate_addr + required_size <= area_base { + // Found a suitable gap before this area + // ktrace!("Found mmap address {:#x} for {} pages (gap before area at {:#x})", + // candidate_addr, page_count, area_base); + return Some(candidate_addr); + } + + // If candidate overlaps with or is too close to this area, + // move to after this area + if candidate_addr < area_end { + candidate_addr = area_end; + // Re-align to page boundary + candidate_addr = (candidate_addr + arch::PGSIZE - 1) & !(arch::PGSIZE - 1); + } + } + + // Check if there's space after all existing areas + // We need to ensure we don't exceed reasonable address space limits + // For safety, let's limit to addresses below the user stack region + let max_mmap_addr = if self.userstack_ubase > 0 { + self.userstack_ubase + } else { + config::USER_STACK_TOP - config::USER_STACK_PAGE_COUNT_MAX * arch::PGSIZE + }; + + if candidate_addr + required_size <= max_mmap_addr { + ktrace!("Found mmap address {:#x} for {} pages (after all areas)", + candidate_addr, page_count); + Some(candidate_addr) + } else { + ktrace!("No suitable mmap address found for {} pages (would exceed limit {:#x})", + page_count, max_mmap_addr); + None + } + } + + pub fn is_map_range_overlapped(&self, uaddr: usize, page_count: usize) -> bool { + if page_count == 0 { + return false; + } + + let end_addr = uaddr.saturating_add(page_count * arch::PGSIZE); + + // Previous area (if any) might extend into the new range. + if let Some((area_base, area)) = self.areas.range(..=uaddr).next_back() { + let area_end = area_base.saturating_add(area.size()); + if uaddr < area_end { + return true; + } + } + + // Next area is the only other candidate because areas are non-overlapping and ordered. + if let Some((area_base, _)) = self.areas.range(uaddr..).next() { + if *area_base < end_addr { + return true; + } + } + + false + } + + pub fn map_area(&mut self, uaddr: usize, area: Box) { + debug_assert!(uaddr % arch::PGSIZE == 0, "uaddr should be page-aligned"); + debug_assert!(!self.is_map_range_overlapped(uaddr, area.page_count()), "Address range is not free"); + self.areas.insert(uaddr, area); + } + + fn find_overlapped_areas(&self, start: usize, end: usize) -> Vec { + let mut overlapped_areas = Vec::new(); + let iter_start = self.areas.range(..=start).next().map(|(k, _)| *k).unwrap_or(0); + + for (&area_base, area) in self.areas.range(iter_start..) { + if end <= area_base { + break; + } + + if area_base + area.size() <= start { + continue; + } + + overlapped_areas.push(area_base); + } + + overlapped_areas + } + + /// Map an area at a fixed address, handling any overlapping areas + /// + /// This function maps a new area at the specified address, automatically handling + /// any existing areas that overlap with the new area's range. Overlapping areas + /// will be split and/or removed as necessary. + /// + /// # Arguments + /// * `uaddr` - Base address where the area should be mapped (must be page-aligned) + /// * `area` - The area to be mapped + /// + /// # Returns + /// * `Ok(())` - Success + /// * `Err(Errno)` - Error during mapping or area splitting + /// + /// # Behavior + /// - If existing areas completely overlap with the new area, they are removed + /// - If existing areas partially overlap, they are split to preserve non-overlapping parts + /// - The new area is then inserted at the specified address + /// + /// # Examples + /// ``` + /// // Map a new area that may overlap with existing mappings + /// let new_area = Box::new(AnonymousArea::new(addr, perm, page_count)); + /// manager.map_area_fixed(addr, new_area)?; + /// ``` + #[allow(dead_code)] + pub fn map_area_fixed(&mut self, uaddr: usize, area: Box, pagetable: &RwLock) { + assert!(uaddr % arch::PGSIZE == 0, "uaddr should be page-aligned"); + + let new_area_end = uaddr + area.size(); + + // Find all areas that overlap with the new area's range + // let mut overlapping_areas = Vec::new(); + + for overlapping_base in self.find_overlapped_areas(uaddr, new_area_end) { + let mut middle = self.areas.remove(&overlapping_base).unwrap(); + let overlapping_end = overlapping_base + middle.size(); + + // KEEP Left part [overlapping_base, uaddr) + if overlapping_base < uaddr { + let left; + (left, middle) = middle.split(uaddr); + self.areas.insert(overlapping_base, left); + } + + // KEEP Right part [new_area_end, overlapping_end) + if new_area_end < overlapping_end { + let right; + (middle, right) = middle.split(new_area_end); + self.areas.insert(new_area_end, right); + } + + // UNMAP Middle part [max(overlapping_base, uaddr), min(overlapping_end, new_area_end)) + middle.unmap(pagetable); + } + + // Now we can safely insert the new area + self.areas.insert(uaddr, area); + } + + pub fn unmap_area(&mut self, uaddr: usize, page_count: usize, pagetable: &RwLock) -> SysResult<()> { + debug_assert!(uaddr % arch::PGSIZE == 0, "uaddr should be page-aligned"); + debug_assert!(page_count > 0, "page_count should be greater than 0"); + + let uaddr_end = uaddr + page_count * arch::PGSIZE; + + // Process each intersecting area + for area_base in self.find_overlapped_areas(uaddr, uaddr_end) { + // Remove the area from the map + let mut middle = self.areas.remove(&area_base).unwrap(); + let area_end = area_base + middle.size(); + + // KEEP Left part [area_base, uaddr) + if area_base < uaddr { + let left; + (left, middle) = middle.split(uaddr); + self.areas.insert(area_base, left); + } + + // KEEP Right part [uaddr_end, area_end) + if uaddr_end < area_end { + let right; + (middle, right) = middle.split(uaddr_end); + self.areas.insert(uaddr_end, right); + } + + // UNMAP Middle part [max(area_base, uaddr), min(area_end, uaddr_end)) + middle.unmap(pagetable); + } + + Ok(()) + } + + /// Set permissions for a specific range of pages + /// + /// Changes the permissions for pages in the range [uaddr, uaddr + page_count * PGSIZE). + /// If the range spans part of an existing area, the area will be split to accommodate + /// the permission change. + /// + /// # Arguments + /// * `uaddr` - Start address (must be page-aligned) + /// * `page_count` - Number of pages to modify + /// * `perm` - New permissions to apply + /// * `pagetable` - Page table to update + /// + /// # Returns + /// * `Ok(())` - Success + /// * `Err(Errno)` - Error (e.g., invalid address range, no mapping found) + pub fn set_map_area_perm(&mut self, uaddr: usize, page_count: usize, perm: MapPerm, pagetable: &RwLock) -> Result<(), Errno> { + debug_assert!(uaddr % arch::PGSIZE == 0, "uaddr must be page-aligned"); + + if page_count == 0 { + return Ok(()); + } + + let uaddr_end = uaddr + page_count * arch::PGSIZE; + + for overlapped_base in self.find_overlapped_areas(uaddr, uaddr_end) { + let mut middle = self.areas.remove(&overlapped_base).unwrap(); + let overlapped_end = overlapped_base + middle.size(); + + if overlapped_base < uaddr { + let left; + (left, middle) = middle.split(uaddr); + self.areas.insert(overlapped_base, left); + } + + if uaddr_end < overlapped_end { + let right; + (middle, right) = middle.split(uaddr_end); + self.areas.insert(uaddr_end, right); + } + + middle.set_perm(perm, pagetable); + self.areas.insert(middle.ubase(), middle); + } + + Ok(()) + } + + /// Split an area and set permissions for the specified range + // fn split_and_set_perm(&mut self, area_base: usize, range_start: usize, range_end: usize, perm: MapPerm, pagetable: &RwLock) -> Result<(), Errno> { + // // Remove the original area from the map + // let mut original_area = self.areas.remove(&area_base) + // .ok_or(Errno::EFAULT)?; + + // let area_end = area_base + original_area.size(); + + // // Special case: if the range covers the entire area, just change permissions + // if range_start == area_base && range_end == area_end { + // ktrace!("Range covers entire area, just changing permissions"); + // original_area.set_perm(perm, pagetable); + // self.areas.insert(area_base, original_area); + // return Ok(()); + // } + + // // Case 1: Range starts at area beginning but doesn't cover the whole area + // if range_start == area_base && range_end < area_end { + // ktrace!("Range starts at area beginning, splitting at end"); + // let right_area = original_area.split(range_end); + // // original_area now covers [area_base, range_end) + // original_area.set_perm(perm, pagetable); + // self.areas.insert(area_base, original_area); + // self.areas.insert(range_end, right_area); + // return Ok(()); + // } + + // // Case 2: Range ends at area end but doesn't start at area beginning + // if range_start > area_base && range_end == area_end { + // ktrace!("Range ends at area end, splitting at start"); + // let mut right_area = original_area.split(range_start); + // // original_area now covers [area_base, range_start) + // // right_area covers [range_start, area_end) + // right_area.set_perm(perm, pagetable); + // self.areas.insert(area_base, original_area); + // self.areas.insert(range_start, right_area); + // return Ok(()); + // } + + // // Case 3: Range is in the middle of the area - need two splits + // if range_start > area_base && range_end < area_end { + // ktrace!("Range is in middle, need two splits"); + // // First split at range_end to get the right part + // let right_area = original_area.split(range_end); + // // Now original_area covers [area_base, range_end) + + // // Second split at range_start to get the middle part + // let mut middle_area = original_area.split(range_start); + // // Now original_area covers [area_base, range_start) + // // middle_area covers [range_start, range_end) + + // middle_area.set_perm(perm, pagetable); + // self.areas.insert(area_base, original_area); // left part + // self.areas.insert(range_start, middle_area); // middle part (new perms) + // self.areas.insert(range_end, right_area); // right part + // return Ok(()); + // } + + // unreachable!(); + // } + + pub fn create_user_stack(&mut self, argv: &[&str], envp: &[&str], auxv: &Auxv, addrspace: &AddrSpace) -> Result { + assert!(self.userstack_ubase == 0, "User stack already created"); + + let mut userstack = Box::new(UserStack::new()); + let ubase = config::USER_STACK_TOP - config::USER_STACK_PAGE_COUNT_MAX * arch::PGSIZE; + + let top = userstack.push_argv_envp_auxv(argv, envp, auxv, addrspace)?; + + self.map_area(ubase, userstack as Box); + self.userstack_ubase = ubase; + + Ok(top) + } + + pub fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + if let Some((_, area)) = self.areas.range_mut(..=uaddr).next_back() { + area.translate_read(uaddr, addrspace) + } else { + None + } + } + + pub fn translate_write(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + if let Some((_, area)) = self.areas.range_mut(..=uaddr).next_back() { + area.translate_write(uaddr, addrspace) + } else { + None + } + } + + pub fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + if let Some((_ubase, area)) = self.areas.range_mut(..=uaddr).next_back() { + if !access_type.match_perm(area.perm()) { + return false; + } + // kinfo!("Trying to fix memory fault at address {:#x} with access type {:?}", uaddr, access_type); + area.try_to_fix_memory_fault(uaddr, access_type, addrspace) + } else { + false + } + } + + pub fn increase_userbrk(&mut self, new_ubrk: usize) -> Result { + if new_ubrk == 0 { + return Ok(self.userbrk.ubrk); + } + + if new_ubrk < self.userbrk.ubrk { + return Ok(self.userbrk.ubrk); // Do not support shrinking brk for simplicity + } + + let new_page_count = (new_ubrk - config::USER_BRK_BASE + arch::PGSIZE - 1) / arch::PGSIZE; + + if new_page_count > self.userbrk.page_count { + let ubase = config::USER_BRK_BASE + self.userbrk.page_count * arch::PGSIZE; + let new_area = Box::new(AnonymousArea::new( + ubase, + MapPerm::R | MapPerm::W | MapPerm::U, + new_page_count - self.userbrk.page_count, + false + )); + + self.map_area(ubase, new_area); + + self.userbrk.page_count = new_page_count; + } + + self.userbrk.ubrk = new_ubrk; + + Ok(self.userbrk.ubrk) + } + + /// Print all mapped areas for debugging purposes + /// + /// Outputs information about each mapped area including: + /// - Base address and size + /// - Area type + /// - Address range + /// Print information about all mapped areas for debugging + /// + /// This function outputs details about all currently mapped areas to help + /// with debugging memory management issues. + #[allow(dead_code)] + pub fn print_all_areas(&self) { + print!("=== Memory Area Manager Status ===\n"); + print!("Total areas: {}\n", self.areas.len()); + print!("User stack base: {:#x}\n", self.userstack_ubase); + print!("User brk: {:#x} (page count: {})\n", self.userbrk.ubrk, self.userbrk.page_count); + print!("\n"); + + if self.areas.is_empty() { + print!("No mapped areas\n"); + } else { + print!("Mapped areas:\n"); + for (index, (&base_addr, area)) in self.areas.iter().enumerate() { + let end_addr = base_addr + area.size(); + print!(" {}. {} [{:#x}, {:#x}) - {} bytes\n", + index + 1, + area.type_name(), + base_addr, + end_addr, + area.size()); + } + } + print!("=== End Memory Area Status ===\n"); + } + + /// Check if a given address range is mapped + pub fn is_range_mapped(&self, uaddr: usize, size: usize) -> bool { + let end_addr = uaddr + size; + + for (&area_base, area) in &self.areas { + let area_end = area_base + area.size(); + + // Check if the range overlaps with this area + if area_base < end_addr && area_end > uaddr { + return true; + } + } + + false + } +} diff --git a/src/kernel/mm/maparea/mod.rs b/src/kernel/mm/maparea/mod.rs new file mode 100644 index 0000000..c288274 --- /dev/null +++ b/src/kernel/mm/maparea/mod.rs @@ -0,0 +1,17 @@ +mod area; +mod nofilemap; +mod anonymous; +mod elf; +mod filemap; +mod userstack; +mod userbrk; +mod manager; +pub mod shm; + +pub use manager::Manager; +pub use area::Area; +pub use elf::ELFArea; +pub use anonymous::AnonymousArea; +pub use filemap::{PrivateFileMapArea, SharedFileMapArea}; +pub use userstack::{Auxv, AuxKey}; +pub use shm::ShmArea; diff --git a/src/kernel/mm/maparea/nofilemap.rs b/src/kernel/mm/maparea/nofilemap.rs new file mode 100644 index 0000000..897996e --- /dev/null +++ b/src/kernel/mm/maparea/nofilemap.rs @@ -0,0 +1,93 @@ +use alloc::sync::Arc; +use crate::kernel::mm::AddrSpace; + +cfg_if::cfg_if! { + if #[cfg(feature="swap-memory")] { + use crate::kernel::mm::swappable; + pub use swappable::SwappableNoFileFrame; + } else { + use crate::kernel::mm::PhysPageFrame; + #[derive(Debug)] + pub struct SwappableNoFileFrame { + frame: PhysPageFrame, + uaddr: usize, + } + + impl SwappableNoFileFrame { + pub fn alloc_zeroed(uaddr: usize, _addrspace: &AddrSpace) -> (Self, usize) { + let frame = PhysPageFrame::alloc_zeroed(); + let kpage = frame.get_page(); + (Self { frame, uaddr}, kpage) + } + + pub fn allocated(uaddr:usize, frame: PhysPageFrame, _addrspace: &AddrSpace) -> Self { + Self { frame, uaddr } + } + + pub fn uaddr(&self) -> usize { + self.uaddr + } + + pub fn copy(&self, _addrspace: &AddrSpace) -> (Self, usize) { + let new_frame = self.frame.copy(); + let kpage = new_frame.get_page(); + (Self { frame: new_frame, uaddr: self.uaddr }, kpage) + } + + pub fn get_page(&self) -> Option { + Some(self.frame.get_page()) + } + + /// Get the kpage, if swapped out, swap in first + pub fn get_page_swap_in(&self) -> usize { + self.frame.get_page() + } + + pub fn is_swapped_out(&self) -> bool { + false + } + } + } +} + +pub enum FrameState { + Unallocated, + Allocated(Arc), + Cow(Arc), +} + +impl FrameState { + pub fn is_unallocated(&self) -> bool { + matches!(self, FrameState::Unallocated) + } + + pub fn is_cow(&self) -> bool { + matches!(self, FrameState::Cow(_)) + } + + pub fn cow_to_allocated(&mut self, addrspace: &AddrSpace) -> usize { + let t = core::mem::replace(self, FrameState::Unallocated); + match t { + FrameState::Cow(frame) => { + match Arc::try_unwrap(frame) { + Ok(f) => { + let kpage = f.get_page_swap_in(); + *self = FrameState::Allocated(Arc::new(f)); + kpage + } + Err(f) => { + let (new_frame, kpage) = f.copy(addrspace); + *self = FrameState::Allocated(Arc::new(new_frame)); + kpage + } + } + } + _ => unreachable!(), + } + } + + pub fn allocate(uaddr: usize, addrspace: &AddrSpace) -> (FrameState, usize) { + let (frame, kpage) = SwappableNoFileFrame::alloc_zeroed(uaddr, addrspace); + (FrameState::Allocated(Arc::new(frame)), kpage) + } +} diff --git a/src/kernel/mm/maparea/shm.rs b/src/kernel/mm/maparea/shm.rs new file mode 100644 index 0000000..5ba9545 --- /dev/null +++ b/src/kernel/mm/maparea/shm.rs @@ -0,0 +1,96 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::RwLock; + +use crate::kernel::mm::{AddrSpace, MapPerm, MemAccessType}; +use crate::arch::{self, PageTable, PageTableTrait}; +use crate::kernel::ipc::shm::ShmFrames; + +use super::Area; + +pub struct ShmArea { + ubase: usize, + frames: Arc, + perm: MapPerm, +} + +impl ShmArea { + pub fn new(ubase: usize, frames: Arc, perm: MapPerm) -> Self { + Self { + ubase, + frames, + perm, + } + } +} + +impl Area for ShmArea { + fn translate_read(&mut self, uaddr: usize, _addrspace: &Arc) -> Option { + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let frames = self.frames.frames.lock(); + if page_index < frames.len() { + Some(frames[page_index].get_page()) + } else { + None + } + } + + fn translate_write(&mut self, uaddr: usize, _addrspace: &Arc) -> Option { + self.translate_read(uaddr, _addrspace) + } + + // fn page_frames(&mut self) -> &mut [Frame] { + // unimplemented!("ShmArea does not support page_frames") + // } + + fn ubase(&self) -> usize { + self.ubase + } + + fn perm(&self) -> MapPerm { + self.perm + } + + fn page_count(&self) -> usize { + self.frames.frames.lock().len() + } + + fn fork(&mut self, _self_pagetable: &RwLock, _fork_pagetable: &RwLock) -> Box { + Box::new(ShmArea { + ubase: self.ubase, + frames: self.frames.clone(), + perm: self.perm, + }) + } + + fn try_to_fix_memory_fault(&mut self, uaddr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + let page_index = (uaddr - self.ubase) / arch::PGSIZE; + let frames = self.frames.frames.lock(); + if page_index >= frames.len() { + return false; + } + + if access_type == MemAccessType::Write && !self.perm.contains(MapPerm::W) { + return false; + } + if access_type == MemAccessType::Read && !self.perm.contains(MapPerm::R) { + return false; + } + + let frame = &frames[page_index]; + addrspace.pagetable().write().mmap(uaddr & !arch::PGMASK, frame.get_page(), self.perm); + true + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pt = pagetable.write(); + let frames = self.frames.frames.lock(); + for i in 0..frames.len() { + pt.munmap(self.ubase + i * arch::PGSIZE); + } + } + + fn type_name(&self) -> &'static str { + "ShmArea" + } +} diff --git a/src/kernel/mm/maparea/userbrk.rs b/src/kernel/mm/maparea/userbrk.rs new file mode 100644 index 0000000..729b8c3 --- /dev/null +++ b/src/kernel/mm/maparea/userbrk.rs @@ -0,0 +1,16 @@ +use crate::kernel::config; + +#[derive(Clone)] +pub struct UserBrk { + pub page_count: usize, + pub ubrk: usize +} + +impl UserBrk { + pub fn new() -> Self { + UserBrk { + page_count: 0, + ubrk: config::USER_BRK_BASE + } + } +} diff --git a/src/kernel/mm/maparea/userstack.rs b/src/kernel/mm/maparea/userstack.rs new file mode 100644 index 0000000..0f62494 --- /dev/null +++ b/src/kernel/mm/maparea/userstack.rs @@ -0,0 +1,411 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::boxed::Box; +use spin::RwLock; + +use crate::kernel::config; +use crate::kernel::mm::maparea::area::Area; +use crate::kernel::mm::{MemAccessType, AddrSpace}; +use crate::kernel::mm::MapPerm; +use crate::kernel::errno::Errno; +use crate::arch::{PageTable, PageTableTrait}; +use crate::arch; + +use super::nofilemap::FrameState; + +#[cfg(feature="swap-memory")] +use super::nofilemap::SwappableNoFileFrame; + +pub enum AuxKey { + _NULL = 0, + _IGNORE = 1, + _EXECFD = 2, + PHDR = 3, + PHENT = 4, + PHNUM = 5, + PAGESZ = 6, + BASE = 7, + FLAGS = 8, + ENTRY = 9, + _NOTELF = 10, + _UID = 11, + _EUID = 12, + _GID = 13, + _EGID = 14, + RANDOM = 25, +} + +const AUX_MAX: usize = 12; + +pub struct Auxv { + pub auxv: [usize; AUX_MAX * 2], + pub length: usize, +} + +impl Auxv { + pub fn new() -> Self { + Auxv { + auxv: [0; AUX_MAX * 2], + length: 0, + } + } + + pub fn push(&mut self, key: AuxKey, value: usize) { + debug_assert!(self.length < AUX_MAX, "Auxiliary vector is full, cannot add more entries."); + self.auxv[self.length * 2] = key as usize; + self.auxv[self.length * 2 + 1] = value; + self.length += 1; + } +} + +pub struct UserStack { + frames: Vec, +} + +impl UserStack { + pub fn new() -> Self { + let frames = Vec::from_iter((0..config::USER_STACK_PAGE_COUNT_MAX).map(|_| FrameState::Unallocated)); + Self { + frames, + } + } + + fn get_max_page_count(&self) -> usize { + self.frames.len() + } + + fn allocate_page(&mut self, page_index: usize, pagetable: &mut PageTable, addrspace: &AddrSpace) -> usize { + debug_assert!(page_index < self.get_max_page_count(), "Page index out of bounds: {}", page_index); + debug_assert!(self.frames[page_index].is_unallocated(), "Page at index {} is already allocated", page_index); + + let uaddr = config::USER_STACK_TOP - (page_index + 1) * arch::PGSIZE; + let (allocated, kpage) = FrameState::allocate(uaddr, addrspace); + + pagetable.mmap( + uaddr, + kpage, + MapPerm::R | MapPerm::W | MapPerm::U, + ); + + self.frames[page_index] = allocated; + + kpage + } + + fn copy_on_write_page(&mut self, page_index: usize, addrspace: &AddrSpace) -> usize { + debug_assert!(page_index < self.get_max_page_count(), "Page index out of bounds: {}", page_index); + debug_assert!(self.frames[page_index].is_cow(), "Page at index {} is not allocated", page_index); + + let kpage = self.frames[page_index].cow_to_allocated(addrspace); + + addrspace.pagetable().write().mmap_replace( + config::USER_STACK_TOP - (page_index + 1) * arch::PGSIZE, + kpage, + MapPerm::R | MapPerm::W | MapPerm::U, + ); + + kpage + } + + #[cfg(feature = "swap-memory")] + fn handle_memory_fault_on_swapped_allocated(&self, frame: &SwappableNoFileFrame, addrspace: &AddrSpace) { + debug_assert!(frame.is_swapped_out(), "Frame is not swapped out"); + let kpage = frame.get_page_swap_in(); + addrspace.pagetable().write().mmap( + frame.uaddr(), + kpage, + MapPerm::R | MapPerm::W | MapPerm::U, + ); + } + + #[cfg(feature = "swap-memory")] + fn handle_cow_read_swapped_out(&self, frame: &SwappableNoFileFrame, addrspace: &AddrSpace) { + debug_assert!(frame.is_swapped_out(), "Frame is not swapped out"); + let kpage = frame.get_page_swap_in(); + addrspace.pagetable().write().mmap( + frame.uaddr(), + kpage, + MapPerm::R | MapPerm::U, + ); + } + + fn push_buffer(&mut self, top: &mut usize, buffer: &[u8], pagetable: &mut PageTable, addrspace: &AddrSpace) -> Result<(), Errno> { + let total_len = buffer.len(); + let new_top = *top - total_len; + let mut uaddr = new_top; + + let mut copied = 0usize; + let mut remaining = buffer.len(); + while remaining != 0 { + let page_offset = uaddr & arch::PGMASK; + let to_copy = core::cmp::min(arch::PGSIZE - page_offset, remaining); + + let page_index = (config::USER_STACK_TOP - uaddr - 1) / arch::PGSIZE; + + if self.frames[page_index].is_unallocated() { + self.allocate_page(page_index, pagetable, addrspace); + } + + match &self.frames[page_index] { + FrameState::Allocated(frame) => { + let pa = frame.get_page_swap_in(); + let dst = unsafe { core::slice::from_raw_parts_mut((pa + page_offset) as *mut u8, to_copy) }; + dst.copy_from_slice(&buffer[copied..copied + to_copy]); + } + _ => panic!("Page at index {} is not allocated", page_index), + } + + uaddr += to_copy; + copied += to_copy; + remaining -= to_copy; + } + *top = new_top; + + Ok(()) + } + + fn push_c_str(&mut self, top: &mut usize, s: &str, pagetable: &mut PageTable, addrspace: &AddrSpace) -> Result<(), Errno> { + self.push_usize(top, 0, pagetable, addrspace)?; + self.push_buffer(top, s.as_bytes(), pagetable, addrspace) + } + + fn push_usize(&mut self, top: &mut usize, value: usize, pagetable: &mut PageTable, addrspace: &AddrSpace) -> Result<(), Errno> { + // *top &= !(core::mem::size_of::() - 1); + self.push_buffer(top, &value.to_le_bytes(), pagetable, addrspace) + } + + fn push_auxv(&mut self, top: &mut usize, auxv: &Auxv, pagetable: &mut PageTable, addrspace: &AddrSpace) -> Result<(), Errno> { + self.push_usize(top, 0, pagetable, addrspace)?; + + if auxv.length == 0 { + return Ok(()); + } + + let buffer = unsafe { + core::slice::from_raw_parts(auxv.auxv.as_ptr() as *const u8, auxv.length * 2 * core::mem::size_of::()) + }; + self.push_buffer(top, &buffer, pagetable, addrspace)?; + + Ok(()) + } + + /* + HIGH + +------------------+ <- config::USER_STACK_TOP + | strings | + +------------------+ + | envp[n] = NULL | + | envp[n-1] | + | ... | + | envp[0] | + +------------------+ + | argv[argc] = NULL| + | argv[argc-1] | + | ... | + | argv[0] | + +------------------+ <- user_stack_top + sizeof(usize) + | argc | + +------------------+ <- user_stack_top + LOW + */ + /// Push arguments and environment variables onto the user stack. + pub fn push_argv_envp_auxv(&mut self, argv: &[&str], envp: &[&str], auxv: &Auxv, addrspace: &AddrSpace) -> Result { + let mut pagetable = addrspace.pagetable().write(); + let mut top = config::USER_STACK_TOP; + + let mut envp_ptrs = Vec::with_capacity(envp.len()); + for &env in envp.iter() { + self.push_c_str(&mut top, env, &mut pagetable, addrspace)?; + envp_ptrs.push(top); + } + + let mut argv_ptrs = Vec::with_capacity(argv.len()); + for &arg in argv.iter() { + self.push_c_str(&mut top, arg, &mut pagetable, addrspace)?; + argv_ptrs.push(top); + } + + self.push_auxv(&mut top, auxv, &mut pagetable, addrspace)?; + + self.push_usize(&mut top, 0, &mut pagetable, addrspace)?; + for &addr in envp_ptrs.iter().rev() { + self.push_usize(&mut top, addr, &mut pagetable, addrspace)?; + } + + self.push_usize(&mut top, 0, &mut pagetable, addrspace)?; + for &addr in argv_ptrs.iter().rev() { + self.push_usize(&mut top, addr, &mut pagetable, addrspace)?; + } + + self.push_usize(&mut top, argv.len(), &mut pagetable, addrspace)?; + + Ok(top) + } +} + +impl Area for UserStack { + fn translate_read(&mut self, uaddr: usize, addrspace: &Arc) -> Option { + let page_index = (config::USER_STACK_TOP - uaddr - 1) / arch::PGSIZE; + if page_index < self.get_max_page_count() { + let page = match &self.frames[page_index] { + FrameState::Unallocated => { + self.allocate_page(page_index, &mut addrspace.pagetable().write(), addrspace) + } + FrameState::Allocated(frame) | FrameState::Cow(frame) => frame.get_page_swap_in(), + }; + + Some(page + uaddr % arch::PGSIZE) + } else { + None + } + } + + fn translate_write(&mut self, vaddr: usize, addrspace: &Arc) -> Option { + let page_index = (config::USER_STACK_TOP - vaddr - 1) / arch::PGSIZE; + if page_index < self.get_max_page_count() { + let page = match &self.frames[page_index] { + FrameState::Unallocated => { + let mut pagetable = addrspace.pagetable().write(); + self.allocate_page(page_index, &mut pagetable, addrspace) + } + FrameState::Allocated(frame) => frame.get_page_swap_in(), + FrameState::Cow(_) => { + self.copy_on_write_page(page_index, addrspace) + } + }; + + Some(page + vaddr % arch::PGSIZE) + } else { + None + } + } + + fn perm(&self) -> MapPerm { + MapPerm::R | MapPerm::W | MapPerm::U + } + + fn fork(&mut self, self_pagetable: &RwLock, new_pagetable: &RwLock) -> Box { + let mut new_pagetable = new_pagetable.write(); + + let new_frames = self.frames.iter().enumerate().map(|(page_index, frame)| { + match frame { + FrameState::Unallocated => FrameState::Unallocated, + FrameState::Allocated(frame) | FrameState::Cow(frame) => { + if let Some(kpage) = frame.get_page() { + new_pagetable.mmap( + config::USER_STACK_TOP - (page_index + 1) * arch::PGSIZE, + kpage, + MapPerm::R | MapPerm::U + ); + } + FrameState::Cow(frame.clone()) + } + } + }).collect(); + + let mut self_pagetable = self_pagetable.write(); + self.frames.iter_mut().enumerate().for_each(|(index, frame)| { + *frame = match frame { + FrameState::Unallocated => { + FrameState::Unallocated + } + FrameState::Allocated(frame) | FrameState::Cow(frame) => { + if !frame.is_swapped_out() { + self_pagetable.mmap_replace_perm( + config::USER_STACK_TOP - (index + 1) * arch::PGSIZE, + MapPerm::R | MapPerm::U + ); + } + FrameState::Cow(frame.clone()) + } + } + }); + + Box::new(UserStack { + frames: new_frames, + }) + } + + fn try_to_fix_memory_fault(&mut self, addr: usize, access_type: MemAccessType, addrspace: &Arc) -> bool { + // ktrace!("UserStack::try_to_fix_memory_fault: addr={:#x}, access_type={:?}, frames={:x?}", addr, access_type, self.frames); + + if addr >= config::USER_STACK_TOP { + return false; + } + + if access_type == MemAccessType::Execute { + return false; + } + + let page_index = (config::USER_STACK_TOP - addr - 1) / arch::PGSIZE; + + if page_index >= self.get_max_page_count() { + return false; + } + + match &self.frames[page_index] { + FrameState::Allocated(frame) => { + #[cfg(feature = "swap-memory")] + self.handle_memory_fault_on_swapped_allocated(frame, addrspace); + #[cfg(not(feature = "swap-memory"))] + { + let _ = frame; + panic!("Page at index {} is already allocated, addr={:#x}, flags={:?}", page_index, addr, addrspace.pagetable().read().mapped_flag(addr)); + } + } + FrameState::Cow(frame) => { + if access_type != MemAccessType::Write { + // If it's a read fault on a COW page, it might be swapped out + #[cfg(feature = "swap-memory")] + self.handle_cow_read_swapped_out(frame, addrspace); + #[cfg(not(feature = "swap-memory"))] + { + let _ = frame; + panic!("Access type is not write for COW page at index {}, addr={:#x}, flags={:?}", page_index, addr, addrspace.pagetable().read().mapped_flag(addr)); + } + } + self.copy_on_write_page(page_index, addrspace); + } + FrameState::Unallocated => { + let mut pagetable = addrspace.pagetable().write(); + self.allocate_page(page_index, &mut pagetable, addrspace); + } + } + + true + } + + fn ubase(&self) -> usize { + config::USER_STACK_TOP - self.get_max_page_count() * arch::PGSIZE + } + + fn page_count(&self) -> usize { + self.get_max_page_count() + } + + fn unmap(&mut self, pagetable: &RwLock) { + let mut pagetable = pagetable.write(); + for (page_index, frame) in self.frames.iter_mut().enumerate() { + if !frame.is_unallocated() { + let uaddr = config::USER_STACK_TOP - (page_index + 1) * arch::PGSIZE; + + #[cfg(feature = "swap-memory")] + let is_mapped = match frame { + FrameState::Allocated(f) | FrameState::Cow(f) => !f.is_swapped_out(), + _ => false, + }; + #[cfg(not(feature = "swap-memory"))] + let is_mapped = true; + if is_mapped { + pagetable.munmap(uaddr); + } + + *frame = FrameState::Unallocated; + } + } + } + + fn type_name(&self) -> &'static str { + "UserStack" + } +} diff --git a/src/kernel/mm/mod.rs b/src/kernel/mm/mod.rs new file mode 100644 index 0000000..382e291 --- /dev/null +++ b/src/kernel/mm/mod.rs @@ -0,0 +1,49 @@ +pub mod page; +pub mod elf; +// mod frame; +mod addrspace; +pub mod vdso; +pub mod maparea; +// pub mod uptr; + +pub use addrspace::*; +pub use page::PhysPageFrame; + +#[cfg(feature = "swap-memory")] +pub mod swappable; + +use bitflags::bitflags; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemAccessType { + Read, + Write, + Execute, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct MapPerm: u8 { + const R = 1 << 0; + const W = 1 << 1; + const X = 1 << 2; + const U = 1 << 3; + const RW = (1 << 0) | (1 << 1); + } +} + +impl MemAccessType { + pub fn match_perm(&self, perm: MapPerm) -> bool { + match self { + MemAccessType::Read => perm.contains(MapPerm::R), + MemAccessType::Write => perm.contains(MapPerm::W), + MemAccessType::Execute => perm.contains(MapPerm::X), + } + } +} + +#[unsafe(link_section = ".text.init")] +pub fn init(frame_start: usize, frame_end: usize) { + page::init(frame_start, frame_end); + vdso::init(); +} diff --git a/src/kernel/mm/page.rs b/src/kernel/mm/page.rs new file mode 100644 index 0000000..e41e563 --- /dev/null +++ b/src/kernel/mm/page.rs @@ -0,0 +1,301 @@ +use core::alloc::Layout; +use crate::klib::{InitedCell, SpinLock}; +use crate::arch; + +struct FrameAllocator { + allocator: buddy_system_allocator::FrameAllocator, + allocated: usize, + #[cfg(feature = "swap-memory")] + waterlevel_high: usize, + #[cfg(feature = "swap-memory")] + waterlevel_low: usize, +} + +impl FrameAllocator { + #[allow(unused_variables)] + fn new(allocator: buddy_system_allocator::FrameAllocator, total: usize) -> Self { + Self { + allocator, + allocated: 0, + #[cfg(feature = "swap-memory")] + waterlevel_high: total * crate::kernel::config::KERNEL_PAGE_SHRINK_WATERLEVEL_HIGH / 100, + #[cfg(feature = "swap-memory")] + waterlevel_low: total * crate::kernel::config::KERNEL_PAGE_SHRINK_WATERLEVEL_LOW / 100, + } + } + + fn alloc(&mut self) -> Option { + let layout = Layout::from_size_align(arch::PGSIZE, arch::PGSIZE).unwrap(); + let addr = self.allocator.alloc_aligned(layout)?; + self.allocated += 1; + Some(addr) + } + + fn alloc_contiguous(&mut self, pages: usize) -> Option { + let layout = Layout::from_size_align(pages * arch::PGSIZE, arch::PGSIZE).unwrap(); + let addr = self.allocator.alloc_aligned(layout)?; + self.allocated += pages; + Some(addr) + } + + fn free(&mut self, addr: usize) { + let layout = Layout::from_size_align(arch::PGSIZE, arch::PGSIZE).unwrap(); + self.allocator.dealloc_aligned(addr, layout); + self.allocated -= 1; + } + + fn free_contiguous(&mut self, addr: usize, pages: usize) { + let layout = Layout::from_size_align(pages * arch::PGSIZE, arch::PGSIZE).unwrap(); + self.allocator.dealloc_aligned(addr, layout); + self.allocated -= pages; + } +} + +static FRAME_ALLOCATOR: InitedCell> = InitedCell::uninit(); +// static META_PTR_BASE: InitedCell = InitedCell::uninit(); +// static FRAME_BASE: InitedCell = InitedCell::uninit(); + +#[unsafe(link_section = ".text.init")] +pub fn init(frame_start: usize, frame_end: usize) { + // let zone_size = frame_end - frame_start; + // let ptr_zone_size = zone_size / (arch::PGSIZE + core::mem::size_of::<*const u8>()) * core::mem::size_of::<*const u8>(); + // META_PTR_BASE.init(frame_start); + + // let frame_base = (frame_start + ptr_zone_size + arch::PGSIZE - 1) & !(arch::PGSIZE - 1); + // FRAME_BASE.init(frame_base); + + let frame_base = frame_start; + let total = (frame_end - frame_start) / arch::PGSIZE; + + let mut allocator = buddy_system_allocator::FrameAllocator::new(); + allocator.add_frame(frame_base, frame_end); + FRAME_ALLOCATOR.init(SpinLock::new(FrameAllocator::new(allocator, total))); +} + +// fn page_meta_ref(page: usize) -> &'static mut *const () { +// let index = (page - *FRAME_BASE) / arch::PGSIZE; +// let meta_ptr_addr = *META_PTR_BASE + index * core::mem::size_of::<*const ()>(); +// unsafe { &mut *(meta_ptr_addr as *mut *const ()) } +// } + +#[cfg(feature = "swap-memory")] +pub fn need_to_shrink() -> bool { + let allocator = FRAME_ALLOCATOR.lock(); + allocator.allocated >= allocator.waterlevel_high +} + +pub fn alloc() -> usize { + if let Some(page) = FRAME_ALLOCATOR.lock().alloc() { + page + } else { + panic!("Out of physical memory"); + } +} + +#[cfg(feature = "swap-memory")] +pub fn alloc_with_shrink() -> usize { + let mut allocator = FRAME_ALLOCATOR.lock(); + + if allocator.allocated >= allocator.waterlevel_high { + let to_shrink = allocator.allocated - allocator.waterlevel_low + 1; + let min_to_shrink = to_shrink / 4 + 1; + drop(allocator); + + crate::kernel::mm::swappable::shrink(to_shrink, min_to_shrink); + + return FRAME_ALLOCATOR.lock().alloc().unwrap() + } + + allocator.alloc().unwrap() +} + +pub fn alloc_zero() -> usize { + let page = FRAME_ALLOCATOR.lock().alloc().unwrap(); + zero(page); + page +} + +#[cfg(feature = "swap-memory")] +pub fn alloc_with_shrink_zero() -> usize { + let page = alloc_with_shrink(); + zero(page); + page +} + +pub fn alloc_contiguous(pages: usize) -> usize { + let page = FRAME_ALLOCATOR.lock().alloc_contiguous(pages).unwrap(); + page +} + +// pub fn alloc_with_meta(meta: T) -> usize { +// let page = alloc(); +// let meta_ptr = page_meta_ref(page); +// unsafe { +// let ptr = alloc::alloc::alloc(Layout::new::()) as *mut T; +// if ptr.is_null() { +// panic!("Failed to allocate memory for page meta"); +// } +// *meta_ptr = ptr as *const (); +// core::ptr::write(ptr, meta); +// } +// page +// } + +pub fn free(page: usize) { + FRAME_ALLOCATOR.lock().free(page); +} + +pub fn free_contiguous(addr: usize, pages: usize) { + FRAME_ALLOCATOR.lock().free_contiguous(addr, pages); +} + +// fn free_with_meta(page: usize) { +// let meta_ptr = page_meta_ref(page); +// unsafe { +// let ptr = *meta_ptr as *mut T; +// if !ptr.is_null() { +// core::ptr::drop_in_place(ptr); +// alloc::alloc::dealloc(ptr as *mut u8, Layout::new::()); +// } +// } +// free(page); +// } + +// fn meta_of(page: usize) -> &'static mut T { +// let meta_ptr = page_meta_ref(page); +// unsafe { +// let ptr = *meta_ptr as *mut T; +// debug_assert!(!ptr.is_null(), "No meta data found for page {:#x}", page); +// &mut *ptr +// } +// } + +pub fn copy(src: usize, dst: usize) { + debug_assert!(src % arch::PGSIZE == 0, "Source address must be page-aligned: {:#x}", src); + debug_assert!(dst % arch::PGSIZE == 0, "Destination address must be page-aligned: {:#x}", dst); + debug_assert!(src != dst, "Source and destination addresses must be different: {:#x}", src); + + unsafe { + core::ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, arch::PGSIZE); + } +} + +pub fn zero(addr: usize) { + unsafe { + core::ptr::write_bytes(addr as *mut u8, 0, arch::PGSIZE); + } +} + +#[macro_export] +macro_rules! safe_page_write { + ($addr:expr, $buffer:expr) => { + { + let addr = $addr; + let buffer = $buffer; + + // Only perform bounds checking in debug mode + if cfg!(debug_assertions) { + if (addr & $crate::arch::PGMASK) + buffer.len() > $crate::arch::PGSIZE { + panic!( + "Buffer exceeds page size at {}:{}:{}\n addr = {:#x}, len = {:#x}", + file!(), + line!(), + column!(), + addr, + buffer.len() + ); + } + } + + unsafe { + core::slice::from_raw_parts_mut(addr as *mut u8, buffer.len()) + .copy_from_slice(buffer); + } + } + }; +} + +#[derive(Debug)] +pub struct PhysPageFrame { + page: usize, +} + +impl PhysPageFrame { + pub fn new(page: usize) -> Self { + Self { page } + } + + pub fn alloc() -> Self { + Self::new(alloc()) + } + + pub fn alloc_zeroed() -> Self { + Self::new(alloc_zero()) + } + + #[cfg(feature = "swap-memory")] + pub fn alloc_with_shrink_zeroed() -> Self { + Self::new(alloc_with_shrink_zero()) + } + + pub fn copy(&self) -> PhysPageFrame { + let new_frame = PhysPageFrame::alloc(); + copy(self.page, new_frame.page); + new_frame + } + + pub fn copy_from_slice(&self, offset: usize, src: &[u8]) { + safe_page_write!(self.page + offset, src); + } + + pub fn copy_to_slice(&self, offset: usize, dst: &mut [u8]) { + debug_assert!(offset + dst.len() <= arch::PGSIZE, "Slice exceeds page frame bounds"); + unsafe { + let src_ptr = (self.page + offset) as *const u8; + let dst_ptr = dst.as_mut_ptr(); + core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, dst.len()); + } + } + + pub fn slice(&self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.page as *mut u8, arch::PGSIZE) } + } + + pub fn get_page(&self) -> usize { + self.page + } + + pub fn ptr(&self) -> *mut u8 { + self.page as *mut u8 + } +} + +impl Drop for PhysPageFrame { + fn drop(&mut self) { + free(self.page); + } +} + +// pub struct PageFrameWithMeta { +// page: usize, +// _marker: core::marker::PhantomData, +// } + +// impl PageFrameWithMeta { +// pub fn new(page: usize) -> Self { +// Self { page, _marker: core::marker::PhantomData } +// } + +// pub fn alloc(meta: T) -> Self { +// let page = alloc_with_meta(meta); +// Self::new(page) +// } + +// pub fn meta(&self) -> &'static mut T { +// meta_of::(self.page) +// } + +// pub fn get_page(&self) -> usize { +// self.page +// } +// } diff --git a/src/kernel/mm/swappable/kswapd.rs b/src/kernel/mm/swappable/kswapd.rs new file mode 100644 index 0000000..9feba9a --- /dev/null +++ b/src/kernel/mm/swappable/kswapd.rs @@ -0,0 +1,19 @@ +use core::time::Duration; + +use crate::kernel::{mm::page, scheduler::current}; +use super::shrink; + +fn kswapd() { + loop { + // sleep 0.5s + current::sleep(Duration::from_millis(500)); + if !page::need_to_shrink() { + continue; + } + shrink(1024, 0); + } +} + +pub fn spawn_kswapd() { + crate::kernel::kthread::spawn(kswapd); +} diff --git a/src/kernel/mm/swappable/lru.rs b/src/kernel/mm/swappable/lru.rs new file mode 100644 index 0000000..f065d5c --- /dev/null +++ b/src/kernel/mm/swappable/lru.rs @@ -0,0 +1,223 @@ +use alloc::collections::BTreeMap; +use alloc::sync::{Arc, Weak}; + +type Link = Option>>; + +#[derive(Debug)] +pub struct Node { + pub key: K, + pub value: V, + prev: Weak>, + next: Link, +} + +impl Node { + pub fn new(key: K, value: V) -> Arc { + Arc::new(Self { + key, + value, + prev: Weak::new(), + next: None, + }) + } +} + +#[derive(Debug)] +pub struct LruList { + head: Link, + tail: Link, +} + +impl LruList { + pub fn new() -> Self { + Self { head: None, tail: None } + } + + pub fn push_front_node(&mut self, node: Arc>) { + unsafe { + let n = &mut *(Arc::as_ptr(&node) as *mut Node); + n.prev = Weak::new(); + n.next = None; + } + + match self.head.take() { + Some(old_head) => { + unsafe { + let oh = &mut *(Arc::as_ptr(&old_head) as *mut Node); + oh.prev = Arc::downgrade(&node); + } + unsafe { + let n = &mut *(Arc::as_ptr(&node) as *mut Node); + n.next = Some(old_head.clone()); + } + self.head = Some(node); + if self.tail.is_none() { + self.tail = Some(old_head); + } + } + None => { + self.tail = Some(node.clone()); + self.head = Some(node); + } + } + } + + pub fn move_to_front(&mut self, node: Arc>) { + if let Some(head) = &self.head { + if Arc::ptr_eq(head, &node) { + return; + } + } + + let (prev, next) = unsafe { + let n = &mut *(Arc::as_ptr(&node) as *mut Node); + (n.prev.upgrade(), n.next.clone()) + }; + + if let Some(prev_node) = &prev { + unsafe { + let p = &mut *(Arc::as_ptr(prev_node) as *mut Node); + p.next = next.clone(); + } + } + + if let Some(next_node) = &next { + unsafe { + let nx = &mut *(Arc::as_ptr(&next_node) as *mut Node); + nx.prev = node.prev.clone(); + } + } + + if let Some(tail) = &self.tail { + if Arc::ptr_eq(tail, &node) { + self.tail = prev; + } + } + + self.push_front_node(node); + } + + pub fn pop_back(&mut self) -> Option>> { + self.tail.take().map(|old_tail| { + let prev = unsafe { + let t = &mut *(Arc::as_ptr(&old_tail) as *mut Node); + t.prev.upgrade() + }; + + match prev { + Some(prev_node) => unsafe { + let p = &mut *(Arc::as_ptr(&prev_node) as *mut Node); + p.next = None; + self.tail = Some(prev_node); + }, + None => { + self.head = None; + } + } + + old_tail + }) + } +} + +pub struct LRUCache { + list: LruList, + map: BTreeMap>>, +} + +impl LRUCache { + pub fn new() -> Self { + Self { + list: LruList::new(), + map: BTreeMap::new(), + } + } + + pub fn put(&mut self, key: K, value: V) { + if let Some(node) = self.map.get(&key) { + unsafe { + let n = &mut *(Arc::as_ptr(node) as *mut Node); + n.value = value; + } + self.list.move_to_front(node.clone()); + return; + } + + let new_node = Node::new(key, value); + self.list.push_front_node(new_node.clone()); + self.map.insert(key, new_node); + } + + pub fn access(&mut self, key: &K) -> bool { + if let Some(node) = self.map.get(key) { + self.list.move_to_front(node.clone()); + true + } else { + false + } + } + + pub fn pop_lru(&mut self) -> Option + where + V: Clone, + { + if let Some(lru_node) = self.list.pop_back() { + self.map.remove(&lru_node.key); + Some(lru_node.key) + } else { + None + } + } + + pub fn tail(&mut self) -> Option<(K, &mut V)> { + self.list.tail.as_ref().map(|node| unsafe { + // SAFETY: We have `&mut self`, which guarantees exclusive access to the `LRUCache`. + // Although the nodes are stored in `Arc`s (which normally implies shared ownership and immutability), + // the `LRUCache` maintains the invariant that it is the sole logical owner of the data. + // No references to the internal nodes or values are leaked outside the cache. + // Therefore, it is safe to cast the pointer to mutable and modify the value, + // as no other references to this value can exist while we hold `&mut self`. + let n = &mut *(Arc::as_ptr(node) as *mut Node); + (n.key, &mut n.value) + }) + } + + pub fn remove(&mut self, key: &K) -> bool { + if let Some(node) = self.map.remove(key) { + let (prev, next) = unsafe { + let n = &mut *(Arc::as_ptr(&node) as *mut Node); + (n.prev.upgrade(), n.next.clone()) + }; + + if let Some(prev_node) = &prev { + unsafe { + let p = &mut *(Arc::as_ptr(prev_node) as *mut Node); + p.next = next.clone(); + } + } + + if let Some(next_node) = &next { + unsafe { + let nx = &mut *(Arc::as_ptr(&next_node) as *mut Node); + nx.prev = node.prev.clone(); + } + } + + if let Some(tail) = &self.list.tail { + if Arc::ptr_eq(tail, &node) { + self.list.tail = prev; + } + } + + if let Some(head) = &self.list.head { + if Arc::ptr_eq(head, &node) { + self.list.head = next; + } + } + + true + } else { + false + } + } +} diff --git a/src/kernel/mm/swappable/mod.rs b/src/kernel/mm/swappable/mod.rs new file mode 100644 index 0000000..f5fb29f --- /dev/null +++ b/src/kernel/mm/swappable/mod.rs @@ -0,0 +1,33 @@ +mod nofile; +mod swapper; +mod lru; +mod kswapd; +mod swappable; + +pub use nofile::SwappableNoFileFrame; +pub use kswapd::spawn_kswapd; +pub use swapper::shrink; + +use lru::LRUCache; +use swappable::SwappableFrame; + +use alloc::collections::LinkedList; +use alloc::sync::{Arc, Weak}; + +use crate::kernel::mm::AddrSpace; +use crate::klib::SpinLock; + +pub type AddrSpaceFamilyChain = Arc>>>; + +use crate::driver::get_block_driver; + +#[unsafe(link_section = ".text.init")] +pub fn init() { + let driver = get_block_driver("virtio_block0").expect("Swap driver not found"); + nofile::init_swapper(driver); + swapper::init_swapper(); +} + +pub fn fini() { + swapper::print_perf_info(); +} diff --git a/src/kernel/mm/swappable/nofile/frame.rs b/src/kernel/mm/swappable/nofile/frame.rs new file mode 100644 index 0000000..c37bf55 --- /dev/null +++ b/src/kernel/mm/swappable/nofile/frame.rs @@ -0,0 +1,107 @@ +use core::usize; +use alloc::sync::Arc; + +use crate::kernel::mm::{AddrSpace, PhysPageFrame}; +use crate::kernel::mm::swappable::AddrSpaceFamilyChain; +use crate::kernel::mm::swappable::swapper; +use crate::klib::SpinLock; + +pub(super) struct AllocatedFrame { + pub(super) frame: PhysPageFrame, + pub(super) dirty: bool, +} + +pub(super) enum State { + Allocated(AllocatedFrame), + SwappedOut, +} + +pub(super) const NO_DISK_BLOCK: usize = usize::MAX; + +pub(super) struct FrameState { + pub(super) state: State, + pub(super) disk_block: usize, +} + +pub struct SwappableNoFileFrameInner { + pub(super) state: SpinLock, + pub(super) family_chain: AddrSpaceFamilyChain, + pub uaddr: usize, +} + +impl SwappableNoFileFrameInner { + pub fn allocated(uaddr: usize, frame: PhysPageFrame, family_chain: AddrSpaceFamilyChain) -> Self { + Self { + state: SpinLock::new( + FrameState { + state: State::Allocated(AllocatedFrame { frame, dirty: false }), + disk_block: NO_DISK_BLOCK + } + ), + family_chain, + uaddr, + } + } +} + +pub struct SwappableNoFileFrame { + inner: Arc, +} + +impl SwappableNoFileFrame { + pub fn allocated(uaddr: usize, frame: PhysPageFrame, addrspace: &AddrSpace) -> Self { + let inner = Arc::new(SwappableNoFileFrameInner::allocated(uaddr, frame, addrspace.family_chain().clone())); + Self { inner } + } + + pub fn alloc(uaddr: usize, addrspace: &AddrSpace) -> Self { + let frame = PhysPageFrame::alloc(); + let kpage = frame.get_page(); + let frame = Self::allocated(uaddr, frame, addrspace); + swapper::push_lru(kpage, frame.inner.clone()); + frame + } + + pub fn alloc_zeroed(uaddr: usize, addrspace: &AddrSpace) -> (Self, usize) { + let frame = PhysPageFrame::alloc_zeroed(); + let kpage = frame.get_page(); + let frame = Self::allocated(uaddr, frame, addrspace); + swapper::push_lru(kpage, frame.inner.clone()); + (frame, kpage) + } + + pub fn copy(&self, addrspace: &AddrSpace) -> (Self, usize) { + let (inner, kpage) = self.inner.copy(addrspace); + (SwappableNoFileFrame { inner }, kpage) + } + + pub fn get_page(&self) -> Option { + match &self.inner.state.lock().state { + State::Allocated(allocated) => { + Some(allocated.frame.get_page()) + }, + State::SwappedOut => { + None + } + } + } + + pub fn get_page_swap_in(&self) -> usize { + self.inner.get_page_swap_in() + } + + pub fn uaddr(&self) -> usize { + self.inner.uaddr + } + + pub fn is_swapped_out(&self) -> bool { + matches!(&self.inner.state.lock().state, State::SwappedOut) + } +} + +impl Drop for SwappableNoFileFrame { + fn drop(&mut self) { + // When an SwappableNoFileFrame is dropped, it should be deallocated from the swapper. + self.inner.free(); + } +} diff --git a/src/kernel/mm/swappable/nofile/mod.rs b/src/kernel/mm/swappable/nofile/mod.rs new file mode 100644 index 0000000..8808088 --- /dev/null +++ b/src/kernel/mm/swappable/nofile/mod.rs @@ -0,0 +1,5 @@ +mod frame; +mod swap; + +pub use swap::*; +pub use frame::SwappableNoFileFrame; diff --git a/src/kernel/mm/swappable/nofile/swap.rs b/src/kernel/mm/swappable/nofile/swap.rs new file mode 100644 index 0000000..293e740 --- /dev/null +++ b/src/kernel/mm/swappable/nofile/swap.rs @@ -0,0 +1,235 @@ +use alloc::sync::Arc; +use bitvec::vec::BitVec; + +use crate::driver::BlockDriverOps; +use crate::arch; +use crate::kernel::mm::swappable::swapper::counter_swap_in; +use crate::kernel::mm::swappable::swapper; +use crate::kernel::mm::{AddrSpace, PhysPageFrame}; +use crate::kernel::mm::swappable::swappable::SwappableFrame; +use crate::klib::{InitedCell, SpinLock}; + +use super::frame::{SwappableNoFileFrameInner, State, AllocatedFrame, NO_DISK_BLOCK}; + +struct BitMap { + allocated: BitVec, + cached: BitVec, // `1` means this block has a cached page in memory `0` means this block only has a swapped out page on disk +} + +struct SwapperDisk { + bitmap: SpinLock, + driver: Arc, + block_per_page: usize, +} + +impl SwapperDisk { + fn new(driver: Arc) -> Self { + let block_size = driver.get_block_size() as usize; + let len = block_size * driver.get_block_count() as usize / arch::PGSIZE; + let bitmap = SpinLock::new(BitMap { allocated: BitVec::repeat(false, len), cached: BitVec::repeat(false, len) }); + let block_per_page = arch::PGSIZE / block_size; + + debug_assert!(block_per_page * block_size == arch::PGSIZE); + crate::kinfo!("Anonymous swapper: {} pages ({} KB) available.", len, len * arch::PGSIZE / 1024); + + Self { + bitmap, + driver, + block_per_page, + } + } + + pub fn block_per_page(&self) -> usize { + self.block_per_page + } + + pub fn read_page(&self, block_start: usize, frame: &PhysPageFrame) { + let start = crate::kernel::event::timer::now(); + self.driver.read_blocks( + block_start, + frame.slice() + ).expect("Failed to read swapped out page from block device"); + let end = crate::kernel::event::timer::now(); + counter_swap_in(end - start); + } + + pub fn write_page(&self, block_start: usize, frame: &PhysPageFrame) { + self.driver.write_blocks(block_start, frame.slice()).expect("Failed to write back"); + } + + pub fn alloc_slot(&self) -> Option { + let bitmap = self.bitmap.lock(); + if let Some(pos) = bitmap.allocated.first_zero() { + Some(pos) + } else if let Some(pos) = bitmap.cached.first_one() { + Some(pos) + } else { + None + } + } + + pub fn use_slot(&self, pos: usize) { + let mut bitmap = self.bitmap.lock(); + bitmap.allocated.set(pos, true); + bitmap.cached.set(pos, false); + } + + pub fn free_slot(&self, pos: usize) { + let mut bitmap = self.bitmap.lock(); + bitmap.allocated.set(pos, false); + bitmap.cached.set(pos, false); + } + + pub fn set_cached(&self, pos: usize, cached: bool) { + self.bitmap.lock().cached.set(pos, cached); + } + + pub fn is_cached(&self, pos: usize) -> bool { + self.bitmap.lock().cached.get(pos).map(|b| *b).unwrap_or(false) + } +} + +static SWAPPER: InitedCell = InitedCell::uninit(); + +pub fn init_swapper(driver: Arc) { + SWAPPER.init(SwapperDisk::new(driver)); +} + +impl SwappableNoFileFrameInner { + pub fn get_page_swap_in(self: &Arc) -> usize { + let mut state = self.state.lock(); + match &state.state { + State::Allocated(allocated) => { + allocated.frame.get_page() + }, + State::SwappedOut => { + let start = crate::kernel::event::timer::now(); + + let disk_block = state.disk_block; + + let frame = PhysPageFrame::alloc_with_shrink_zeroed(); + SWAPPER.read_page(disk_block, &frame); + + // Cached the swap page in disk + let pos = disk_block / SWAPPER.block_per_page(); + SWAPPER.set_cached(pos, true); + + let kpage = frame.get_page(); + state.state = State::Allocated(AllocatedFrame{ + frame, + dirty: false, + }); + swapper::push_lru(kpage, self.clone()); // Note: This clone might be problematic if self is not Arc + + let end = crate::kernel::event::timer::now(); + counter_swap_in(end - start); + + kpage + } + } + } + + pub fn copy(&self, addrspace: &AddrSpace) -> (Arc, usize) { + let state = self.state.lock(); + let new_frame = match &state.state { + State::Allocated(allocated) => { + allocated.frame.copy() + } + State::SwappedOut => { + let new_frame = PhysPageFrame::alloc_with_shrink_zeroed(); + debug_assert!(state.disk_block != NO_DISK_BLOCK); + SWAPPER.read_page(state.disk_block, &new_frame); + new_frame + } + }; + let kpage = new_frame.get_page(); + let allocated = Arc::new(SwappableNoFileFrameInner::allocated(self.uaddr, new_frame, addrspace.family_chain().clone())); + swapper::push_lru(kpage, allocated.clone()); + (allocated, kpage) + } + + pub fn free(&self) { + let state = self.state.lock(); + match &state.state { + State::Allocated(allocated) => { + let key = allocated.frame.get_page(); + swapper::remove_lru(key); + } + State::SwappedOut => { + let pos = state.disk_block / SWAPPER.block_per_page(); + SWAPPER.free_slot(pos); + } + } + } +} + +impl SwappableFrame for SwappableNoFileFrameInner { + fn swap_out(&self, dirty: bool) -> bool { + let mut state = self.state.lock(); + + let disk_block = state.disk_block; + + let allocated = match &mut state.state { + State::Allocated(allocated) => { allocated }, + State::SwappedOut => { return false; } + }; + + let pos = if disk_block != NO_DISK_BLOCK { + disk_block / SWAPPER.block_per_page() + } else if let Some(pos) = SWAPPER.alloc_slot() { + pos + } else { + return false; + }; + + let block_start = pos * SWAPPER.block_per_page(); + let need_write = dirty || !SWAPPER.is_cached(pos); + + SWAPPER.use_slot(pos); + + if need_write { + SWAPPER.write_page(block_start, &allocated.frame); + } + + let kpage = allocated.frame.get_page(); + self.family_chain.lock().retain(|member| { + if let Some(addrspace) = member.upgrade() { + addrspace.unmap_swap_page(self.uaddr, kpage); + true + } else { + false + } + }); + + state.state = State::SwappedOut; + state.disk_block = block_start; + + true + } + + fn take_access_dirty_bit(&self) -> Option<(bool, bool)> { + let mut state = self.state.lock(); + let allocated = match &mut state.state { + State::Allocated(allocated) => { allocated }, + State::SwappedOut => { return None; } + }; + + let mut accessed = false; + let mut dirty = allocated.dirty; + + for member in &*self.family_chain.lock() { + if let Some(addrspace) = member.upgrade() { + if let Some((a, d)) = addrspace.take_page_access_dirty_bit(self.uaddr) { + accessed |= a; + dirty |= d; + } + } + } + + if dirty { + allocated.dirty = true; + } + + Some((accessed, dirty)) + } +} diff --git a/src/kernel/mm/swappable/swappable.rs b/src/kernel/mm/swappable/swappable.rs new file mode 100644 index 0000000..4d3e868 --- /dev/null +++ b/src/kernel/mm/swappable/swappable.rs @@ -0,0 +1,4 @@ +pub trait SwappableFrame { + fn swap_out(&self, dirty: bool) -> bool; + fn take_access_dirty_bit(&self) -> Option<(bool, bool)>; +} diff --git a/src/kernel/mm/swappable/swapper.rs b/src/kernel/mm/swappable/swapper.rs new file mode 100644 index 0000000..5751e5f --- /dev/null +++ b/src/kernel/mm/swappable/swapper.rs @@ -0,0 +1,159 @@ +use core::time::Duration; +use alloc::sync::Arc; + +use crate::kernel::mm::swappable::LRUCache; +use crate::klib::{InitedCell, SpinLock}; + +use super::SwappableFrame; + +struct Counter { + swap_in_count: usize, + swap_out_count: usize, + swap_in_time: Duration, + swap_out_time: Duration, + shrink_count: usize, + shrink_time: Duration, +} + +static COUNTER: SpinLock = SpinLock::new(Counter { + swap_in_count: 0, + swap_out_count: 0, + swap_in_time: Duration::ZERO, + swap_out_time: Duration::ZERO, + shrink_count: 0, + shrink_time: Duration::ZERO, +}); + +pub fn print_perf_info() { + let counter = COUNTER.lock(); + crate::kinfo!("Anonymous Swapper Performance Info:"); + crate::kinfo!(" Swap In: {} times, total time: {:?}", counter.swap_in_count, counter.swap_in_time); + crate::kinfo!(" Swap Out: {} times, total time: {:?}", counter.swap_out_count, counter.swap_out_time); + crate::kinfo!(" Shrink: {} times, total time: {:?}", counter.shrink_count, counter.shrink_time); +} + +pub fn counter_swap_in(time: Duration) { + let mut counter = COUNTER.lock(); + counter.swap_in_count += 1; + counter.swap_in_time += time; +} + +pub fn counter_swap_out(time: Duration) { + let mut counter = COUNTER.lock(); + counter.swap_out_count += 1; + counter.swap_out_time += time; +} + +#[derive(Clone)] +struct SwapEntry { + frame: Arc, + dirty: bool, +} + +impl SwapEntry { + fn new(frame: Arc) -> Self { + Self { + frame, + dirty: false, + } + } +} + +struct Swapper { + lru: SpinLock>, +} + +impl Swapper { + fn new() -> Self { + Self { + lru: SpinLock::new(LRUCache::new()), + } + } + + fn put(&self, kpage: usize, frame: Arc) { + self.lru.lock().put(kpage, SwapEntry::new(frame)); + } + + fn remove_lru(&self, kpage: usize) { + self.lru.lock().remove(&kpage); + } + + fn shrink(&self, page_count: usize, min_to_shrink: usize) { + let shrink_start = crate::kernel::event::timer::now(); + let mut lru = self.lru.lock(); + + let mut swapped_count = 0; + for _ in 0..(page_count * 2) { + if let Some((key, entry)) = lru.tail() { + // Remove the tail entry so we can own and modify it + let frame = &entry.frame; + let (accessed, dirty) = frame.take_access_dirty_bit().unwrap_or((false, false)); + + if accessed { + entry.dirty |= dirty; + // Recently accessed, move it to the head of LRU by reinserting + lru.access(&key); + continue; + } + + frame.swap_out(dirty); + lru.pop_lru(); + + swapped_count += 1; + + if swapped_count >= page_count { + break; + } + } else { + break; + } + } + + if swapped_count < min_to_shrink { + for _ in swapped_count..min_to_shrink { + if let Some((_, entry)) = lru.tail() { + let frame = &entry.frame; + + // Swap out this frame + let mut dirty = entry.dirty; + if !dirty { + let (_, d) = frame.take_access_dirty_bit().unwrap_or((false, false)); + dirty |= d; + } + + let start = crate::kernel::event::timer::now(); + frame.swap_out(dirty); + lru.pop_lru(); + let end = crate::kernel::event::timer::now(); + counter_swap_out(end - start); + + swapped_count += 1; + } else { + break; + } + } + } + + let mut counter = COUNTER.lock(); + counter.shrink_count += 1; + counter.shrink_time += crate::kernel::event::timer::now() - shrink_start; + } +} + +static SWAPPER: InitedCell = InitedCell::uninit(); + +pub fn init_swapper() { + SWAPPER.init(Swapper::new()); +} + +pub fn push_lru(kpage: usize, frame: Arc) { + SWAPPER.put(kpage, frame); +} + +pub fn remove_lru(kpage: usize) { + SWAPPER.remove_lru(kpage); +} + +pub fn shrink(page_count: usize, min_to_shrink: usize) { + SWAPPER.shrink(page_count, min_to_shrink); +} diff --git a/src/kernel/mm/vdso.rs b/src/kernel/mm/vdso.rs new file mode 100644 index 0000000..4a03549 --- /dev/null +++ b/src/kernel/mm/vdso.rs @@ -0,0 +1,134 @@ +use alloc::vec::Vec; +use alloc::vec; + +use crate::kernel::config; +use crate::kernel::mm::elf::def::{Elf64Ehdr, Elf64Phdr}; +use crate::arch::{self, PageTableTrait}; +use crate::kernel::mm::MapPerm; +use crate::klib::initcell::InitedCell; + +use super::PhysPageFrame; + +unsafe extern "C" { + static __vdso_start: u8; + static __vdso_end: u8; +} + +mod addr { + // include!("/home/rache/code/KernelX/./vdso/build/riscv64/symbols.inc"); + // include!(concat!(env!("OUT_DIR"), "/symbols.inc")); + include!(concat!(env!("KERNELX_HOME"), "/vdso/build/", env!("ARCH"), env!("ARCH_BITS"), "/symbols.inc")); +} + +fn vdso_start() -> usize { + core::ptr::addr_of!(__vdso_start) as usize +} + +#[inline(always)] +pub fn addr_of(symbol: &str) -> usize { + match symbol { + "sigreturn_trampoline" => addr::__vdso_sigreturn_trampoline, + _ => panic!("Unknown VDSO symbol: {}", symbol), + } +} + +struct LoadedProgram { + ubase: usize, + pages: Vec, +} + +struct VDSOInfo { + programs: Vec, +} + +static VDSO: InitedCell = InitedCell::uninit(); + +fn load_programs(ehdr: &Elf64Ehdr) -> Vec { + let ph_addr = vdso_start() + ehdr.e_phoff as usize; + let mut loaded_programs = Vec::new(); + + for i in 0..ehdr.e_phnum { + let phdr = unsafe { (ph_addr as *const Elf64Phdr).add(i as usize).as_ref().unwrap() }; + if !phdr.is_load() { + continue; + } + + let mut pages = vec![]; + let mut loaded = 0; + let mut copied = 0; + let memsz = phdr.p_memsz as usize; + let filesz = phdr.p_filesz as usize; + let program_start = (vdso_start() + phdr.p_offset as usize) as *const u8; + + // Load unaligned + let pageoff = phdr.p_vaddr as usize & arch::PGMASK; + if pageoff != 0 { + let page = PhysPageFrame::alloc_zeroed(); + let to_copy = core::cmp::min(arch::PGSIZE - pageoff, filesz); + unsafe { + core::ptr::copy_nonoverlapping( + program_start, + page.ptr().add(pageoff), + to_copy, + ); + } + + loaded += to_copy; + copied += to_copy; + pages.push(page); + } + + while loaded < filesz { + let page = PhysPageFrame::alloc_zeroed(); + let to_copy = core::cmp::min(arch::PGSIZE, filesz - copied); + unsafe { + core::ptr::copy_nonoverlapping( + program_start.add(loaded), + page.ptr(), + to_copy, + ); + } + + pages.push(page); + copied += to_copy; + loaded += arch::PGSIZE; + } + + while loaded < memsz { + let page = PhysPageFrame::alloc_zeroed(); + pages.push(page); + loaded += arch::PGSIZE; + } + + loaded_programs.push(LoadedProgram { + ubase: phdr.p_vaddr as usize, + pages, + }); + } + + loaded_programs +} + +#[unsafe(link_section = ".text.init")] +pub fn init() { + let ehdr = unsafe {(vdso_start() as *const Elf64Ehdr).as_ref().unwrap()}; + + if !ehdr.is_valid_elf() || !ehdr.is_64bit() || !ehdr.is_little_endian() || !ehdr.is_riscv() || !ehdr.is_dynamic() { + panic!("Invalid VDSO ELF header: {:?}", ehdr); + } + + // load VDSO's program to memory + let loaded_programs = load_programs(ehdr); + + VDSO.init(VDSOInfo { programs: loaded_programs }); +} + +pub fn map_to_pagetale(pagetable: &mut arch::PageTable) { + for program in &VDSO.programs { + let mut uaddr = program.ubase + config::VDSO_BASE; + for page in program.pages.iter() { + pagetable.mmap(uaddr, page.get_page(), MapPerm::R | MapPerm::X | MapPerm::U); + uaddr += arch::PGSIZE; + } + } +} diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs new file mode 100644 index 0000000..b8dce8e --- /dev/null +++ b/src/kernel/mod.rs @@ -0,0 +1,16 @@ +mod main; +pub mod mm; +pub mod task; +pub mod kthread; +pub mod scheduler; +pub mod errno; +pub mod trap; +pub mod syscall; +pub mod ipc; +pub mod event; +pub mod usync; +pub mod config; +pub mod uapi; + +pub use main::exit; +pub use main::parse_boot_args; diff --git a/src/kernel/scheduler/current.rs b/src/kernel/scheduler/current.rs new file mode 100644 index 0000000..ce4a81a --- /dev/null +++ b/src/kernel/scheduler/current.rs @@ -0,0 +1,167 @@ +use core::time::Duration; + +use alloc::sync::Arc; +use spin::Mutex; + +use crate::kernel::event::{Event, timer}; +use crate::kernel::ipc::SignalActionTable; +use crate::kernel::mm::AddrSpace; +use crate::kernel::scheduler::task::Task; +use crate::kernel::task::{PCB, TCB}; +use crate::kernel::task::fdtable::FDTable; +use crate::kernel::scheduler::Processor; +use crate::kernel::uapi::Uid; +use crate::arch; +use crate::fs::Dentry; +use crate::klib::SpinLock; + +use super::Tid; + +pub fn processor() -> &'static mut Processor<'static> { + let p = arch::get_percpu_data() as *mut Processor; + + debug_assert!(!p.is_null()); + + unsafe { &mut *p } +} + +pub fn set(p: *mut Processor) { + arch::set_percpu_data(p as usize); +} + +pub fn clear() { + arch::set_percpu_data(0); +} + +pub fn is_clear() -> bool { + arch::get_percpu_data() == 0 +} + +pub fn task() -> &'static Arc { + let processor = processor(); + &processor.task() +} + +pub fn tcb() -> &'static TCB { + let processor = processor(); + processor.tcb() +} + +pub fn tid() -> Tid { + if is_clear() { + -1 + } else { + task().tid() + } +} + +pub fn pid() -> Tid { + if is_clear() { + -1 + } else { + pcb().get_pid() + } +} + +pub fn uid() -> Uid { + 0 +} + +pub fn pcb() -> &'static Arc { + tcb().get_parent() +} + +pub fn signal_actions() -> &'static Mutex { + let pcb = pcb(); + pcb.signal_actions() +} + +pub fn addrspace() -> &'static Arc { + let tcb = tcb(); + tcb.get_addrspace() +} + +pub fn fdtable() -> &'static SpinLock { + let tcb = tcb(); + tcb.fdtable() +} + +pub fn with_cwd(f: F) -> R +where F: FnOnce(&Arc) -> R { + let pcb = pcb(); + pcb.with_cwd(f) +} + +pub mod copy_to_user { + use crate::kernel::errno::SysResult; + use super::addrspace; + + pub fn buffer(uaddr: usize, buf: &[u8]) -> SysResult<()> { + addrspace().copy_to_user_buffer(uaddr, buf) + } + + pub fn object(uaddr: usize, value: T) -> SysResult<()> { + addrspace().copy_to_user(uaddr, value) + } + + pub fn slice(uaddr: usize, slice: &[T]) -> SysResult<()> { + addrspace().copy_to_user_slice(uaddr, slice) + } + + pub fn string(uaddr: usize, s: &str, max_size: usize) -> SysResult { + let bytes = s.as_bytes(); + let len = core::cmp::min(bytes.len(), max_size - 1); + addrspace().copy_to_user_buffer(uaddr, &bytes[..len])?; + addrspace().copy_to_user_buffer(uaddr + len, &[0u8])?; + Ok(len) + } +} + +pub mod copy_from_user { + use alloc::string::String; + use crate::kernel::errno::SysResult; + use super::addrspace; + + pub fn buffer(uaddr: usize, buf: &mut [u8]) -> SysResult<()> { + addrspace().copy_from_user_buffer(uaddr, buf) + } + + pub fn object(uaddr: usize) -> SysResult { + addrspace().copy_from_user::(uaddr) + } + + pub fn string(uaddr: usize) -> SysResult { + addrspace().get_user_string(uaddr) + } + + pub fn slice(uaddr: usize, slice: &mut [T]) -> SysResult<()> { + addrspace().copy_from_user_slice(uaddr, slice) + } +} + +pub fn schedule() { + processor().schedule() +} + +pub fn block(reason: &'static str) -> Event { + task().block(reason); + schedule(); + task().take_wakeup_event().unwrap() +} + +pub fn block_uninterruptible(reason: &'static str) -> Event { + task().block_uninterruptible(reason); + schedule(); + task().take_wakeup_event().unwrap() +} + +pub fn sleep(durations: Duration) -> Event { + timer::add_timer(task().clone(), durations); + task().block("sleep"); + schedule(); + task().take_wakeup_event().unwrap() +} + +pub fn umask() -> u32 { + pcb().umask() as u32 +} diff --git a/src/kernel/scheduler/kernelstack.rs b/src/kernel/scheduler/kernelstack.rs new file mode 100644 index 0000000..a911546 --- /dev/null +++ b/src/kernel/scheduler/kernelstack.rs @@ -0,0 +1,36 @@ +use crate::arch; +use crate::kernel::mm::{self, MapPerm}; + +pub struct KernelStack { + top: usize, + page_count: usize, +} + +impl KernelStack { + pub fn new(page_count: usize) -> Self { + let base = mm::page::alloc_contiguous(page_count + 1); + let top = base + arch::PGSIZE * (page_count + 1); + unsafe { arch::unmap_kernel_addr(base, arch::PGSIZE) }; + Self { top, page_count } + } + + pub fn get_top(&self) -> usize { + self.top + } + + pub fn check_stack_overflow(&self, sp: usize) -> bool { + let base = self.top - arch::PGSIZE * (self.page_count + 1); + sp < self.top && sp >= base + } +} + +impl Drop for KernelStack { + fn drop(&mut self) { + let base = self.top - arch::PGSIZE * (self.page_count + 1); + arch::map_kernel_addr(base, arch::kaddr_to_paddr(base), arch::PGSIZE, MapPerm::RW); + mm::page::free_contiguous(base, self.page_count + 1); + } +} + +unsafe impl Send for KernelStack {} +unsafe impl Sync for KernelStack {} \ No newline at end of file diff --git a/src/kernel/scheduler/mod.rs b/src/kernel/scheduler/mod.rs new file mode 100644 index 0000000..683e2ba --- /dev/null +++ b/src/kernel/scheduler/mod.rs @@ -0,0 +1,11 @@ +mod scheduler; +mod processor; +mod task; + +pub mod current; +pub mod tid; + +pub use scheduler::*; +pub use processor::*; +pub use task::*; +pub use tid::Tid; diff --git a/src/kernel/scheduler/processor.rs b/src/kernel/scheduler/processor.rs new file mode 100644 index 0000000..3322fad --- /dev/null +++ b/src/kernel/scheduler/processor.rs @@ -0,0 +1,39 @@ +use alloc::sync::Arc; + +use crate::kernel::scheduler::task::Task; +use crate::kernel::scheduler::current; +use crate::kernel::task::TCB; +use crate::arch; + +pub struct Processor<'a> { + idle_kernel_context: arch::KernelContext, + task: &'a Arc, +} + +impl<'a> Processor<'a> { + pub fn new(task: &'a Arc) -> Self { + Self { + idle_kernel_context: arch::KernelContext::new_idle(), + task + } + } + + pub fn task(&self) -> &Arc { + &self.task + } + + pub fn tcb(&self) -> &TCB { + self.task.tcb() + } + + pub fn switch_to_task(&mut self){ + current::set(self); + arch::kernel_switch(&mut self.idle_kernel_context, self.task.get_kcontext_ptr()); + current::clear(); + } + + pub fn schedule(&mut self) { + arch::disable_interrupt(); + arch::kernel_switch(self.task.get_kcontext_ptr(), &mut self.idle_kernel_context); + } +} diff --git a/src/kernel/scheduler/scheduler.rs b/src/kernel/scheduler/scheduler.rs new file mode 100644 index 0000000..8ce8b63 --- /dev/null +++ b/src/kernel/scheduler/scheduler.rs @@ -0,0 +1,78 @@ +use alloc::collections::vec_deque::VecDeque; +use alloc::sync::Arc; + +use crate::kernel::scheduler::current; +use crate::kernel::scheduler::task::Task; +use crate::kernel::event::Event; +use crate::arch; +use crate::klib::SpinLock; + +use super::processor::Processor; + +pub struct Scheduler { + ready_queue: SpinLock>>, +} + +impl Scheduler { + const fn new() -> Self { + Self { + ready_queue: SpinLock::new(VecDeque::new()), + } + } + + fn push_task(&self, task: Arc) { + let mut ready_queue = self.ready_queue.lock(); + ready_queue.iter().for_each(|t| { + debug_assert!(t.tid() != task.tid(), "Task {} is already in ready queue!", t.tid()); + }); + ready_queue.push_back(task); + } + + fn fetch_next_task(&self) -> Option> { + self.ready_queue.lock().pop_front() + } +} + +static SCHEDULER: Scheduler = Scheduler::new(); + +pub fn push_task(task: Arc) { + SCHEDULER.push_task(task); +} + +pub fn fetch_next_task() -> Option> { + SCHEDULER.fetch_next_task() +} + +pub fn wakeup_task(task: Arc, event: Event) { + if task.wakeup(event) { + push_task(task); + } +} + +pub fn wakeup_task_uninterruptible(task: Arc, event: Event) { + task.wakeup_uninterruptible(event); + push_task(task); +} + +pub fn run_tasks(_hartid: u8) -> ! { + current::clear(); + loop { + arch::disable_interrupt(); + if let Some(task) = fetch_next_task() { + if !task.run_if_ready() { + continue; + } + + let mut processor = Processor::new(&task); + + processor.switch_to_task(); + + if task.state_running_to_ready() { + push_task(task); + } + } else { + arch::enable_interrupt(); + arch::wait_for_interrupt(); + } + } +} diff --git a/src/kernel/scheduler/task.rs b/src/kernel/scheduler/task.rs new file mode 100644 index 0000000..179dac7 --- /dev/null +++ b/src/kernel/scheduler/task.rs @@ -0,0 +1,68 @@ +use crate::arch; +use crate::kernel::event::Event; +use crate::kernel::task::TCB; +use crate::kernel::mm; +use crate::kernel::mm::MapPerm; + +use super::Tid; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TaskState { + Running, + Ready, + Blocked, + BlockedUninterruptible, + Exited, +} + +pub struct KernelStack { + top: usize, + page_count: usize, +} + +impl KernelStack { + pub fn new(page_count: usize) -> Self { + let base = mm::page::alloc_contiguous(page_count + 1); + let top = base + arch::PGSIZE * (page_count + 1); + unsafe { arch::unmap_kernel_addr(base, arch::PGSIZE) }; + Self { top, page_count } + } + + pub fn get_top(&self) -> usize { + self.top + } + + pub fn check_stack_overflow(&self, sp: usize) -> bool { + let base = self.top - arch::PGSIZE * (self.page_count + 1); + sp < self.top && sp >= base + } +} + +impl Drop for KernelStack { + fn drop(&mut self) { + let base = self.top - arch::PGSIZE * (self.page_count + 1); + arch::map_kernel_addr(base, arch::kaddr_to_paddr(base), arch::PGSIZE, MapPerm::RW); + mm::page::free_contiguous(base, self.page_count + 1); + } +} + +unsafe impl Send for KernelStack {} +unsafe impl Sync for KernelStack {} + +pub trait Task: Send + Sync { + fn tid(&self) -> Tid; + fn get_kcontext_ptr(&self) -> *mut arch::KernelContext; + fn kstack(&self) -> &KernelStack; + + fn run_if_ready(&self) -> bool; + fn state_running_to_ready(&self) -> bool; + + fn block(&self, reason: &str) -> bool; + fn block_uninterruptible(&self, reason: &str) -> bool; + + fn wakeup(&self, event: Event) -> bool; + fn wakeup_uninterruptible(&self, event: Event); + fn take_wakeup_event(&self) -> Option; + + fn tcb(&self) -> &TCB; +} diff --git a/src/kernel/scheduler/tid.rs b/src/kernel/scheduler/tid.rs new file mode 100644 index 0000000..58060ea --- /dev/null +++ b/src/kernel/scheduler/tid.rs @@ -0,0 +1,16 @@ +use spin::Mutex; + +use crate::kernel::syscall::UserStruct; + +pub type Tid = i32; + +impl UserStruct for Tid {} + +static NEXT_TID: Mutex = Mutex::new(0); + +pub fn alloc() -> Tid { + let mut next_tid = NEXT_TID.lock(); + let tid = *next_tid; + *next_tid += 1; + tid +} diff --git a/src/kernel/syscall/def.rs b/src/kernel/syscall/def.rs new file mode 100644 index 0000000..d54339c --- /dev/null +++ b/src/kernel/syscall/def.rs @@ -0,0 +1,3 @@ +pub const AT_FDCWD: isize = -100; + +pub const BUFFER_SIZE: usize = 1024; diff --git a/src/kernel/syscall/event.rs b/src/kernel/syscall/event.rs new file mode 100644 index 0000000..a63ea66 --- /dev/null +++ b/src/kernel/syscall/event.rs @@ -0,0 +1,180 @@ +use core::time::Duration; +use alloc::vec; +use alloc::vec::Vec; +use alloc::sync::Arc; + +use crate::fs::file::FileOps; +use crate::kernel::event::{Event, PollEvent, PollEventSet, timer}; +use crate::kernel::ipc::SignalSet; +use crate::kernel::scheduler::current; +use crate::kernel::syscall::uptr::{UArray, UPtr, UserPointer, UserStruct}; +use crate::kernel::syscall::SysResult; +use crate::kernel::errno::Errno; +use crate::kernel::uapi::{self, Timespec32}; + +const FD_SET_SIZE: usize = 1024; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct FdSet { + fds_bits: [usize; FD_SET_SIZE / (8 * core::mem::size_of::())], // support up to 512 fds +} +impl UserStruct for FdSet {} + +// TODO: implement this +pub fn pselect6_time32( + nfds: usize, + uptr_readfds: UPtr, + uptr_writefds: UPtr, + uptr_exceptfds: UPtr, + uptr_timeout: UPtr, + _uptr_sigmask: UPtr +) -> SysResult { + if nfds == 0 || nfds > FD_SET_SIZE { + return Err(Errno::EINVAL); + } + + let mut _readfds = uptr_readfds.read_optional()?; + let mut _writefds = uptr_writefds.read_optional()?; + let mut _exceptfds = uptr_exceptfds.read_optional()?; + + let _timeout: Option = uptr_timeout.read_optional()?.map(|ts| { + ts.into() + }); + + Ok(0) +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct Pollfd { + pub fd: i32, + pub events: i16, + pub revents: i16, +} +impl UserStruct for Pollfd {} + +impl Pollfd { + pub fn default() -> Self { + Self { fd: -1, events: -1, revents: -1 } + } +} + +fn poll(pollfds: &mut [Pollfd], timeout: Option) -> SysResult { + let mut fdtable = current::fdtable().lock(); + + let mut count = 0u32; + let mut i = 0; + + let mut poll_files: Vec<(Arc, &mut Pollfd)> = pollfds.iter_mut() + .filter_map(|pfd| { + if pfd.fd < 0 { + count += 1; + return None; + } + + let file = match fdtable.get(pfd.fd as usize) { + Ok(f) => f, + Err(_) => { + count += 1; + return None; + } + }; + + let poll_set = PollEventSet::from_bits_truncate(pfd.events); + if let Some(event) = file.poll(i, poll_set).unwrap() { + pfd.revents = match event { + PollEvent::ReadReady => PollEventSet::POLLIN.bits(), + PollEvent::WriteReady => PollEventSet::POLLOUT.bits(), + PollEvent::Priority => PollEventSet::POLLPRI.bits(), + PollEvent::HangUp => PollEventSet::POLLHUP.bits(), + }; + count += 1; + } + // kinfo!("poll: fd={}, events={:#x}, revents={:#x}", pfd.fd, pfd.events, pfd.revents); + + i += 1; + Some((file, pfd)) + }) + .collect(); + + drop(fdtable); + + if count != 0 { + return Ok(count as usize); + } + + if let Some(timeout) = timeout { + timer::add_timer(current::task().clone(), timeout); + } + + // start polling + let event = current::block("poll"); + + let (poll_event, waker) = match event { + Event::Poll{ event, waker} => (event, waker), + Event::Timeout => return Ok(0), // Timeout occurred + Event::Signal => return Err(Errno::EINTR), // Interrupted by other events + _ => unreachable!("Invalid event type in poll: {:?}", event), + }; + + debug_assert!(waker < poll_files.len()); + + poll_files.iter_mut().enumerate().for_each(|(i, (file, pfd))| { + if i != waker { + file.poll_cancel(); + pfd.revents = 0; + } + }); + + poll_files[waker].1.revents = match poll_event { + PollEvent::ReadReady => PollEventSet::POLLIN.bits(), + PollEvent::WriteReady => PollEventSet::POLLOUT.bits(), + PollEvent::Priority => PollEventSet::POLLPRI.bits(), + PollEvent::HangUp => PollEventSet::POLLHUP.bits(), + }; + + Ok(1) +} + +pub fn ppoll_time32(uptr_ufds: UArray, nfds: usize, uptr_timeout: UPtr, _uptr_sigmask: usize, _sigmask_size: usize) -> SysResult { + if nfds == 0 { + return Ok(0); + } + + uptr_ufds.should_not_null()?; + + let mut pollfds = vec![Pollfd::default(); nfds]; + uptr_ufds.read(0, &mut pollfds)?; + + let timeout = if !uptr_timeout.is_null() { + let ts = uptr_timeout.read()?; + if ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= 1_000_000_000 { + return Err(Errno::EINVAL); + } + Some(ts.into()) + } else { + None + }; + + let r = poll(&mut pollfds, timeout)?; + + uptr_ufds.write(0, &pollfds)?; + + Ok(r) +} + + +// TODO: implement the setitimer syscall +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct ITimerValue { + pub it_interval: uapi::TimeVal, + pub it_value: uapi::TimeVal, +} + +impl UserStruct for ITimerValue {} + +pub fn setitimer(_which: usize, _uptr_new_value: UPtr, _uptr_old_value: UPtr) -> SysResult { + Err(Errno::ENOSYS) +} diff --git a/src/kernel/syscall/fs.rs b/src/kernel/syscall/fs.rs new file mode 100644 index 0000000..6537da5 --- /dev/null +++ b/src/kernel/syscall/fs.rs @@ -0,0 +1,802 @@ +use core::usize; +use core::time::Duration; + +use alloc::sync::Arc; +use bitflags::bitflags; +use num_enum::TryFromPrimitive; + +use crate::arch::get_time_us; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::scheduler::current::{copy_from_user, copy_to_user}; +use crate::kernel::scheduler::*; +use crate::kernel::syscall::uptr::{UserPointer, UArray, UBuffer, UString, UPtr}; +use crate::kernel::syscall::{SyscallRet, UserStruct}; +use crate::kernel::task::fdtable::FDFlags; +use crate::kernel::uapi::{Dirent, DirentType, FileStat, OpenFlags, Statfs, Timespec}; +use crate::fs::{Dentry, Mode, Perm, PermFlags}; +use crate::fs::vfs; +use crate::fs::file::{File, FileFlags, FileOps, SeekWhence}; + +use super::def::*; + +pub fn dup(oldfd: usize) -> SyscallRet { + let mut fdtable = current::fdtable().lock(); + let file = fdtable.get(oldfd)?; + let newfd = fdtable.push(file.clone(), FDFlags::empty())?; + Ok(newfd) +} + +pub fn dup2(oldfd: usize, newfd: usize) -> SyscallRet { + let mut fdtable = current::fdtable().lock(); + if oldfd == newfd { + // If oldfd and newfd are the same, just return newfd + fdtable.get(oldfd)?; // Check if oldfd is valid + return Ok(newfd); + } + + let file = fdtable.get(oldfd)?; + fdtable.set(newfd, file, FDFlags::empty())?; + + Ok(newfd) +} + +#[allow(non_camel_case_types)] +#[derive(TryFromPrimitive)] +#[repr(usize)] +pub enum FcntlCmd { + F_DUPFD = 0, + F_GETFD = 1, + F_SETFD = 2, + F_GETFL = 3, + F_SETFL = 4, + F_DUPFD_CLOEXEC = 1030, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct FDArgs: usize { + const FD_CLOEXEC = 1; + } +} + +pub fn fcntl64(fd: usize, cmd: usize, arg: usize) -> SyscallRet { + match FcntlCmd::try_from(cmd).map_err(|_| Errno::EINVAL)? { + FcntlCmd::F_DUPFD_CLOEXEC => { + let mut fdtable = current::fdtable().lock(); + let file = fdtable.get(fd)?; + let fd = fdtable.push(file, FDFlags { cloexec: true })?; + Ok(fd) + } + + FcntlCmd::F_GETFL => { + let file = current::fdtable().lock().get(fd)?; + let mut open_flags = OpenFlags::O_RDONLY; + if file.readable() && file.writable() { + open_flags = OpenFlags::O_RDWR; + } else if file.writable() { + open_flags = OpenFlags::O_WRONLY; + } + Ok(open_flags.bits()) + } + + FcntlCmd::F_SETFL => { + let file = current::fdtable().lock().get(fd)?; + let flags = FDArgs::from_bits(arg).ok_or(Errno::EINVAL)?; + current::fdtable().lock().set(fd, file, FDFlags { + cloexec: flags.contains(FDArgs::FD_CLOEXEC), + })?; + + Ok(0) + } + + FcntlCmd::F_SETFD => { + let flags = FDArgs::from_bits(arg).ok_or(Errno::EINVAL)?; + + let mut fdtable = current::fdtable().lock(); + let mut fdflags = fdtable.get_fd_flags(fd)?; + fdflags.cloexec = flags.contains(FDArgs::FD_CLOEXEC); + fdtable.set_fd_flags(fd, fdflags)?; + + Ok(0) + } + + _ => Err(Errno::EINVAL), + } +} + +pub fn openat(dirfd: usize, uptr_filename: UString, flags: usize, mode: usize) -> SyscallRet { + uptr_filename.should_not_null()?; + + let open_flags = OpenFlags::from_bits(flags).ok_or(Errno::EINVAL)?; + let readable = open_flags.contains(OpenFlags::O_RDONLY) || open_flags.contains(OpenFlags::O_RDWR); + let writable = open_flags.contains(OpenFlags::O_WRONLY) || open_flags.contains(OpenFlags::O_RDWR); + let file_flags = FileFlags { + writable, + readable, + blocked: !open_flags.contains(OpenFlags::O_NONBLOCK) + }; + let fd_flags = FDFlags { + cloexec: open_flags.contains(OpenFlags::O_CLOEXEC), + }; + + let path = uptr_filename.read()?; + + let helper = |parent: &Arc| { + if open_flags.contains(OpenFlags::O_TMPFILE) { + if !open_flags.contains(OpenFlags::O_WRONLY) || open_flags.contains(OpenFlags::O_RDWR) { + return Err(Errno::EINVAL) + } + + let dentry = vfs::load_dentry_at(parent, &path)?; + return vfs::create_temp(&dentry, file_flags, Mode::from_bits(mode as u32 & 0o777).ok_or(Errno::EINVAL)? | Mode::S_IFREG); + } + + let mut perm_flags = PermFlags::empty(); + if readable { perm_flags.insert(PermFlags::R); } + if writable { perm_flags.insert(PermFlags::W); } + + match vfs::openat_file(parent, &path, file_flags, &Perm::new(perm_flags)) { + Ok(file) => { + if open_flags.contains(OpenFlags::O_CREATE) && open_flags.contains(OpenFlags::O_EXCL) { + return Err(Errno::EEXIST); + } + Ok(file) + } + Err(e) => { + if e == Errno::ENOENT && open_flags.contains(OpenFlags::O_CREATE) { + // Create the file + let mode = Mode::from_bits(mode as u32 & 0o777 & !current::umask()).ok_or(Errno::EINVAL)? | Mode::S_IFREG; + let (parent_dentry, child_name) = vfs::load_parent_dentry_at(parent, &path)?; + parent_dentry.create(&child_name, mode)?; + vfs::openat_file(parent, &path, file_flags, &Perm::new(perm_flags)) + } else { + Err(e) + } + } + } + }; + + let file = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| helper(cwd))? + } else { + helper(vfs::get_root_dentry())? + }; + + let fd = current::fdtable().lock().push(Arc::new(file), fd_flags)?; + + Ok(fd) +} + +pub fn read(fd: usize, ubuf: UBuffer, count: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if !file.readable() { + return Err(Errno::EBADF); + } + + if count == 0 { + return Ok(0); + } + + ubuf.should_not_null()?; + + let mut buffer = [0u8; BUFFER_SIZE]; + let mut total_read = 0; + let mut left = count; + + while left > 0 { + let to_read = core::cmp::min(left, BUFFER_SIZE); + let bytes_read = file.read(&mut buffer[..to_read])?; + if bytes_read == 0 { + break; // EOF + } + + ubuf.write(total_read, &buffer[..bytes_read])?; + + total_read += bytes_read; + left -= bytes_read; + + if bytes_read < to_read { + break; // EOF + } + } + + Ok(total_read) +} + +pub fn readlinkat(dirfd: usize, uptr_path: UString, ubuf: UString, bufsize: usize) -> SyscallRet { + uptr_path.should_not_null()?; + ubuf.should_not_null()?; + + let path = uptr_path.read()?; + + // crate::kinfo!("readlinkat: dirfd={}, path=\"{}\"", dirfd as isize, path); + + let path = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_dentry_at(cwd, &path))? + } else { + vfs::load_dentry_at( + current::fdtable().lock().get(dirfd)?.get_dentry().ok_or(Errno::ENOTDIR)?, + &path + )? + }.readlink()?; + + ubuf.write(&path, bufsize)?; + + Ok(0) +} + +pub fn write(fd: usize, ubuf: UBuffer, mut count: usize) -> SyscallRet { + if count == 0 { + return Ok(0); + } + + ubuf.should_not_null()?; + + let file = current::fdtable().lock().get(fd)?; + if !file.writable() { + return Err(Errno::EBADF); + } + + let mut written = 0; + + let mut buffer = [0u8; BUFFER_SIZE]; + while count != 0 { + let to_write = core::cmp::min(count, BUFFER_SIZE); + ubuf.read(written, &mut buffer[..to_write])?; + + file.write(&buffer[..to_write])?; + + count -= to_write; + written += to_write; + } + + Ok(written) +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct IOVec { + pub base: usize, + pub len: usize, +} + +impl UserStruct for IOVec {} + +pub fn readv(fd: usize, uptr_iov: UPtr, iovcnt: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if iovcnt == 0 { + return Ok(0); + } + + uptr_iov.should_not_null()?; + + let mut total_read = 0; + + for i in 0..iovcnt { + let iov = uptr_iov.add(i).read()?; + + let mut read = 0usize; + let mut remaining = iov.len; + let mut buffer = [0u8; BUFFER_SIZE]; + while remaining != 0 { + let to_read = core::cmp::min(remaining, BUFFER_SIZE); + let bytes_read = file.read(&mut buffer[..to_read])?; + if bytes_read == 0 { + break; // EOF + } + + copy_to_user::buffer(iov.base + read, &buffer[..bytes_read])?; + + remaining -= bytes_read; + read += bytes_read; + } + + total_read += read; + } + + Ok(total_read) +} + +pub fn pread64(fd: usize, ubuf: UBuffer, count: usize, pos: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if count == 0 { + return Ok(0) + } + + let mut written = 0; + let mut buffer = [0u8; BUFFER_SIZE]; + let mut left = count; + + while left != 0 { + let to_read = core::cmp::min(left, BUFFER_SIZE); + let bytes_read = file.pread(&mut buffer[..to_read], pos + (count - left))?; + if bytes_read == 0 { + break; // EOF + } + + ubuf.write(count - left, &buffer[..bytes_read])?; + + left -= bytes_read; + written += bytes_read; + + if bytes_read < to_read { + break; // EOF + } + } + + Ok(written) +} + +pub fn pwrite64(fd: usize, ubuf: UBuffer, count: usize, pos: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if count == 0 { + return Ok(0) + } + + let mut written = 0; + let mut buffer = [0u8; BUFFER_SIZE]; + let mut left = count; + + while left != 0 { + let to_write = core::cmp::min(left, BUFFER_SIZE); + ubuf.read(count - left, &mut buffer[..to_write])?; + + let bytes_written = file.pwrite(&buffer[..to_write], pos + (count - left))?; + if bytes_written == 0 { + break; // EOF + } + + left -= bytes_written; + written += bytes_written; + + if bytes_written < to_write { + break; // EOF + } + } + + Ok(written) +} + +pub fn writev(fd: usize, uptr_iov: UPtr, iovcnt: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if iovcnt == 0 { + return Ok(0); + } + + uptr_iov.should_not_null()?; + + let mut total_written = 0; + + for i in 0..iovcnt { + let iov = uptr_iov.add(i).read()?; + + let mut written = 0usize; + let mut remaining = iov.len; + let mut buffer = [0u8; BUFFER_SIZE]; + while remaining != 0 { + let to_write = core::cmp::min(remaining, BUFFER_SIZE); + copy_from_user::buffer(iov.base + written, &mut buffer[..to_write]) + .map_err(|_| Errno::EFAULT)?; + + let bytes_written = file.write(&buffer[..to_write]).map_err(|_| Errno::EIO)?; + if bytes_written != to_write { + break; // EOF + } + + remaining -= to_write; + written += to_write; + } + + total_written += written; + } + + Ok(total_written) +} + +pub fn lseek(fd: usize, offset: usize, how: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + let how = match how { + 0 => SeekWhence::BEG, + 1 => SeekWhence::CUR, + 2 => SeekWhence::END, + _ => return Err(Errno::EINVAL), + }; + + file.seek(offset as isize, how) +} + +pub fn close(fd: usize) -> Result { + current::fdtable().lock().close(fd)?; + + Ok(0) +} + +pub fn sendfile(out_fd: usize, in_fd: usize, uptr_offset: UPtr, count: usize) -> SyscallRet { + let mut fdtable = current::fdtable().lock(); + let out_file = fdtable.get(out_fd)?; + let in_file = fdtable.get(in_fd)?.downcast_arc::().map_err(|_| Errno::EINVAL)?; + drop(fdtable); // Release lock early + + if !out_file.writable() { + return Err(Errno::EBADF); + } + if !in_file.readable() { + return Err(Errno::EBADF); + } + + let in_file_offset = in_file.seek(0, SeekWhence::CUR)?; + let mut local_offset = if uptr_offset.is_null() { + in_file_offset + } else { + uptr_offset.read()? + }; + + let mut total_sent = 0; + let mut left = count; + + let mut buffer = [0u8; BUFFER_SIZE]; + + while left > 0 { + let to_read = core::cmp::min(left, BUFFER_SIZE); + let bytes_read = in_file.read_at(&mut buffer[..to_read], local_offset)?; + if bytes_read == 0 { + break; // EOF + } + + let bytes_written = out_file.write(&buffer[..bytes_read])?; + if bytes_written == 0 { + break; // Can't write more + } + + local_offset += bytes_read; + total_sent += bytes_written; + left -= bytes_written; + + if bytes_read < to_read { + break; // EOF + } + + if bytes_written < bytes_read { + break; // Can't write more + } + } + + if uptr_offset.is_null() { + in_file.seek(local_offset as isize, SeekWhence::BEG)?; + } else { + uptr_offset.write(local_offset)?; + } + + Ok(total_sent) +} + +pub fn ioctl(fd: usize, request: usize, arg: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + file.ioctl(request, arg) +} + +pub fn faccessat(dirfd: usize, uptr_path: UString, _mode: usize) -> SyscallRet { + uptr_path.should_not_null()?; + + let path = uptr_path.read()?; + + // crate::kinfo!("faccessat: dirfd={}, path=\"{}\"", dirfd, path); + + if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_dentry_at(cwd, &path))?; + } else { + let file = current::fdtable().lock().get(dirfd)?; + vfs::load_dentry_at(file.get_dentry().ok_or(Errno::ENOTDIR)?, &path)?; + } + + Ok(0) +} + +pub fn fstatat(dirfd: usize, uptr_path: UString, uptr_stat: UPtr, _flags: usize) -> SyscallRet { + uptr_path.should_not_null()?; + // uptr_stat.should_not_null()?; + + let path = uptr_path.read()?; + + // crate::kinfo!("fstatat: dirfd={}, path=\"{}\"", dirfd, path); + + let fstat = if path.is_empty() { + current::fdtable().lock().get(dirfd)?.fstat()? + } else { + if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::openat_file(cwd, &path, FileFlags::dontcare(), &Perm::dontcare()))? + } else { + vfs::openat_file( + current::fdtable().lock().get(dirfd)?.get_dentry().ok_or(Errno::ENOTDIR)?, + &path, + FileFlags::dontcare(), + &Perm::dontcare() + )? + }.fstat()? + }; + + uptr_stat.write(fstat)?; + + Ok(0) +} + +pub fn statfs64(uptr_path: UString, uptr_buf: UPtr) -> SyscallRet { + uptr_path.should_not_null()?; + uptr_buf.should_not_null()?; + + let path = uptr_path.read()?; + let dentry = current::with_cwd(|cwd| vfs::load_dentry_at(cwd, &path))?; + + let statfs = vfs::statfs(dentry.sno())?; + + uptr_buf.write(statfs)?; + + Ok(0) +} + +pub fn newfstat(fd: usize, uptr_stat: UPtr) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + let fstat = file.fstat()?; + + uptr_stat.write(fstat)?; + + Ok(0) +} + +const UTIME_NOW: u64 = 0x3fffffff; +const UTIME_OMIT: u64 = 0x3ffffffe; + +pub fn utimensat(dirfd: usize, uptr_path: UString, uptr_times: UArray, _flags: usize) -> SyscallRet { + uptr_times.should_not_null()?; + + let atime = uptr_times.index(0).read()?; + let mtime = uptr_times.index(1).read()?; + + let path = if uptr_path.is_null() { + "" + } else { + &uptr_path.read()? + }; + let dentry = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_dentry_at(cwd, &path))? + } else { + vfs::load_dentry_at( + current::fdtable().lock().get(dirfd)?.get_dentry().ok_or(Errno::ENOTDIR)?, + path + )? + }; + let inode = dentry.get_inode(); + let now = get_time_us(); + if atime.tv_nsec != UTIME_OMIT { + if atime.tv_nsec == UTIME_NOW { + let duration = Duration::new((now / 1_000_000) as u64, (now % 1_000_000 * 1000) as u32); + inode.update_atime(&duration)?; + } else { + let duration = Duration::new(atime.tv_sec, atime.tv_nsec as u32); + inode.update_atime(&duration)?; + } + } + + if mtime.tv_nsec != UTIME_OMIT { + if mtime.tv_nsec == UTIME_NOW { + let duration = Duration::new((now / 1_000_000) as u64, (now % 1_000_000 * 1000) as u32); + inode.update_mtime(&duration)?; + } else { + let duration = Duration::new(mtime.tv_sec, mtime.tv_nsec as u32); + inode.update_mtime(&duration)?; + } + } + + Ok(0) +} + +pub fn mkdirat(dirfd: usize, uptr_path: UString, mode: usize) -> SyscallRet { + if mode > 0o7777 { + return Err(Errno::EINVAL); + } + let mode = Mode::from_bits(mode as u32 & !current::umask()).ok_or(Errno::EINVAL)? | Mode::S_IFDIR; + uptr_path.should_not_null()?; + + let path = uptr_path.read()?; + + let (parent, name) = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_parent_dentry_at(cwd, &path))? + } else { + vfs::load_parent_dentry_at( + current::fdtable().lock().get(dirfd)?.get_dentry().ok_or(Errno::ENOTDIR)?, + &path + )? + }; + + parent.create(name, mode)?; + + Ok(0) +} + +const DIRENT_NAME_OFFSET: usize = 8 + 8 + 2 + 1; // d_ino + d_off + d_reclen + d_type + +pub fn getdents64(fd: usize, uptr_dirent: usize, count: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + let file = file.downcast_arc::().map_err(|_| Errno::EBADF)?; + + if uptr_dirent == 0 { + return Err(Errno::EINVAL); + } + + let mut total_copied = 0; + + loop { + let (dent, old_pos) = match file.get_dent() { + Ok(Some(d)) => d, + Ok(None) => { + if total_copied == 0 { + return Ok(0); // No more entries + } else { + break; + } + }, + Err(e) => { + return Err(e) + }, + }; + + let name = &dent.name; + let name_bytes = name.as_bytes(); + let name_len = core::cmp::min(name_bytes.len(), 255); + let reclen = DIRENT_NAME_OFFSET + name_len + 1; + let reclen_aligned = (reclen + 7) & !7; // Align to 8 bytes + + if total_copied + reclen_aligned > count { + // crate::kinfo!("getdents64: reached count limit total_copied={} reclen_aligned={} count={}", total_copied, reclen_aligned, count); + file.seek(old_pos as isize, SeekWhence::BEG)?; // Rewind one entry + break; + } + + let dirent = Dirent { + d_ino: dent.ino as u64, + d_off: 0, // Not used + d_reclen: reclen_aligned as u16, + d_type: DirentType::from(dent.file_type) as u8, + }; + + // Copy dirent to user space + let dirent_ptr = uptr_dirent + total_copied; + + copy_to_user::object(dirent_ptr, dirent)?; + copy_to_user::string(dirent_ptr + DIRENT_NAME_OFFSET, name, name_len + 1)?; + + total_copied += reclen_aligned; + } + + if total_copied == 0 { + Err(Errno::EINVAL) + } else { + Ok(total_copied) + } +} + +pub fn unlinkat(dirfd: usize, uptr_path: UString, _flags: usize) -> SyscallRet { + uptr_path.should_not_null()?; + + let path = uptr_path.read()?; + + let parent_dentry = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_parent_dentry_at(cwd, &path))? + } else { + vfs::load_parent_dentry(&path)? + }; + + let parent = parent_dentry.0; + let name = &parent_dentry.1; + + parent.unlink(name)?; + + Ok(0) +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct RenameFlags: usize { + const RENAME_NOREPLACE = 1; + const RENAME_EXCHANGE = 2; + const RENAME_WHITEOUT = 4; + } +} + +pub fn renameat2(olddirfd: usize, uptr_oldpath: UString, newdirfd: usize, uptr_newpath: UString, _flags: usize) -> SysResult { + uptr_oldpath.should_not_null()?; + uptr_newpath.should_not_null()?; + + let old_path = uptr_oldpath.read()?; + let new_path = uptr_newpath.read()?; + + let old_parent_dentry = if olddirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_parent_dentry_at(cwd, &old_path))? + } else { + vfs::load_parent_dentry(&old_path)? + }; + let new_parent_dentry = if newdirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_parent_dentry_at(cwd, &new_path))? + } else { + vfs::load_parent_dentry(&new_path)? + }; + + let old_parent = old_parent_dentry.0; + let old_name = old_parent_dentry.1; + let new_parent = new_parent_dentry.0; + let new_name = new_parent_dentry.1; + + if old_parent.sno() != new_parent.sno() { + return Err(Errno::EXDEV); // Cross-device link + } + + old_parent.rename(&old_name, &new_parent, &new_name)?; + + Ok(0) +} + +pub fn fchmodat(dirfd: usize, uptr_path: UString, mode: usize) -> SyscallRet { + if mode > 0o777 { + return Err(Errno::EINVAL); + } + let mode = Mode::from_bits(mode as u32).ok_or(Errno::EINVAL)?; + + uptr_path.should_not_null()?; + + let path = uptr_path.read()?; + + let dentry = if dirfd as isize == AT_FDCWD { + current::with_cwd(|cwd| vfs::load_dentry_at(cwd, &path))? + } else { + vfs::load_dentry_at( + current::fdtable().lock().get(dirfd)?.get_dentry().ok_or(Errno::ENOTDIR)?, + &path + )? + }; + + dentry.get_inode().chmod(mode)?; + + Ok(0) +} + +pub fn ftruncate64(fd: usize, length: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + if !file.writable() { + return Err(Errno::EBADF); + } + + file.downcast_arc::() + .map_err(|_| Errno::EINVAL)? + .ftruncate(length as u64)?; + + Ok(0) +} + +pub fn umask(mask: usize) -> SyscallRet { + if mask > 0o777 { + return Err(Errno::EINVAL); + } + + let pcb = current::pcb(); + let old_mask = pcb.umask(); + pcb.set_umask(mask as u16); + + Ok(old_mask as usize) +} + +pub fn fsync(fd: usize) -> SyscallRet { + let file = current::fdtable().lock().get(fd)?; + + file.fsync()?; + + Ok(0) +} diff --git a/src/kernel/syscall/futex.rs b/src/kernel/syscall/futex.rs new file mode 100644 index 0000000..ce72bda --- /dev/null +++ b/src/kernel/syscall/futex.rs @@ -0,0 +1,96 @@ +use num_enum::TryFromPrimitive; + +use crate::kernel::event::timer; +use crate::kernel::scheduler::current; +use crate::kernel::uapi; +use crate::kernel::errno::Errno; +use crate::kernel::syscall::uptr::{UserPointer, UPtr}; +use crate::kernel::syscall::SyscallRet; +use crate::kernel::usync::futex::{self, RobustListHead}; +use crate::kernel::event::Event; + +pub fn set_robust_list(u: UPtr) -> SyscallRet { + current::tcb().set_robust_list(u.uaddr()); + Ok(0) +} + +pub fn get_robust_list() -> SyscallRet { + match current::tcb().get_robust_list() { + Some(robust_list) => Ok(robust_list), + None => Ok(0), + } +} + + +#[repr(usize)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, TryFromPrimitive)] +enum FutexOp { + Wait = 0, + Wake = 1, + REQUEUE = 3, + WaitBitset = 9, + WakeBitset = 10, +} + +const FUTEX_OP_MASK: usize = 0x7f; + +pub fn futex( + uaddr: UPtr, + futex_op: usize, + val: usize, + timeout: UPtr, + uaddr2: UPtr<()>, + val3: usize +) -> SyscallRet { + uaddr.should_not_null()?; + if uaddr.uaddr() & 3 != 0 { + return Err(Errno::EINVAL); + } + + let op = FutexOp::try_from(futex_op & FUTEX_OP_MASK).map_err(|_| Errno::EINVAL)?; + // kinfo!("futex: uaddr={:#x}, futex_op={:?}, val={}, timeout={:?}", uaddr.uaddr(), op, val, timeout); + + match op { + FutexOp::Wait | FutexOp::WaitBitset => { + let kaddr = uaddr.kaddr()?; + + let bitset = if op == FutexOp::WaitBitset { + val3 as u32 + } else { + u32::MAX + }; + + futex::wait_current(kaddr, val as i32, bitset)?; + if let Some(timeout) = timeout.read_optional()? { + timer::add_timer(current::task().clone(), timeout.into()); + } + + let event = current::block("futex"); + + match event { + Event::Futex => { + Ok(0) + } + Event::Timeout => { + Err(Errno::ETIMEDOUT) + } + Event::Signal => { + Err(Errno::EINTR) + } + _ => unreachable!("state.event={:?}", event) + } + }, + FutexOp::Wake | FutexOp::WakeBitset => { + let kaddr = uaddr.kaddr()?; + let woken = futex::wake(kaddr, val as usize, u32::MAX)?; + Ok(woken as usize) + }, + + FutexOp::REQUEUE => { + let kaddr = uaddr.kaddr()?; + let kaddr2 = uaddr2.kaddr()?; + let n = futex::requeue(kaddr, kaddr2, val as usize, None)?; + Ok(n as usize) + }, + } +} diff --git a/src/kernel/syscall/ipc.rs b/src/kernel/syscall/ipc.rs new file mode 100644 index 0000000..c6459e2 --- /dev/null +++ b/src/kernel/syscall/ipc.rs @@ -0,0 +1,250 @@ +use alloc::sync::Arc; +use num_enum::TryFromPrimitive; +use core::time::Duration; +use bitflags::bitflags; + +use crate::kernel::config; +use crate::kernel::event::{timer, Event}; +use crate::kernel::ipc::{KSiFields, Pipe, SiCode, SignalSet}; +use crate::kernel::ipc::shm::{IpcGetFlag, IPC_RMID, IPC_SET, IPC_STAT}; +use crate::kernel::ipc::shm; +use crate::kernel::scheduler::{current, Tid}; +use crate::kernel::syscall::uptr::{UserPointer, UArray, UPtr}; +use crate::kernel::task::fdtable::FDFlags; +use crate::kernel::errno::Errno; +use crate::kernel::uapi; +use crate::kernel::task::manager; +use crate::arch; + +use super::SyscallRet; + +bitflags! { + struct PipeFlags: usize { + const O_CLOEXEC = 0x80000; + } +} + +pub fn pipe(uptr_pipefd: UArray, flags: usize) -> SyscallRet { + let flags = PipeFlags::from_bits_truncate(flags); + let fd_flags = FDFlags{ + cloexec: flags.contains(PipeFlags::O_CLOEXEC), + }; + + let (read_end, write_end) = Pipe::create(config::PIPE_CAPACITY); + let read_end = Arc::new(read_end); + let write_end = Arc::new(write_end); + + let (read_fd, write_fd); + { + let mut fdtable = current::fdtable().lock(); + read_fd = fdtable.push(read_end, fd_flags)?; + write_fd = fdtable.push(write_end, fd_flags)?; + } + + uptr_pipefd.write(0, &[read_fd as i32, write_fd as i32])?; + + Ok(0) +} + +pub fn kill(pid: usize, signum: usize) -> SyscallRet { + let pid = pid as i32; + let signum = signum as u32; + + if pid > 0 { + let pcb = manager::get(pid).ok_or(Errno::ESRCH)?; + pcb.send_signal( + signum.try_into()?, + SiCode::SI_USER, + KSiFields::kill(current::pid(), current::uid()), + None + )?; + } + + Ok(0) +} + +pub fn tkill(tid: usize, signum: usize) -> SyscallRet { + let tid = tid as Tid; + let signum = (signum as u32).try_into()?; + let parent = manager::find_task_parent(tid).ok_or(Errno::ESRCH)?; + parent.send_signal( + signum, + SiCode::SI_TKILL, + KSiFields::kill(current::pid(), current::uid()), + Some(tid) + )?; + + Ok(0) +} + +pub fn tgkill(tgid: usize, tid: usize, signum: usize) -> SyscallRet { + let tgid = tgid as i32; + let tid = tid as i32; + let signum = signum as u32; + + if tgid >= 0 { + let pcb = manager::get(tgid).ok_or(Errno::ESRCH)?; + pcb.send_signal( + signum.try_into()?, + SiCode::SI_TKILL, + KSiFields::kill(current::pid(), current::uid()), + Some(tid) + )?; + } + + Ok(0) +} + +#[repr(usize)] +#[derive(Debug, TryFromPrimitive)] +enum SigProcmaskHow { + Block = 0, + Unblock = 1, + Setmask = 2, +} + +pub fn rt_sigprocmask(how: usize, uptr_set: UPtr, uptr_oldset: UPtr) -> SyscallRet { + let how = SigProcmaskHow::try_from(how).map_err(|_| Errno::EINVAL)?; + + let mut signal_mask = current::tcb().signal_mask.lock(); + if !uptr_oldset.is_null() { + uptr_oldset.write(*signal_mask)?; + } + + if !uptr_set.is_null() { + let set = uptr_set.read()?; + *signal_mask = match how { + SigProcmaskHow::Block => *signal_mask | set, + SigProcmaskHow::Unblock => *signal_mask & !set, + SigProcmaskHow::Setmask => set, + }; + } + + Ok(0) +} + +pub fn rt_sigaction(signum: usize, uptr_act: UPtr, uptr_oldact: UPtr, sigsetsize: usize) -> SyscallRet { + assert!(sigsetsize == core::mem::size_of::()); + + let signum = (signum as u32).try_into()?; + + let mut signal_actions = current::signal_actions().lock(); + if !uptr_oldact.is_null() { + let old_action = signal_actions.get(signum); + uptr_oldact.write(old_action.into())?; + } + + if !uptr_act.is_null() { + let new_action = uptr_act.read()?; + let new_action = new_action.try_into()?; + + signal_actions.set(signum, &new_action)?; + } + + Ok(0) +} + +pub fn rt_sigsuspend(mask: UPtr) -> SyscallRet { + mask.should_not_null()?; + + let set = mask.read()?; + + let tcb = current::tcb(); + let mut signal_mask = tcb.signal_mask.lock(); + let old = *signal_mask; + *signal_mask = set; + + let event = current::block("sigsuspend"); + + *tcb.signal_mask.lock() = old; + + match event { + Event::Signal => Err(Errno::EINTR), + _ => unreachable!() + } +} + +pub fn rt_sig_return() -> SyscallRet { + current::tcb().return_from_signal(); + arch::return_to_user(); +} + +pub fn sigtimedwait(uptr_set: UPtr, _uptr_info: UPtr<()>, uptr_timeout: UPtr) -> SyscallRet { + uptr_set.should_not_null()?; + + let timeout = uptr_timeout.read_optional()?; + let signal_set = uptr_set.read()?; + + let mut state = current::tcb().state().lock(); + if let Some(pending) = state.pending_signal { + if signal_set.contains(pending.signum) { + state.pending_signal.take(); + return Ok(pending.signum.into()); + } + } + + if let Some(ts) = timeout { + let timeout_duration = Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32); + timer::add_timer(current::task().clone(), timeout_duration); + } + + state.signal_to_wait = signal_set; + + drop(state); + + let event = current::block("sigtimedwait"); + + match event { + Event::WaitSignal { signum } => Ok(signum.into()), + Event::Signal => Err(Errno::EINTR), + Event::Timeout => Err(Errno::EAGAIN), + _ => unreachable!(), + } +} + +pub fn shmget(key: usize, size: usize, shmflg: usize) -> SyscallRet { + let flags = IpcGetFlag::from_bits_truncate(shmflg); + let shmid = shm::get_or_create_shm(key, size, flags)?; + Ok(shmid) +} + +pub fn shmat(shmid: usize, shmaddr: usize, shmflg: usize) -> SyscallRet { + let addr_space = current::addrspace(); + let flags = shm::ShmFlag::from_bits_truncate(shmflg); + let addr = shm::attach_shm(shmid, addr_space, shmaddr, flags)?; + Ok(addr) +} + +pub fn shmctl(shmid: usize, cmd: usize, _buf: usize) -> SyscallRet { + match cmd { + IPC_RMID => { + shm::mark_remove_shm(shmid)?; + Ok(0) + } + IPC_STAT => { + // TODO: Implement IPC_STAT + Err(Errno::ENOSYS) + } + IPC_SET => { + // TODO: Implement IPC_SET + Err(Errno::ENOSYS) + } + _ => Err(Errno::EINVAL), + } +} + +pub fn shmdt(_shmaddr: usize) -> SyscallRet { + // TODO: Implement shmdt based on address + // Currently our manager uses shmid to detach, but syscall uses address. + // We need to find shmid from address or change manager to support detach by address. + // For now, return ENOSYS or implement a lookup. + + // Since we don't have reverse lookup yet, let's leave it as TODO or + // we can iterate over areas in addrspace to find the shm area? + // But shm_manager needs shmid. + + // Real implementation would look up the VMA at shmaddr, check if it's a SHM VMA, + // get the shmid/shm object from it, and then detach. + + Err(Errno::ENOSYS) +} diff --git a/src/kernel/syscall/misc.rs b/src/kernel/syscall/misc.rs new file mode 100644 index 0000000..b7bfcc0 --- /dev/null +++ b/src/kernel/syscall/misc.rs @@ -0,0 +1,224 @@ +use num_enum::TryFromPrimitive; +use alloc::vec; + +use crate::fs::vfs; +use crate::kernel::scheduler::current; +use crate::kernel::config; +use crate::kernel::errno::Errno; +use crate::kernel::syscall::uptr::{UserPointer, UBuffer, UPtr}; +use crate::kernel::syscall::{SyscallRet, UserStruct}; +use crate::kernel::uapi; +use crate::klib::random::random; +use crate::arch; + +pub fn rseq() -> Result { + // This syscall is a no-op in the current implementation. + // It is provided for compatibility with the Linux API. + Ok(0) +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Utsname { + pub sysname: [u8; 65], + pub nodename: [u8; 65], + pub release: [u8; 65], + pub version: [u8; 65], + pub machine: [u8; 65], + pub domainname: [u8; 65], +} + +impl UserStruct for Utsname {} + +impl Utsname { + pub fn new() -> Self { + let mut ustname = Utsname { + sysname: [0; 65], + nodename: [0; 65], + release: [0; 65], + version: [0; 65], + machine: [0; 65], + domainname: [0; 65], + }; + let sysname = b"KernelX"; + ustname.sysname[..sysname.len()].copy_from_slice(sysname); + + // let release = option_env!("KERNELX_RELEASE").unwrap_or("0.1.0"); + let release = "5.0.0" ; + ustname.release[..release.len()].copy_from_slice(release.as_bytes()); + + let machine = b"riscv64"; + ustname.machine[..machine.len()].copy_from_slice(machine); + + ustname + } +} + +pub fn newuname(uptr_uname: UPtr) -> Result { + uptr_uname.write(Utsname::new())?; + + Ok(0) +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RLimit { + rlim_cur: usize, + rlim_max: usize, +} + +impl UserStruct for RLimit {} + +#[repr(usize)] +#[derive(TryFromPrimitive)] +enum RLimitResource { + STACK = 3, + NOFILE = 7, +} + +pub fn prlimit64(_pid: usize, resource: usize, uptr_new_limit: UPtr, uptr_old_limit: UPtr) -> SyscallRet { + // crate::kinfo!("prlimit64: pid={}, resource={}, uptr_new={}, uptr_old={}", _pid, resource, uptr_new_limit.uaddr(), uptr_old_limit.uaddr()); + + let resource = RLimitResource::try_from(resource).map_err(|_| Errno::EINVAL)?; + + match resource { + RLimitResource::STACK => { + if !uptr_old_limit.is_null() { + let stack_size = config::USER_STACK_PAGE_COUNT_MAX * arch::PGSIZE; + let old_limit = RLimit { + rlim_cur: stack_size, + rlim_max: stack_size, + }; + uptr_old_limit.write(old_limit)?; + } + + if !uptr_new_limit.is_null() { + let new_limit = uptr_new_limit.read()?; + if new_limit.rlim_cur != new_limit.rlim_max { + return Err(Errno::EINVAL); + } + } + } + + RLimitResource::NOFILE => { + let mut fdtable = current::fdtable().lock(); + if !uptr_old_limit.is_null() { + let old_limit = RLimit { + rlim_cur: fdtable.get_max_fd(), + rlim_max: fdtable.get_max_fd(), + }; + uptr_old_limit.write(old_limit)?; + } + + if !uptr_new_limit.is_null() { + let new_limit = uptr_new_limit.read()?; + if new_limit.rlim_cur != new_limit.rlim_max || new_limit.rlim_max > config::MAX_FD { + return Err(Errno::EINVAL); + } + + fdtable.set_max_fd(new_limit.rlim_max); + } + } + } + + Ok(0) +} + +pub fn getrandom(ubuf: UBuffer, len: usize, _flags: usize) -> SyscallRet { + ubuf.should_not_null()?; + + let mut buf = vec![0u8; len]; + for chunk in buf.chunks_mut(4) { + let rand = random(); + for (i, byte) in chunk.iter_mut().enumerate() { + *byte = ((rand >> (i * 4)) & 0xFF) as u8; + } + } + + ubuf.write(0, &buf)?; + + Ok(len) +} + +pub fn membarrier() -> SyscallRet { + Ok(0) +} + +pub fn get_mempolicy() -> SyscallRet { + // TODO: Not implemented yet + Ok(0) +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Rusage { + ru_utime: uapi::TimeVal, // user CPU time used + ru_stime: uapi::TimeVal, // system CPU time used + ru_maxrss: isize, // maximum resident set size + ru_ixrss: isize, // integral shared memory size + ru_idrss: isize, // integral unshared data size + ru_isrss: isize, // integral unshared stack size + ru_minflt: isize, // page reclaims (soft page faults) + ru_majflt: isize, // page faults (hard page faults) + ru_nswap: isize, // swaps + ru_inblock: isize, // block input operations + ru_oublock: isize, // block output operations + ru_msgsnd: isize, // IPC messages sent + ru_msgrcv: isize, // IPC messages received + ru_nsignals: isize, // signals received + ru_nvcsw: isize, // voluntary context switches + ru_nivcsw: isize, // involuntary context switches +} + +impl UserStruct for Rusage {} + +impl Default for Rusage { + fn default() -> Self { + Rusage { + ru_utime: uapi::TimeVal { tv_sec: 0, tv_usec: 0 }, + ru_stime: uapi::TimeVal { tv_sec: 0, tv_usec: 0 }, + ru_maxrss: 0, + ru_ixrss: 0, + ru_idrss: 0, + ru_isrss: 0, + ru_minflt: 0, + ru_majflt: 0, + ru_nswap: 0, + ru_inblock: 0, + ru_oublock: 0, + ru_msgsnd: 0, + ru_msgrcv: 0, + ru_nsignals: 0, + ru_nvcsw: 0, + ru_nivcsw: 0, + } + } +} + +#[repr(usize)] +#[derive(TryFromPrimitive)] +pub enum RusageWho { + SELF = 0, +} + +pub fn getrusage(who: usize, uptr_rusage: UPtr) -> SyscallRet { + let who = RusageWho::try_from(who).map_err(|_| Errno::EINVAL)?; + + let mut rusage = Rusage::default(); + + match who { + RusageWho::SELF => { + let (utime, stime) = current::pcb().tasks_usage_time(); + rusage.ru_utime = utime.into(); + rusage.ru_stime = stime.into(); + } + }; + + uptr_rusage.write(rusage)?; + + Ok(0) +} + +pub fn sync() -> SyscallRet { + vfs::sync_all().map(|_| 0) +} diff --git a/src/kernel/syscall/mm.rs b/src/kernel/syscall/mm.rs new file mode 100644 index 0000000..dd56906 --- /dev/null +++ b/src/kernel/syscall/mm.rs @@ -0,0 +1,170 @@ +use alloc::boxed::Box; +use bitflags::bitflags; + +use crate::fs::file::{File, FileOps}; +use crate::kernel::mm::MapPerm; +use crate::kernel::mm::maparea::{Area, AnonymousArea, PrivateFileMapArea, SharedFileMapArea}; +use crate::kernel::scheduler::*; +use crate::kernel::errno::Errno; +use crate::kernel::syscall::SyscallRet; +use crate::arch; +use crate::ktrace; + +pub fn brk(brk: usize) -> SyscallRet { + let r = current::addrspace().increase_userbrk(brk); + r +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct MMapProt: usize { + const READ = 0x1; + const WRITE = 0x2; + const EXEC = 0x4; + } +} + +impl Into for MMapProt { + fn into(self) -> MapPerm { + let mut perm = MapPerm::U; + if self.contains(MMapProt::READ ) { perm |= MapPerm::R; } + if self.contains(MMapProt::WRITE) { perm |= MapPerm::W; } + if self.contains(MMapProt::EXEC ) { perm |= MapPerm::X; } + perm + } +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct MMapFlags: usize { + const SHARED = 0x001; // Shared mapping + const PRIVATE = 0x002; // Private mapping + const FIXED = 0x010; // Fixed address mapping + const ANONYMOUS = 0x020; // Anonymous mapping + const DENYWRITE = 0x800; // Deny write access + const NORESERVE = 0x4000; // Do not reserve swap space + const MAP_STACK = 0x20000; + } +} + +pub fn mmap(addr: usize, length: usize, prot: usize, flags: usize, fd: usize, offset: usize) -> SyscallRet { + let flags = MMapFlags::from_bits(flags).ok_or(Errno::EINVAL)?; + + if addr % arch::PGSIZE != 0 || length == 0 { + return Err(Errno::EINVAL); + } + + let prot = MMapProt::from_bits(prot).ok_or(Errno::EINVAL)?; + + let mut perm = MapPerm::U; + if prot.contains(MMapProt::READ ) { perm |= MapPerm::R; } + if prot.contains(MMapProt::WRITE) { perm |= MapPerm::W; } + if prot.contains(MMapProt::EXEC ) { perm |= MapPerm::X; } + + let mut area: Box = if flags.contains(MMapFlags::ANONYMOUS) { + if fd != usize::MAX { + return Err(Errno::EINVAL); + } + + let page_count = (length + arch::PGSIZE - 1) / arch::PGSIZE; + let shared = flags.contains(MMapFlags::SHARED); + Box::new(AnonymousArea::new(0, perm, page_count, shared)) + } else { + if offset % arch::PGSIZE != 0 { + return Err(Errno::EINVAL); + } + + let file = current::fdtable() + .lock() + .get(fd)? + .downcast_arc::() + .map_err(|_| Errno::EINVAL)?; + + let inode = file.get_inode().unwrap().clone(); + let index = file.get_dentry().unwrap().get_inode_index(); + + if flags.contains(MMapFlags::SHARED) { + // if length % arch::PGSIZE != 0 { + // return Err(Errno::EINVAL); + // } + + let pagecount = (length + arch::PGSIZE - 1) / arch::PGSIZE; + Box::new(SharedFileMapArea::new( + 0, + perm, + inode, + index, + offset, + pagecount, + )) + } else { + Box::new(PrivateFileMapArea::new( + 0, + perm, + file, + offset, + length + )) + } + }; + + current::addrspace().with_map_manager_mut(|map_manager| { + let fixed = flags.contains(MMapFlags::FIXED); + let ubase = if addr == 0 || (!fixed && map_manager.is_range_mapped(addr, length)) { + map_manager.find_mmap_ubase((length + arch::PGSIZE - 1) / arch::PGSIZE).ok_or(Errno::ENOMEM)? + } else { + addr + }; + + area.set_ubase(ubase); + + if fixed { + map_manager.map_area_fixed(ubase, area, current::addrspace().pagetable()); + } else { + map_manager.map_area(ubase, area); + } + + Ok(ubase) + }) +} + +pub fn munmap(addr: usize, length: usize) -> SyscallRet { + if addr % arch::PGSIZE != 0 || length == 0 || length % arch::PGSIZE != 0 { + return Err(Errno::EINVAL); + } + + let page_count = (length + arch::PGSIZE - 1) / arch::PGSIZE; + + current::addrspace().with_map_manager_mut(|map_manager| { + map_manager.unmap_area(addr, page_count, current::addrspace().pagetable()) + })?; + + Ok(0) +} + +pub fn mprotect(addr: usize, length: usize, prot: usize) -> SyscallRet { + let prot = MMapProt::from_bits(prot).ok_or(Errno::EINVAL)?; + + ktrace!("mprotect called: addr={:#x}, length={}, prot={:#x}", addr, length, prot); + + if length == 0 || length % arch::PGSIZE != 0 || addr % arch::PGSIZE != 0 { + return Err(Errno::EINVAL); + } + + // Align up length to page size + let page_count = (length + arch::PGSIZE - 1) / arch::PGSIZE; + + current::addrspace().set_area_perm(addr, page_count, prot.into())?; + + Ok(0) +} + +pub fn msync(addr: usize, length: usize, flags: usize) -> SyscallRet { + // Currently no-op + Ok(0) +} + +pub fn madvise() -> SyscallRet { + // Currently no-op + Ok(0) +} diff --git a/src/kernel/syscall/mod.rs b/src/kernel/syscall/mod.rs new file mode 100644 index 0000000..044b5dd --- /dev/null +++ b/src/kernel/syscall/mod.rs @@ -0,0 +1,21 @@ +mod task; +mod mm; +mod fs; +mod misc; +mod time; +mod event; +mod ipc; +mod uid; +mod futex; +mod def; + +mod num; +mod uptr; + +pub use num::syscall; +pub use uptr::UserStruct; + +use crate::kernel::errno::SysResult; + +pub type Args = [usize; 7]; +pub type SyscallRet = SysResult; diff --git a/src/kernel/syscall/num.rs b/src/kernel/syscall/num.rs new file mode 100644 index 0000000..393eef0 --- /dev/null +++ b/src/kernel/syscall/num.rs @@ -0,0 +1,275 @@ +use crate::kernel::errno::Errno; +use super::*; + +macro_rules! syscall_table { + ( + $num_var:ident, $args_var:ident; + $( + $num:literal => $handler:ident :: $func:ident ( $arg_count:tt ) + ),* $(,)? + ) => { + match $num_var { + $( + $num => { + syscall_table!(@trace_enter $num, stringify!($func), $arg_count, $args_var); + let result = syscall_table!(@call $handler :: $func, $arg_count, $args_var); + syscall_table!(@trace_result $num, stringify!($func), $arg_count, $args_var, &result); + result + }, + )* + _ => { + #[cfg(feature = "warn-unimplemented-syscall")] + crate::kwarn!("Unsupported syscall: {}, user_pc={:#x}, tid={}", $num_var, crate::arch::get_user_pc(), crate::kernel::scheduler::current::tid()); + Err(Errno::ENOSYS) + } + } + }; + + (@trace_enter $num:expr, $name:expr, 0, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[], tid={}", $num, $name, $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 1, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}], tid={}", $num, $name, $args[0], $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 2, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}, {:#x}], tid={}", $num, $name, $args[0], $args[1], $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 3, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}, {:#x}, {:#x}], tid={}", $num, $name, $args[0], $args[1], $args[2], $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 4, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}, {:#x}, {:#x}, {:#x}], tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 5, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}], tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], $crate::kernel::scheduler::current::tid()); + } + }; + (@trace_enter $num:expr, $name:expr, 6, $args:ident) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + println!("[SYSCALL] {} ({}): ENTER args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}], tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $crate::kernel::scheduler::current::tid()); + } + }; + + (@trace_result $num:expr, $name:expr, 0, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[] -> Ok({:#x}), tid={}", $num, $name, value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[] -> Err({:?}), tid={}", $num, $name, errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 1, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 2, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], $args[1], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], $args[1], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 3, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], $args[1], $args[2], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], $args[1], $args[2], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 4, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 5, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + (@trace_result $num:expr, $name:expr, 6, $args:ident, $result:expr) => { + #[cfg(feature = "log-trace-syscall")] + { + use crate::println; + match $result { + Ok(value) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}] -> Ok({:#x}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], value, $crate::kernel::scheduler::current::tid()), + Err(errno) => println!("[SYSCALL] {} ({}): args=[{:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}] -> Err({:?}), tid={}", $num, $name, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], errno, $crate::kernel::scheduler::current::tid()), + } + } + }; + + (@call $handler:ident :: $func:ident, 0, $args:ident) => { + $handler::$func() + }; + (@call $handler:ident :: $func:ident, 1, $args:ident) => { + $handler::$func($args[0].into()) + }; + (@call $handler:ident :: $func:ident, 2, $args:ident) => { + $handler::$func($args[0].into(), $args[1].into()) + }; + (@call $handler:ident :: $func:ident, 3, $args:ident) => { + $handler::$func($args[0].into(), $args[1].into(), $args[2].into()) + }; + (@call $handler:ident :: $func:ident, 4, $args:ident) => { + $handler::$func($args[0].into(), $args[1].into(), $args[2].into(), $args[3].into()) + }; + (@call $handler:ident :: $func:ident, 5, $args:ident) => { + $handler::$func($args[0].into(), $args[1].into(), $args[2].into(), $args[3].into(), $args[4].into()) + }; + (@call $handler:ident :: $func:ident, 6, $args:ident) => { + $handler::$func($args[0].into(), $args[1].into(), $args[2].into(), $args[3].into(), $args[4].into(), $args[5].into()) + }; +} + +pub fn syscall(num: usize, args: &Args) -> Result { + syscall_table! { + num, args; + + // Filesystem + 23 => fs::dup(1), + 24 => fs::dup2(2), + 25 => fs::fcntl64(3), + 29 => fs::ioctl(3), + 34 => fs::mkdirat(3), + 35 => fs::unlinkat(3), + 43 => fs::statfs64(2), + 46 => fs::ftruncate64(2), + 48 => fs::faccessat(3), + 53 => fs::fchmodat(3), + 56 => fs::openat(4), + 57 => fs::close(1), + 61 => fs::getdents64(3), + 62 => fs::lseek(3), + 63 => fs::read(3), + 64 => fs::write(3), + 65 => fs::readv(3), + 66 => fs::writev(3), + 67 => fs::pread64(4), + 68 => fs::pwrite64(4), + 71 => fs::sendfile(4), + 78 => fs::readlinkat(4), + 79 => fs::fstatat(4), + 80 => fs::newfstat(2), + 82 => fs::fsync(1), + 88 => fs::utimensat(4), + 166 => fs::umask(1), + 276 => fs::renameat2(5), + + // Task + 17 => task::getcwd(2), + 49 => task::chdir(1), + 93 => task::exit(1), + 94 => task::exit_group(1), + 96 => task::set_tid_address(1), + 124 => task::sched_yield(0), + 157 => task::setsid(0), + 172 => task::getpid(0), + 173 => task::getppid(0), + 178 => task::gettid(0), + 220 => task::clone(5), + 221 => task::execve(3), + 260 => task::wait4(4), + + // Memory + 214 => mm::brk(1), + 215 => mm::munmap(2), + 222 => mm::mmap(6), + 226 => mm::mprotect(3), + 227 => mm::msync(3), + 233 => mm::madvise(0), + + // futex + 98 => futex::futex(6), + 99 => futex::set_robust_list(1), + 100 => futex::get_robust_list(0), + + // misc + 81 => misc::sync(0), + 160 => misc::newuname(1), + 165 => misc::getrusage(2), + 236 => misc::get_mempolicy(0), + 261 => misc::prlimit64(4), + 278 => misc::getrandom(3), + 283 => misc::membarrier(0), + 293 => misc::rseq(0), + + 174 => uid::getuid(0), + 175 => uid::geteuid(0), + 176 => uid::getgid(0), + 177 => uid::getegid(0), + + // IPC + 59 => ipc::pipe(2), + 129 => ipc::kill(2), + 130 => ipc::tkill(2), + 131 => ipc::tgkill(3), + 133 => ipc::rt_sigsuspend(1), + 134 => ipc::rt_sigaction(4), + 135 => ipc::rt_sigprocmask(3), + 137 => ipc::sigtimedwait(3), + 139 => ipc::rt_sig_return(0), + 194 => ipc::shmget(3), + 195 => ipc::shmctl(3), + 196 => ipc::shmat(3), + 197 => ipc::shmdt(1), + + // Time + 101 => time::nanosleep(2), + 113 => time::clock_gettime(2), + 115 => time::clock_nanosleep(4), + 169 => time::gettimeofday(2), + + // Event + 72 => event::pselect6_time32(6), + 73 => event::ppoll_time32(5), + 103 => event::setitimer(3), + } +} diff --git a/src/kernel/syscall/task.rs b/src/kernel/syscall/task.rs new file mode 100644 index 0000000..3a342bb --- /dev/null +++ b/src/kernel/syscall/task.rs @@ -0,0 +1,239 @@ +use alloc::string::String; +use alloc::vec::Vec; +use bitflags::bitflags; + +use crate::fs::file::FileFlags; +use crate::fs::{Perm, PermFlags, vfs}; +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::event::Event; +use crate::kernel::scheduler::current::{copy_from_user, copy_to_user}; +use crate::kernel::scheduler::{current, Tid}; +use crate::kernel::scheduler; +use crate::kernel::syscall::SyscallRet; +use crate::kernel::syscall::uptr::{UserPointer, UArray, UPtr, UString}; +use crate::kernel::task::def::TaskCloneFlags; + +pub fn sched_yield() -> SyscallRet { + current::schedule(); + Ok(0) +} + +pub fn getpid() -> SyscallRet { + let pcb = current::pcb(); + Ok(pcb.get_pid() as usize) +} + +pub fn gettid() -> SyscallRet { + let tcb = current::tcb(); + Ok(tcb.get_tid() as usize) +} + +pub fn getppid() -> SyscallRet { + let pcb = current::pcb(); + let ppid = pcb.parent.lock().as_ref().map_or(0, |p| p.get_pid()); + Ok(ppid as usize) +} + +pub fn setsid() -> SyscallRet { + let pcb = current::pcb(); + // pcb.set_sid(); + Ok(pcb.get_pid() as usize) +} + +bitflags! { + #[derive(Debug)] + struct CloneFlags: i32 { + const VM = 0x0000100; + const FS = 0x0000200; + const FILES = 0x0000400; + const SIGHAND = 0x00000800; + const PIDFD = 0x00001000; + const VFORK = 0x0000_4000; + const PARENT = 0x00008000; + const THREAD = 0x00010000; + const SYSVSEM = 0x00040000; + const SETTLS = 0x00080000; + const PARENT_SETTID = 0x00100000; + const CHILD_CLEARTID = 0x00200000; + const CLONE_DETACHED = 0x00400000; + const CHILD_SETTID = 0x01000000; + const UNTRACED = 0x00800000; + const NEWCGROUP = 0x02000000; + const NEWUTS = 0x04000000; + const NEWIPC = 0x08000000; + const NEWUSER = 0x10000000; + const NEWPID = 0x20000000; + const NEWNET = 0x40000000; + } +} + +pub fn clone(flags: usize, stack: usize, uptr_parent_tid: UPtr, tls: usize, uptr_child_tid: usize) -> SyscallRet { + let flags = CloneFlags::from_bits((flags & !0xff) as i32).ok_or(Errno::EINVAL)?; + + let task_flags = TaskCloneFlags { + vm: flags.contains(CloneFlags::VM), + files: flags.contains(CloneFlags::FILES), + thread: flags.contains(CloneFlags::THREAD), + }; + + let tls = if flags.contains(CloneFlags::SETTLS) { + Some(tls) + } else { + None + }; + + let child = current::pcb().clone_task(current::tcb(), stack, &task_flags, tls)?; + + if flags.contains(CloneFlags::CHILD_SETTID) { + let _ = child.get_addrspace().copy_to_user(uptr_child_tid, 0 as Tid); + } + + if flags.contains(CloneFlags::CHILD_CLEARTID) { + child.set_tid_address(uptr_child_tid); + } + + let child_tid = child.get_tid(); + + if flags.contains(CloneFlags::PARENT_SETTID) { + uptr_parent_tid.write(child_tid)?; + } + + if flags.contains(CloneFlags::VFORK) { + child.set_parent_waiting_vfork(Some(current::task().clone())); + scheduler::push_task(child); + + let event = current::block_uninterruptible("vfork"); + + match event { + Event::VFork => {} + _ => unreachable!(), + } + } else { + scheduler::push_task(child); + } + + // kinfo!("clone: created child task with TID {}", child_tid); + + Ok(child_tid as usize) +} + +pub fn execve(uptr_path: UString, uptr_argv: UArray, uptr_envp: UArray) -> SyscallRet { + { + uptr_path.should_not_null()?; + + let path = uptr_path.read()?; + + // crate::kinfo!("execve: {}", path); + + let file = current::with_cwd(|cwd| vfs::openat_file(&cwd, &path, FileFlags::dontcare(), &Perm::new(PermFlags::X)))?; + + let helper = |uarray: UArray| -> SysResult> { + if uarray.is_null() { + return Ok(Vec::new()); + } + + let mut vec = Vec::new(); + let mut i = 0; + loop { + let p = uarray.index(i).read()?; + if p.is_null() { + break; + } + vec.push(p.read()?); + i += 1; + } + Ok(vec) + }; + + let argv = helper(uptr_argv)?; + let envp = helper(uptr_envp)?; + let argv_ref: Vec<&str> = argv.iter().map(|s| s.as_str()).collect(); + let envp_ref: Vec<&str> = envp.iter().map(|s| s.as_str()).collect(); + + current::pcb().exec(current::tcb(), file, &argv_ref, &envp_ref)?; + current::tcb().wake_parent_waiting_vfork(); + } + + current::schedule(); + + unreachable!() +} + +bitflags! { + pub struct WaitOptions: usize { + const WNOHANG = 1 << 0; + const WUNTRACED = 1 << 1; + } +} + +pub fn wait4(pid: usize, uptr_status: usize, options: usize, _user_rusages: usize) -> Result { + let pcb = current::pcb(); + let options = WaitOptions::from_bits(options).unwrap_or(WaitOptions::empty()); + let pid = pid as isize; + + let wait_pid; + let exit_code: usize; + + if pid == -1 { + if let Some(result) = pcb.wait_any_child(!options.contains(WaitOptions::WNOHANG))? { + wait_pid = result.0; + exit_code = result.1 as usize; + } else { + return Ok(usize::MAX); + } + } else { + if let Some(result) = pcb.wait_child(pid as i32, !options.contains(WaitOptions::WNOHANG))? { + wait_pid = pid as i32; + exit_code = result as usize; + } else { + return Ok(usize::MAX); + } + } + + if uptr_status != 0 { + let status: u32 = (exit_code as u32 & 0xff) << 8; // WEXITSTATUS + copy_to_user::object(uptr_status, status)?; + } + + Ok(wait_pid as usize) +} + +pub fn exit(code: usize) -> Result { + let tcb = current::tcb(); + tcb.exit(code as u8); + + tcb.wake_parent_waiting_vfork(); + + current::schedule(); + + unreachable!() +} + +pub fn exit_group(code: usize) -> Result { + let pcb = current::pcb(); + pcb.exit(code as u8); + + current::tcb().wake_parent_waiting_vfork(); + + current::schedule(); + + unreachable!() +} + +pub fn set_tid_address(tid_address: usize) -> Result { + let tcb = current::tcb(); + tcb.set_tid_address(tid_address); + Ok(0) +} + +pub fn getcwd(ubuf: usize, size: usize) -> SysResult { + let cwd = current::with_cwd(|dentry| dentry.get_path()); + copy_to_user::string(ubuf, &cwd, size) +} + +pub fn chdir(user_path: usize) -> SysResult { + let path = copy_from_user::string(user_path)?; + let dentry = current::with_cwd(|cwd| vfs::load_dentry_at(&cwd, &path))?; + current::pcb().set_cwd(&dentry); + Ok(0) +} diff --git a/src/kernel/syscall/time.rs b/src/kernel/syscall/time.rs new file mode 100644 index 0000000..05124a2 --- /dev/null +++ b/src/kernel/syscall/time.rs @@ -0,0 +1,72 @@ +use crate::kernel::scheduler::current; +use crate::kernel::event::{timer, Event}; +use crate::kernel::errno::{SysResult, Errno}; +use crate::kernel::syscall::uptr::{UserPointer, UPtr}; +use crate::kernel::uapi::{Timespec, Timeval}; +use crate::arch; + +pub fn gettimeofday(uptr_timeval: UPtr, _uptr_tz: usize) -> SysResult { + uptr_timeval.should_not_null()?; + + let us = arch::get_time_us(); + let timeval = Timeval { + tv_sec: us / 1000000, + tv_usec: us % 1000000, + }; + + uptr_timeval.write(timeval)?; + + Ok(0) +} + +pub fn nanosleep(uptr_req: UPtr, _uptr_rem: usize) -> SysResult { + uptr_req.should_not_null()?; + + let req = uptr_req.read()?; + + if req.tv_sec == 0 && req.tv_nsec == 0 { + return Ok(0); + } + + timer::add_timer(current::task().clone(), req.into()); + let event = current::block("timer nanosleep"); + + match event { + Event::Timeout => Ok(0), + Event::Signal => Err(Errno::EINTR), + _ => unreachable!(), + } +} + +pub fn clock_nanosleep(_clockid: usize, _flags: usize, uptr_req: UPtr, _uptr_rem: usize) -> SysResult { + uptr_req.should_not_null()?; + + let req = uptr_req.read()?; + + if req.tv_sec == 0 && req.tv_nsec == 0 { + return Ok(0); + } + + timer::add_timer(current::task().clone(), req.into()); + let event = current::block("timer nanosleep"); + + match event { + Event::Timeout => Ok(0), + Event::Signal => Err(Errno::EINTR), + _ => unreachable!(), + } +} + +pub fn clock_gettime(_clockid: usize, uptr_timespec: UPtr) -> SysResult { + uptr_timespec.should_not_null()?; + + let us = arch::get_time_us(); + let timespec = Timespec { + tv_sec: us / 1000000, + tv_nsec: (us % 1000000) * 1000, + }; + + uptr_timespec.write(timespec)?; + + Ok(0) +} diff --git a/src/kernel/syscall/uid.rs b/src/kernel/syscall/uid.rs new file mode 100644 index 0000000..d6e2dbc --- /dev/null +++ b/src/kernel/syscall/uid.rs @@ -0,0 +1,17 @@ +use crate::kernel::errno::SysResult; + +pub fn getuid() -> SysResult { + Ok(0) +} + +pub fn geteuid() -> SysResult { + Ok(0) +} + +pub fn getgid() -> SysResult { + Ok(0) +} + +pub fn getegid() -> SysResult { + Ok(0) +} diff --git a/src/kernel/syscall/uptr.rs b/src/kernel/syscall/uptr.rs new file mode 100644 index 0000000..3736621 --- /dev/null +++ b/src/kernel/syscall/uptr.rs @@ -0,0 +1,189 @@ +use alloc::string::String; +use core::fmt::Debug; +use core::mem::size_of; + +use crate::kernel::scheduler::current::{copy_from_user, copy_to_user}; +use crate::kernel::scheduler::current; +use crate::kernel::errno::{SysResult, Errno}; + +/// Macro to implement From for user pointer types +macro_rules! impl_from_usize { + ($type_name:ident) => { + impl From for $type_name { + fn from(uaddr: usize) -> Self { + $type_name { + uaddr, + _marker: core::marker::PhantomData, + } + } + } + }; +} + +pub trait UserStruct: Sized + Copy {} + +impl UserStruct for u8 {} +impl UserStruct for usize {} +impl UserStruct for () {} + +pub trait UserPointer { + fn from_uaddr(uaddr: usize) -> Self; + + fn uaddr(&self) -> usize; + + fn is_null(&self) -> bool { + self.uaddr() == 0 + } + + fn should_not_null(&self) -> SysResult<()> { + if self.is_null() { + Err(Errno::EINVAL) + } else { + Ok(()) + } + } + + fn kaddr(&self) -> SysResult { + debug_assert!(!self.is_null()); + current::addrspace().translate_write(self.uaddr()) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UPtr { + uaddr: usize, + _marker: core::marker::PhantomData, +} + +impl UPtr { + pub fn read(&self) -> SysResult { + debug_assert!(!self.is_null()); + copy_from_user::object(self.uaddr) + } + + pub fn write(&self, value: T) -> SysResult<()> { + debug_assert!(!self.is_null()); + copy_to_user::object(self.uaddr, value) + } + + pub fn add(&self, offset: usize) -> Self { + Self::from_uaddr(self.uaddr + offset * size_of::()) + } + + pub fn read_optional(&self) -> SysResult> { + if self.is_null() { + Ok(None) + } else { + Ok(Some(self.read()?)) + } + } +} + +impl UserPointer for UPtr { + fn uaddr(&self) -> usize { + self.uaddr + } + + fn from_uaddr(uaddr: usize) -> Self { + UPtr { + uaddr, + _marker: core::marker::PhantomData, + } + } +} + +impl Debug for UPtr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "UPtr({:#x})", self.uaddr) + } +} + +impl_from_usize!(UPtr); + +impl Into for UPtr { + fn into(self) -> usize { + self.uaddr + } +} + +pub struct UArray { + uaddr: usize, + _marker: core::marker::PhantomData, +} + +impl UArray { + pub fn read(&self, offset: usize, buf: &mut [T]) -> SysResult<()> { + debug_assert!(!self.is_null()); + copy_from_user::slice(self.uaddr + size_of::() * offset, buf) + } + + pub fn write(&self, offset: usize, buf: &[T]) -> SysResult<()> { + copy_to_user::slice(self.uaddr + size_of::() * offset, buf) + } + + pub fn index(&self, i: usize) -> UPtr { + UPtr::from_uaddr(self.uaddr + i * size_of::()) + } +} + +impl UserPointer for UArray { + fn uaddr(&self) -> usize { + self.uaddr + } + + fn from_uaddr(uaddr: usize) -> Self { + UArray { + uaddr, + _marker: core::marker::PhantomData, + } + } +} + +impl_from_usize!(UArray); + +pub type UBuffer = UArray; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UString { + uaddr: usize, +} + +impl UString { + pub fn read(&self) -> SysResult { + debug_assert!(!self.is_null()); + copy_from_user::string(self.uaddr) + } + + pub fn write(&self, s: &str, max_size: usize) -> SysResult { + debug_assert!(!self.is_null()); + copy_to_user::string(self.uaddr, s, max_size) + } + + pub fn is_null(&self) -> bool { + self.uaddr == 0 + } + + pub fn should_not_null(&self) -> SysResult<()> { + if self.is_null() { + Err(Errno::EINVAL) + } else { + Ok(()) + } + } +} + +impl UserStruct for UString {} + +impl From for UString { + fn from(uaddr: usize) -> Self { + UString { uaddr } + } +} + +impl From> for UString { + fn from(value: UPtr) -> Self { + Self { uaddr: value.uaddr() } + } +} diff --git a/src/kernel/task/def.rs b/src/kernel/task/def.rs new file mode 100644 index 0000000..f4a2ae4 --- /dev/null +++ b/src/kernel/task/def.rs @@ -0,0 +1,6 @@ +#[allow(dead_code)] +pub struct TaskCloneFlags { + pub files: bool, + pub vm: bool, + pub thread: bool, +} \ No newline at end of file diff --git a/src/kernel/task/fdtable.rs b/src/kernel/task/fdtable.rs new file mode 100644 index 0000000..6474a34 --- /dev/null +++ b/src/kernel/task/fdtable.rs @@ -0,0 +1,128 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::vec; + +use crate::fs::file::FileOps; +use crate::kernel::config; +use crate::kernel::errno::{Errno, SysResult}; + +#[derive(Clone, Copy)] +pub struct FDFlags { + pub cloexec: bool, +} + +impl FDFlags { + pub fn empty() -> Self { + Self { cloexec: false } + } +} + +#[derive(Clone)] +struct FDItem { + pub file: Arc, + pub flags: FDFlags, +} + +pub struct FDTable { + table: Vec>, + max_fd: usize, +} + +impl FDTable { + pub fn new() -> Self { + Self { + table: vec![None; 32], // Initialize with 32 file descriptors + max_fd: config::MAX_FD, + } + } + + pub fn get(&mut self, fd: usize) -> SysResult> { + if fd < self.table.len() { + let item = self.table[fd].as_ref().ok_or(Errno::EBADF)?; + Ok(item.file.clone()) + } else { + Err(Errno::EBADF) + } + } + + pub fn set(&mut self, fd: usize, file: Arc, flags: FDFlags) -> SysResult<()> { + if fd >= config::MAX_FD { + return Err(Errno::EBADF); + } + if fd >= self.table.len() { + self.table.resize(fd + 1, None); + } + self.table[fd] = Some(FDItem { file, flags }); + Ok(()) + } + + pub fn get_fd_flags(&self, fd: usize) -> SysResult { + if fd < self.table.len() { + let item = self.table[fd].as_ref().ok_or(Errno::EBADF)?; + Ok(item.flags) + } else { + Err(Errno::EBADF) + } + } + + pub fn set_fd_flags(&mut self, fd: usize, flags: FDFlags) -> SysResult<()> { + if fd < self.table.len() { + let item = self.table[fd].as_mut().ok_or(Errno::EBADF)?; + item.flags = flags; + Ok(()) + } else { + Err(Errno::EBADF) + } + } + + pub fn push(&mut self, file: Arc, flags: FDFlags) -> Result { + if let Some(pos) = self.table.iter().position(|f| f.is_none()) { + self.table[pos] = Some(FDItem { file, flags }); + Ok(pos) + } else { + if self.table.len() >= self.max_fd { + return Err(Errno::EMFILE); + } + self.table.push(Some(FDItem { file, flags })); + Ok(self.table.len() - 1) + } + } + + pub fn close(&mut self, fd: usize) -> SysResult<()> { + if fd < self.table.len() { + if self.table[fd].is_none() { + return Err(Errno::EBADF); + } + self.table[fd] = None; + Ok(()) + } else { + Err(Errno::EBADF) + } + } + + pub fn fork(&self) -> Self { + let new_table = self.table.iter().map(|item| { + item.as_ref().map(|fd_item| fd_item.clone()) + }).collect(); + + Self { table: new_table, max_fd: self.max_fd } + } + + pub fn cloexec(&mut self) { + self.table.iter_mut().for_each(|item| { + if let Some(fd_item) = item { + if fd_item.flags.cloexec { + *item = None; + } + } + }); + } + + pub fn set_max_fd(&mut self, max_fd: usize) { + self.max_fd = max_fd; + } + + pub fn get_max_fd(&self) -> usize { + self.max_fd + } +} diff --git a/src/kernel/task/initprocess.rs b/src/kernel/task/initprocess.rs new file mode 100644 index 0000000..6c38d74 --- /dev/null +++ b/src/kernel/task/initprocess.rs @@ -0,0 +1,63 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; + +use alloc::sync::Arc; + +use crate::kernel::task::pcb::PCB; +use crate::fs::vfs; +use crate::fs::file::FileFlags; +use crate::kinfo; + +const INITPATH: &'static str = match option_env!("KERNELX_INITPATH") { + Some(path) => path, + None => "/init", +}; + +const INITCWD: &'static str = match option_env!("KERNELX_INITCWD") { + Some(path) => path, + None => "/", +}; + +const INIT_ARGV: &[&str] = &[ + INITPATH, + "sh", + "busybox_testcode.sh" +]; + +const INIT_ENVP: &[&str] = &[ + // "LD_LIBRARY_PATH=/lib", + // "LD_SHOW_AUXV=1", + // "LD_DEBUG=all", + // "LD_BIND_NOW=1", + // "LD_PRELOAD=", + // "LD_USE_LOAD_BIAS=0" +]; + +struct InitProcess(UnsafeCell>>); + +unsafe impl Sync for InitProcess {} + +static INIT_PROCESS: InitProcess = InitProcess(UnsafeCell::new(MaybeUninit::uninit())); + +pub fn create_initprocess() { + kinfo!("Loading init process from ELF: {}", INITPATH); + let initfile = vfs::open_file(INITPATH, FileFlags { readable: true, writable: false }) + .expect("Failed to open init file"); + + let pcb = PCB::new_initprocess( + initfile, + INITCWD, + INIT_ARGV, + INIT_ENVP + ).expect("Failed to initialize init process from ELF"); + + unsafe { + *INIT_PROCESS.0.get() = MaybeUninit::new(pcb); + } +} + +pub fn get_initprocess() -> &'static Arc { + unsafe { + (&*INIT_PROCESS.0.get()).assume_init_ref() + } +} diff --git a/src/kernel/task/manager.rs b/src/kernel/task/manager.rs new file mode 100644 index 0000000..8e914a9 --- /dev/null +++ b/src/kernel/task/manager.rs @@ -0,0 +1,108 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use alloc::collections::BTreeMap; +use alloc::sync::Arc; + +use crate::fs::{Perm, PermFlags, vfs}; +use crate::fs::file::FileFlags; +use crate::kernel::scheduler::Tid; +use crate::klib::SpinLock; + +use super::{PCB, Pid}; + +pub struct Manager { + pcbs: SpinLock>>, + initprocess: UnsafeCell>>, +} + +impl Manager { + const fn new() -> Self { + Self { + pcbs: SpinLock::new(BTreeMap::new()), + initprocess: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + fn create_initprocess(&self, initpath: &str, initcwd: &str) { + let initargv: &[&str] = &[ + initpath, + // "find", + // "main.c", + // "-name", + // "\"busybox_cmd.txt\"" + "sh", + // "/runall.sh", + "lmbench_testcode.sh" + ]; + + let initenvp: &[&str] = &[]; + + let initfile = vfs::open_file( + initpath, + FileFlags { readable: true, writable: false, blocked: true }, + &Perm::new(PermFlags::X) + ).expect("Failed to open init file"); + let pcb = PCB::new_initprocess(initfile, initcwd, initargv, initenvp).expect("Failed to initialize init process from ELF"); + + self.pcbs.lock().insert(0, pcb.clone()); + + unsafe { + *self.initprocess.get() = MaybeUninit::new(pcb); + } + } + + fn get_initprocess(&self) -> &Arc { + unsafe { + (&*self.initprocess.get()).assume_init_ref() + } + } + + fn insert_pcb(&self, pcb: Arc) { + let mut pcbs = self.pcbs.lock(); + pcbs.insert(pcb.get_pid(), pcb); + } + + fn get_pcb(&self, pid: Pid) -> Option> { + let pcbs = self.pcbs.lock(); + pcbs.get(&pid).cloned() + } + + fn remove_pcb(&self, pid: Pid) -> Option> { + let mut pcbs = self.pcbs.lock(); + pcbs.remove(&pid) + } + + fn find_task_parent(&self, tid: Tid) -> Option> { + self.pcbs.lock().iter().find(|(_, pcb)| { + pcb.has_child(tid) + }).map(|(_, pcb)| pcb.clone()) + } +} + +unsafe impl Sync for Manager {} + +static MANAGER: Manager = Manager::new(); + +pub fn create_initprocess(initpath: &str, initcwd: &str) { + MANAGER.create_initprocess(initpath, initcwd); +} + +pub fn get_initprocess() -> &'static Arc { + MANAGER.get_initprocess() +} + +pub fn insert(pcb: Arc) { + MANAGER.insert_pcb(pcb); +} + +pub fn get(pid: Pid) -> Option> { + MANAGER.get_pcb(pid) +} + +pub fn remove(pid: Pid) -> Option> { + MANAGER.remove_pcb(pid) +} + +pub fn find_task_parent(tid: Tid) -> Option> { + MANAGER.find_task_parent(tid) +} diff --git a/src/kernel/task/mod.rs b/src/kernel/task/mod.rs new file mode 100644 index 0000000..34b703b --- /dev/null +++ b/src/kernel/task/mod.rs @@ -0,0 +1,9 @@ +mod tcb; +mod pcb; +pub mod manager; +pub mod fdtable; +pub mod def; + +pub use tcb::*; +pub use pcb::*; +pub use manager::{get_initprocess, create_initprocess}; diff --git a/src/kernel/task/pcb.rs b/src/kernel/task/pcb.rs new file mode 100644 index 0000000..bb57cc4 --- /dev/null +++ b/src/kernel/task/pcb.rs @@ -0,0 +1,339 @@ +use alloc::vec::Vec; +use alloc::sync::Arc; +use core::time::Duration; +use spin::Mutex; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::scheduler::tid::Tid; +use crate::kernel::task::def::TaskCloneFlags; +use crate::kernel::task::{get_initprocess, manager}; +use crate::kernel::scheduler::{Task, TaskState, current, tid}; +use crate::kernel::scheduler; +use crate::kernel::event::Event; +use crate::kernel::ipc::{KSiFields, PendingSignalQueue, SiCode, SiSigChld, SignalActionTable, signum}; +use crate::fs::file::File; +use crate::fs::vfs; +use crate::fs::Dentry; +use crate::klib::SpinLock; + +use super::tcb::TCB; + +pub type Pid = Tid; + +struct Signal { + actions: Mutex, + pending: Mutex, +} + +pub struct PCB { + pid: Tid, + pub parent: SpinLock>>, + is_zombie: SpinLock, + exit_code: SpinLock, + + pub tasks: SpinLock>>, + cwd: SpinLock>, + umask: SpinLock, + waiting_task: SpinLock>>, + + signal: Signal, + + children: Mutex>>, +} + +impl PCB { + pub fn new(pid: i32, parent: &Arc) -> Arc { + Arc::new(Self { + pid, + parent: SpinLock::new(Some(parent.clone())), + is_zombie: SpinLock::new(false), + exit_code: SpinLock::new(0), + + tasks: SpinLock::new(Vec::new()), + cwd: SpinLock::new(parent.cwd.lock().clone()), + umask: SpinLock::new(*parent.umask.lock()), + waiting_task: SpinLock::new(Vec::new()), + + signal: Signal { + actions: Mutex::new(SignalActionTable::new()), + pending: Mutex::new(PendingSignalQueue::new()), + }, + + children: Mutex::new(Vec::new()), + }) + } + + pub fn new_initprocess(file: File, cwd: &str, argv: &[&str], envp: &[&str]) -> Result, Errno> { + let new_tid = tid::alloc(); + + let cwd = vfs::load_dentry(cwd)?; + + let pcb = Arc::new(Self { + pid: 0, + parent: SpinLock::new(None), + is_zombie: SpinLock::new(false), + exit_code: SpinLock::new(0), + + tasks: SpinLock::new(Vec::new()), + cwd: SpinLock::new(cwd.clone()), + umask: SpinLock::new(0o022), + waiting_task: SpinLock::new(Vec::new()), + + signal: Signal { + actions: Mutex::new(SignalActionTable::new()), + pending: Mutex::new(PendingSignalQueue::new()), + }, + + children: Mutex::new(Vec::new()), + }); + + let first_task = TCB::new_inittask(new_tid, &pcb, file, argv, envp); + pcb.tasks.lock().push(first_task.clone()); + + scheduler::push_task(first_task); + + Ok(pcb) + } + + pub fn get_pid(&self) -> Tid { + self.pid + } + + fn is_zombie(&self) -> bool { + *self.is_zombie.lock() + } + + fn get_exit_code(&self) -> u8 { + *self.exit_code.lock() + } + + pub fn has_child(&self, tid: Tid) -> bool { + self.tasks.lock().iter().any(|tcb| tcb.get_tid() == tid) + } + + pub fn with_cwd(&self, f: F) -> R + where F: FnOnce(&Arc) -> R { + let cwd = self.cwd.lock(); + f(&cwd) + } + + pub fn set_cwd(&self, dentry: &Arc) { + *self.cwd.lock() = dentry.clone(); + } + + pub fn umask(&self) -> u16 { + *self.umask.lock() + } + + pub fn set_umask(&self, mask: u16) { + *self.umask.lock() = mask & 0o777; + } + + pub fn clone_task( + self: &Arc, + tcb: &TCB, + userstack: usize, + flags: &TaskCloneFlags, + tls: Option, + ) -> Result, Errno> { + let new_tid = tid::alloc(); + let new_tcb; + + if flags.thread { + new_tcb = tcb.new_clone(new_tid, self, userstack, flags, tls); + self.tasks.lock().push(new_tcb.clone()); + } else { + let new_parent = PCB::new(new_tid, self); + new_tcb = tcb.new_clone(new_tid, &new_parent, userstack, flags, tls); + new_parent.tasks.lock().push(new_tcb.clone()); + self.children.lock().push(new_parent.clone()); + manager::insert(new_parent); + } + + Ok(new_tcb) + } + + pub fn exec( + self: &Arc, + tcb: &TCB, + file: File, + argv: &[&str], + envp: &[&str] + ) -> Result<(), Errno> { + let first_task = tcb.new_exec(file, argv, envp)?; + + let mut tasks = self.tasks.lock(); + tasks.iter_mut().for_each(|tcb| { + tcb.with_state_mut(|state| state.state = TaskState::Exited ); + }); + tasks.clear(); + tasks.push(first_task.clone()); + + scheduler::push_task(first_task); + + Ok(()) + } + + pub fn exit(self: &Arc, code: u8) { + let mut task = self.tasks.lock(); + task.iter().for_each(|t| { + t.with_state_mut(|state| state.state = TaskState::Exited ); + }); + task.clear(); + + drop(task); + + *self.is_zombie.lock() = true; + *self.exit_code.lock() = code; + + if self.pid == 0 { + panic!("Init process exited with code {}, system will halt.", code); + } + + if let Some(parent) = self.parent.lock().as_ref() { + parent.waiting_task.lock().drain(..).for_each(|t| { + scheduler::wakeup_task(t, Event::Process { child: self.pid }); + }); + + let fields = KSiFields::SigChld(SiSigChld { + si_pid: self.pid, + si_uid: current::uid(), + si_status: code as i32, + si_utime: 0, + si_stime: 0 + }); + parent.send_signal(signum::SIGCHLD, SiCode::SI_KERNEL, fields, None).unwrap_or(()); + } + + let mut children = self.children.lock(); + children.iter_mut().for_each(|c| { + *c.parent.lock() = Some(get_initprocess().clone()); + }); + + manager::remove(self.pid); + + get_initprocess().children.lock().append(&mut children); + } + + pub fn wait_child(&self, pid: i32, blocked: bool) -> Result, Errno> { + let child = { + let children = self.children.lock(); + children.iter().find(|c| c.get_pid() == pid).cloned() + }; + + if let Some(child) = child { + if child.is_zombie() { + let exit_code = child.get_exit_code(); + // self.children.lock().retain(|c| c.get_pid() != pid); + return Ok(Some(exit_code)); + } + + if blocked { + loop { + self.waiting_task.lock().push(current::task().clone()); + + let event = current::block("wait_child"); + + match event { + Event::Process { child } => { + if child == pid { + break; + } + } + + Event::Signal => { + return Err(Errno::EINTR); + } + + _ => unreachable!(), + } + } + + let exit_code = child.get_exit_code(); + { + let mut children = self.children.lock(); + children.retain(|c| c.get_pid() != pid); + } + + return Ok(Some(exit_code)); + } else { + return Ok(None); + } + } else { // No child found + if blocked { + return Err(Errno::ECHILD); + } else { + return Ok(None); + } + } + } + + pub fn wait_any_child(&self, blocked: bool) -> SysResult> { + let mut children = self.children.lock(); + + if let Some(child) = children.iter().find(|c| c.is_zombie()) { + let pid = child.get_pid(); + let exit_code = child.get_exit_code(); + children.retain(|c| c.get_pid() != pid); + return Ok(Some((pid, exit_code))); + } + + drop(children); + + if !blocked { + return Ok(None); + } + + self.waiting_task.lock().push(current::task().clone()); + + let event = current::block("wait_any_child"); + + match event { + Event::Process { child } => { + let pid = child; + let exit_code; + let mut children = self.children.lock(); + + match children.iter().find(|c| c.get_pid() == child){ + Some(child_pcb) => { + exit_code = child_pcb.get_exit_code(); + }, + None => unreachable!(), // The child must exist + } + + children.retain(|c| c.get_pid() != pid); + + Ok(Some((pid, exit_code))) + } + Event::Signal => { + Err(Errno::EINTR) + } + _ => unreachable!(), + } + } + + pub fn signal_actions(&self) -> &Mutex { + &self.signal.actions + } + + pub fn pending_signals(&self) -> &Mutex { + &self.signal.pending + } + + pub fn tasks_usage_time(&self) -> (Duration, Duration) { + let tasks = self.tasks.lock(); + let mut utime = Duration::ZERO; + let mut stime = Duration::ZERO; + + tasks.iter().for_each(|task| { + let counter = task.time_counter.lock(); + utime += counter.user_time; + stime += counter.system_time; + }); + + (utime, stime) + } +} + +unsafe impl Send for PCB {} +unsafe impl Sync for PCB {} diff --git a/src/kernel/task/tcb.rs b/src/kernel/task/tcb.rs new file mode 100644 index 0000000..57e1473 --- /dev/null +++ b/src/kernel/task/tcb.rs @@ -0,0 +1,541 @@ +use core::time::Duration; +use alloc::sync::Arc; +use alloc::vec; +use spin::Mutex; + +use crate::kernel::config::UTASK_KSTACK_PAGE_COUNT; +use crate::kernel::scheduler; +use crate::kernel::scheduler::current; +use crate::kernel::scheduler::Task; +use crate::kernel::usync::futex; +use crate::kernel::config; +use crate::kernel::task::def::TaskCloneFlags; +use crate::kernel::task::PCB; +use crate::kernel::task::fdtable::{FDFlags, FDTable}; +use crate::kernel::mm::{AddrSpace, elf}; +use crate::kernel::mm::maparea::{AuxKey, Auxv}; +use crate::kernel::event::{Event, timer}; +use crate::kernel::ipc::{PendingSignal, SignalSet}; +use crate::kernel::errno::Errno; +use crate::kernel::scheduler::{TaskState, Tid, KernelStack}; +use crate::fs::file::{File, FileFlags, CharFile}; +use crate::fs::{Perm, PermFlags, vfs}; +use crate::klib::SpinLock; +use crate::arch::{UserContext, KernelContext, UserContextTrait}; +use crate::arch; +use crate::driver; +use crate::ktrace; + +#[derive(Debug, Clone, Copy)] +pub struct TaskStateSet { + pub state: TaskState, + + pub pending_signal: Option, + pub signal_to_wait: SignalSet, +} + +impl TaskStateSet { + pub fn new() -> Self { + Self { + state: TaskState::Ready, + pending_signal: None, + signal_to_wait: SignalSet::empty() + } + } +} + +pub struct TimeCounter { + pub user_time: Duration, + pub system_time: Duration, + pub user_start: Option, + pub system_start: Option, +} + +impl TimeCounter { + pub fn new() -> Self { + Self { + user_time: Duration::ZERO, + system_time: Duration::ZERO, + user_start: None, + system_start: Some(timer::now()), + } + } +} + +pub struct TCB { + pub tid: Tid, + pub parent: Arc, + tid_address: Mutex>, + pub robust_list: SpinLock>, + + user_context_ptr: *mut UserContext, + user_context_uaddr: usize, + kernel_context: KernelContext, + pub kernel_stack: KernelStack, + + addrspace: Arc, + fdtable: Arc>, + + pub signal_mask: SpinLock, + + state: SpinLock, + pub wakeup_event: SpinLock>, + parent_waiting_vfork: SpinLock>>, + pub time_counter: SpinLock, +} + +impl TCB { + pub fn new( + tid: i32, + parent: &Arc, + + mut user_context: UserContext, + + addrspace: Arc, + fdtable: Arc>, + ) -> Arc { + let kernel_stack = KernelStack::new(UTASK_KSTACK_PAGE_COUNT); + user_context.set_kernel_stack_top(kernel_stack.get_top()); + + let (user_context_uaddr, user_context_ptr) = addrspace.alloc_usercontext_page(); + user_context.set_addrspace(&addrspace); + + unsafe { + user_context_ptr.write(user_context); + } + + let tcb = Arc::new(Self { + tid, + parent: parent.clone(), + tid_address: Mutex::new(None), + robust_list: SpinLock::new(None), + + user_context_ptr, + user_context_uaddr, + kernel_context: KernelContext::new(&kernel_stack), + kernel_stack, + + addrspace, + fdtable, + + signal_mask: SpinLock::new(SignalSet::empty()), + + state: SpinLock::new(TaskStateSet::new()), + wakeup_event: SpinLock::new(None), + parent_waiting_vfork: SpinLock::new(None), + time_counter: SpinLock::new(TimeCounter::new()), + }); + + tcb + } + + pub fn new_inittask( + tid: i32, + parent: &Arc, + file: File, + argv: &[&str], + envp: &[&str], + ) -> Arc { + // Read the shebang + let mut first_line = [0u8; 128]; + let n = file.read_at(&mut first_line, 0).expect("Failed to read first line of init file"); + let first_line = core::str::from_utf8(&first_line[..n]).unwrap_or(""); + let first_line = first_line.lines().next().unwrap_or(""); + let first_line = first_line.trim_end_matches('\n'); + if first_line.starts_with("#!") { + let shebang = first_line.trim_start_matches("#!").trim(); + let mut parts = shebang.split_whitespace(); + if let Some(interpreter) = parts.next() { + let mut new_argv = vec![interpreter]; + for part in parts { + new_argv.push(part); + } + for arg in argv { + new_argv.push(arg); + } + + let interpreter_file = vfs::open_file( + interpreter, + FileFlags::dontcare(), + &Perm::new(PermFlags::X) + ).expect("Failed to open."); + return Self::new_inittask(tid, parent, interpreter_file, &new_argv, envp); + } + } + + let file = Arc::new(file); + + let mut addrspace = AddrSpace::new(); + let (user_entry, dyn_info) = elf::loader::load_elf(&file, &mut addrspace) + .expect("Failed to load ELF for init task"); + + let mut auxv = Auxv::new(); + if let Some(dyn_info) = dyn_info { + auxv.push(AuxKey::BASE, dyn_info.interpreter_base); + auxv.push(AuxKey::PHDR, dyn_info.phdr_addr); + auxv.push(AuxKey::PHENT, dyn_info.phent as usize); + auxv.push(AuxKey::PHNUM, dyn_info.phnum as usize); + auxv.push(AuxKey::FLAGS, 0); + auxv.push(AuxKey::ENTRY, dyn_info.user_entry); + ktrace!("Dynamic linker info: {:?}", dyn_info); + } + + auxv.push(AuxKey::RANDOM, config::USER_RANDOM_ADDR_BASE); + auxv.push(AuxKey::PAGESZ, arch::PGSIZE); + + let userstack_top = addrspace.create_user_stack(argv, envp, &auxv).expect("Failed to push args and envp to userstack"); + + let mut fdtable = FDTable::new(); + let stdout_dev = driver::get_char_driver("sbi-console").unwrap(); + for _ in 0..3 { + fdtable.push(Arc::new(CharFile::new(stdout_dev.clone())), FDFlags::empty()).unwrap(); + } + fdtable.push(file.clone(), FDFlags::empty()).unwrap(); + + let mut user_context = UserContext::new(); + user_context.set_user_stack_top(userstack_top); + user_context.set_user_entry(user_entry); + + let tcb = Self::new( + tid, + parent, + user_context, + addrspace, + Arc::new(SpinLock::new(fdtable)) + ); + + tcb + } + + pub fn new_clone( + &self, + tid: Tid, + parent: &Arc, + userstack: usize, + flags: &TaskCloneFlags, + tls: Option, + ) -> Arc { + let mut new_user_context = UserContext::new(); + self.with_user_context(|user_context | { + new_user_context = user_context.new_clone(); + }); + + if flags.vm { + new_user_context.set_user_stack_top(userstack); + } + + let new_addrspace; + + if flags.vm { + new_addrspace = self.addrspace.clone(); + } else { + let addrspace = self.addrspace.fork(); + new_user_context.set_addrspace(&addrspace); + new_addrspace = addrspace; + } + + new_user_context.skip_syscall_instruction(); + + if let Some(tls) = tls { + new_user_context.set_tls(tls); + } + + let new_tcb = Self::new( + tid, + parent, + new_user_context, + new_addrspace, + Arc::new(SpinLock::new(self.fdtable.lock().fork())), + ); + + new_tcb + } + + pub fn new_exec( + &self, + file: File, + argv: &[&str], + envp: &[&str], + ) -> Result, Errno> { + // Read the shebang + let mut first_line = [0u8; 128]; + let n = file.read_at(&mut first_line, 0)?; + let first_line = core::str::from_utf8(&first_line[..n]).unwrap_or(""); + let first_line = first_line.lines().next().unwrap_or(""); + let first_line = first_line.trim_end_matches('\n'); + if first_line.starts_with("#!") { + let shebang = first_line.trim_start_matches("#!").trim(); + let mut parts = shebang.split_whitespace(); + if let Some(interpreter) = parts.next() { + let mut new_argv = vec![interpreter]; + for part in parts { + new_argv.push(part); + } + for arg in argv { + new_argv.push(arg); + } + + let interpreter_file = vfs::open_file( + interpreter, + FileFlags::dontcare(), + &Perm::new(PermFlags::X) + )?; + return self.new_exec(interpreter_file, &new_argv, envp); + } + } + + let file = Arc::new(file); + + let mut addrspace = AddrSpace::new(); + let (user_entry, dyn_info) = elf::loader::load_elf(&file, &mut addrspace)?; + + let mut auxv = Auxv::new(); + if let Some(dyn_info) = dyn_info { + auxv.push(AuxKey::BASE, dyn_info.interpreter_base); + auxv.push(AuxKey::PHDR, dyn_info.phdr_addr); + auxv.push(AuxKey::PHENT, dyn_info.phent as usize); + auxv.push(AuxKey::PHNUM, dyn_info.phnum as usize); + auxv.push(AuxKey::ENTRY, dyn_info.user_entry); + } + + auxv.push(AuxKey::PAGESZ, arch::PGSIZE); + auxv.push(AuxKey::RANDOM, config::USER_RANDOM_ADDR_BASE); + + let usetstack_top = addrspace.create_user_stack(argv, envp, &auxv)?; + + let mut new_user_context = UserContext::new(); + new_user_context.set_user_stack_top(usetstack_top); + new_user_context.set_user_entry(user_entry); + + self.fdtable().lock().cloexec(); + + let new_tcb = TCB::new( + self.tid, + &self.parent, + new_user_context, + addrspace, + self.fdtable().clone(), + ); + + Ok(new_tcb) + } + + pub fn get_tid(&self) -> i32 { + self.tid + } + + pub fn get_user_context_uaddr(&self) -> usize { + self.user_context_uaddr + } + + pub fn get_user_context_ptr(&self) -> *mut UserContext { + self.user_context_ptr + } + + pub fn get_parent(&self) -> &Arc { + &self.parent + } + + pub fn with_user_context(&self, f: F) -> R + where + F: FnOnce(&UserContext) -> R, + { + let user_context = unsafe { self.user_context_ptr.as_ref().unwrap() }; + f(user_context) + } + + pub fn with_user_context_mut(&self, f: F) -> R + where + F: FnOnce(&mut UserContext) -> R, + { + let user_context = unsafe { self.user_context_ptr.as_mut().unwrap() }; + f(user_context) + } + + pub fn user_context(&self) -> &mut UserContext { + unsafe { self.user_context_ptr.as_mut().unwrap() } + } + + pub fn get_addrspace(&self) -> &Arc { + &self.addrspace + } + + pub fn fdtable(&self) -> &Arc> { + &self.fdtable + } + + pub fn get_signal_mask(&self) -> SignalSet { + *self.signal_mask.lock() + } + + pub fn set_signal_mask(&self, mask: SignalSet) { + *self.signal_mask.lock() = mask; + } + + pub fn set_tid_address(&self, addr: usize) { + *self.tid_address.lock() = Some(addr); + } + + // pub fn wakeup(self: &Arc, event: Event) { + // let mut state = self.state().lock(); + // if state.state != TaskState::Blocked { + // return; + // } + // state.state = TaskState::Ready; + // *self.wakeup_event.lock() = Some(event); + + // scheduler::push_task(Task::User(self.clone())); + // } + + // pub fn wakeup_uninterruptible(self: &Arc, event: Event) { + // let mut state = self.state().lock(); + // match state.state { + // TaskState::Blocked | TaskState::BlockedUninterruptible => {}, + // _ => return, + // } + // state.state = TaskState::Ready; + // *self.wakeup_event.lock() = Some(event); + + // scheduler::push_task(Task::User(self.clone())); + // } + + // pub fn take_wakeup_event(&self) -> Option { + // self.wakeup_event.lock().take() + // } + + // pub fn run(&self) { + // let mut state = self.state.lock(); + // // assert!(state.state == TaskState::Ready); + // state.state = TaskState::Running; + // } + + pub fn state(&self) -> &SpinLock { + &self.state + } + + pub fn with_state_mut(&self, f: F) -> R + where + F: FnOnce(&mut TaskStateSet) -> R, + { + let mut state = self.state.lock(); + f(&mut state) + } + + pub fn exit(&self, code: u8) { + let mut state = self.state.lock(); + + if let Some(tid_address) = *self.tid_address.lock() { + if let Ok(tid_kaddr) = self.addrspace.translate_write(tid_address) { + // debug_assert!(tid_kaddr & 0x3 == 0); + unsafe { *(tid_kaddr as *mut Tid) = 0 }; + let _ = futex::wake(tid_kaddr, 1, u32::MAX); + } + } + + state.state = TaskState::Exited; + + drop(state); + + if self.parent.get_pid() == self.tid { + self.parent.exit(code); + } + } + + pub fn set_parent_waiting_vfork(&self, parent: Option>) { + *self.parent_waiting_vfork.lock() = parent; + } + + pub fn wake_parent_waiting_vfork(&self) { + if let Some(parent) = self.parent_waiting_vfork.lock().take() { + // kinfo!("Waking up parent task {} waiting for vfork", parent.tid()); + scheduler::wakeup_task_uninterruptible(parent, Event::VFork); + } + } +} + +impl Task for TCB { + fn tid(&self) -> Tid { + self.tid + } + + fn get_kcontext_ptr(&self) -> *mut KernelContext { + &self.kernel_context as *const KernelContext as *mut KernelContext + } + + fn kstack(&self) -> &KernelStack { + &self.kernel_stack + } + + fn tcb(&self) -> &TCB { + self + } + + fn run_if_ready(&self) -> bool { + let mut state = self.state.lock(); + if state.state != TaskState::Ready { + return false; + } + state.state = TaskState::Running; + return true; + } + + fn state_running_to_ready(&self) -> bool { + let mut state = self.state.lock(); + if state.state != TaskState::Running { + return false; + } + state.state = TaskState::Ready; + true + } + + fn block(&self, _reason: &str) -> bool { + debug_assert!(current::tid() == self.tid); + + let mut state = self.state.lock(); + match state.state { + TaskState::Ready | TaskState::Running => {}, + _ => return false, + } + state.state = TaskState::Blocked; + true + } + + fn block_uninterruptible(&self, _reason: &str) -> bool { + debug_assert!(current::tid() == self.tid); + + let mut state = self.state.lock(); + match state.state { + TaskState::Ready | TaskState::Running => {}, + _ => return false, + } + state.state = TaskState::BlockedUninterruptible; + true + } + + fn wakeup(&self, event: Event) -> bool { + let mut state = self.state().lock(); + if state.state != TaskState::Blocked { + return false; + } + state.state = TaskState::Ready; + *self.wakeup_event.lock() = Some(event); + true + } + + fn wakeup_uninterruptible(&self, event: Event) { + let mut state = self.state().lock(); + match state.state { + TaskState::Blocked | TaskState::BlockedUninterruptible => {}, + _ => return, + } + state.state = TaskState::Ready; + *self.wakeup_event.lock() = Some(event); + } + + fn take_wakeup_event(&self) -> Option { + self.wakeup_event.lock().take() + } +} + +unsafe impl Send for TCB {} +unsafe impl Sync for TCB {} diff --git a/src/kernel/trap.rs b/src/kernel/trap.rs new file mode 100644 index 0000000..7fd7e34 --- /dev/null +++ b/src/kernel/trap.rs @@ -0,0 +1,69 @@ +use crate::arch::UserContextTrait; +use crate::kernel::mm::MemAccessType; +use crate::kernel::scheduler::current; +use crate::kernel::ipc::{KSiFields, SiCode, signum}; +use crate::kernel::syscall; +use crate::kernel::event::timer; +use crate::kwarn; + +pub fn trap_enter() { + let tcb = current::tcb(); + let counter = &mut tcb.time_counter.lock(); + counter.system_start = Some(timer::now()); + let user_start = counter.user_start.take().unwrap(); + counter.user_time += timer::now() - user_start; +} + +pub fn trap_return() { + let tcb = current::tcb(); + tcb.recive_pending_signal_from_parent(); + tcb.handle_signal(); + + let counter = &mut tcb.time_counter.lock(); + counter.user_start = Some(timer::now()); + let system_start = counter.system_start.take().unwrap(); + counter.system_time += timer::now() - system_start; +} + +pub fn timer_interrupt() { + timer::interrupt(); + + if !current::is_clear() { + current::schedule(); + } +} + +pub fn syscall(num: usize, args: &syscall::Args) -> usize { + let ret = match syscall::syscall(num, args) { + Ok(ret) => ret, + Err(errno) => { + -(errno as isize) as usize + } + }; + + current::tcb().user_context().skip_syscall_instruction(); + + current::schedule(); + + ret +} + +pub fn memory_fault(addr: usize, access_type: MemAccessType) { + let fixed = current::addrspace().try_to_fix_memory_fault(addr, access_type); + + if !fixed { + kwarn!("Failed to fix memory fault at address: {:#x}, access_type={:?}, pc={:#x}, tid={}, KILLED", addr, access_type, crate::arch::get_user_pc(), current::tid()); + // TODO: Implement the sicode and fields for memory fault + current::pcb().send_signal(signum::SIGSEGV, SiCode::SI_KERNEL, KSiFields::Empty, None).unwrap(); + current::schedule(); + } +} + +pub fn illegal_inst() { + // TODO: Implement the sicode and fields for illegal inst + current::pcb().send_signal(signum::SIGSEGV, SiCode::SI_KERNEL, KSiFields::Empty, None).unwrap(); +} + +pub fn memory_misaligned() { + current::pcb().send_signal(signum::SIGBUS, SiCode::SI_KERNEL, KSiFields::Empty, None).unwrap(); +} \ No newline at end of file diff --git a/src/kernel/uapi/dirent.rs b/src/kernel/uapi/dirent.rs new file mode 100644 index 0000000..0af3465 --- /dev/null +++ b/src/kernel/uapi/dirent.rs @@ -0,0 +1,36 @@ +use crate::fs::FileType; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct Dirent { + pub d_ino: u64, + pub d_off: i64, + pub d_reclen: u16, + pub d_type: u8, +} + +pub enum DirentType { + Unknown = 0, + FIFO = 1, + CharDevice = 2, + Directory = 4, + BlockDevice = 6, + RegularFile = 8, + Symlink = 10, + Socket = 12, +} + +impl From for DirentType { + fn from(ft: FileType) -> Self { + match ft { + FileType::Regular => DirentType::RegularFile, + FileType::Directory => DirentType::Directory, + FileType::CharDevice => DirentType::CharDevice, + FileType::BlockDevice => DirentType::BlockDevice, + FileType::FIFO => DirentType::FIFO, + FileType::Symlink => DirentType::Symlink, + FileType::Socket => DirentType::Socket, + FileType::Unknown => DirentType::Unknown, + } + } +} \ No newline at end of file diff --git a/src/kernel/uapi/filestat.rs b/src/kernel/uapi/filestat.rs new file mode 100644 index 0000000..65d7f5d --- /dev/null +++ b/src/kernel/uapi/filestat.rs @@ -0,0 +1,59 @@ +use crate::kernel::syscall::UserStruct; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FileStat { + pub st_dev: u64, + pub st_ino: u64, + pub st_mode: u32, + pub st_nlink: u32, + pub st_uid: u32, + pub st_gid: u32, + pub st_rdev: u64, + __pad: u64, + pub st_size: i64, + pub st_blksize: i32, + __pad2: i32, + pub st_blocks: u64, + pub st_atime_sec: i64, + pub st_atime_nsec: i64, + pub st_mtime_sec: i64, + pub st_mtime_nsec: i64, + pub st_ctime_sec: i64, + pub st_ctime_nsec: i64, + __unused: [u32; 2], +} + +impl FileStat { + pub fn empty() -> Self { + FileStat { + st_dev: 0, + st_ino: 0, + st_mode: 0, + st_nlink: 0, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + __pad: 0, + st_size: 0, + st_blksize: 4096, + __pad2: 0, + st_blocks: 0, + st_atime_sec: 0, + st_atime_nsec: 0, + st_mtime_sec: 0, + st_mtime_nsec: 0, + st_ctime_sec: 0, + st_ctime_nsec: 0, + __unused: [0; 2], + } + } +} + +impl Default for FileStat { + fn default() -> Self { + Self::empty() + } +} + +impl UserStruct for FileStat {} diff --git a/src/kernel/uapi/mod.rs b/src/kernel/uapi/mod.rs new file mode 100644 index 0000000..960b4ec --- /dev/null +++ b/src/kernel/uapi/mod.rs @@ -0,0 +1,18 @@ +#![allow(non_camel_case_types)] + +mod openflags; +mod dirent; +mod filestat; +mod timespec; +mod sigaction; +mod statfs; + +pub use openflags::*; +pub use dirent::*; +pub use filestat::*; +pub use timespec::*; +pub use sigaction::*; +pub use statfs::*; + +pub type uid_t = u32; +pub type Uid = u32; diff --git a/src/kernel/uapi/openflags.rs b/src/kernel/uapi/openflags.rs new file mode 100644 index 0000000..8a0fdae --- /dev/null +++ b/src/kernel/uapi/openflags.rs @@ -0,0 +1,27 @@ +use bitflags::bitflags; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct OpenFlags: usize { + const O_RDONLY = 0; + const O_WRONLY = 1 << 0; + const O_RDWR = 1 << 1; + const O_CREATE = 1 << 6; + const O_EXCL = 1 << 7; + const O_NOCTTY = 1 << 8; + const O_TRUNC = 1 << 9; + const O_APPEND = 1 << 10; + const O_NONBLOCK = 1 << 11; + const O_DSYNC = 1 << 12; + const O_ASYNC = 1 << 13; + const O_DIRECT = 1 << 14; + const O_LARGEFILE = 1 << 15; + const O_DIRECTORY = 1 << 16; + const O_NOFOLLOW = 1 << 17; + const O_NOATIME = 1 << 18; + const O_CLOEXEC = 1 << 19; + const O_SYNC = (1 << 20) | (1 << 12); + const O_PATH = 1 << 21; + const O_TMPFILE = (1 << 22) | (1 << 16); + } +} \ No newline at end of file diff --git a/src/kernel/uapi/sigaction.rs b/src/kernel/uapi/sigaction.rs new file mode 100644 index 0000000..446adec --- /dev/null +++ b/src/kernel/uapi/sigaction.rs @@ -0,0 +1,37 @@ +use crate::kernel::errno::Errno; +use crate::kernel::errno::SysResult; +use crate::kernel::ipc::SignalAction; +use crate::kernel::ipc::SignalActionFlags; +use crate::kernel::ipc::SignalSet; +use crate::kernel::syscall::UserStruct; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct Sigaction { + pub sa_handler: usize, + pub sa_flags: u32, + pub sa_mask: SignalSet, +} + +impl TryInto for Sigaction { + type Error = Errno; + fn try_into(self) -> SysResult { + Ok(SignalAction { + handler: self.sa_handler, + mask: self.sa_mask, + flags: SignalActionFlags::from_bits(self.sa_flags as u32).ok_or(Errno::EINVAL)?, + }) + } +} + +impl From for Sigaction { + fn from(item: SignalAction) -> Self { + Sigaction { + sa_handler: item.handler, + sa_mask: item.mask, + sa_flags: item.flags.bits(), + } + } +} + +impl UserStruct for Sigaction {} diff --git a/src/kernel/uapi/statfs.rs b/src/kernel/uapi/statfs.rs new file mode 100644 index 0000000..fd2511b --- /dev/null +++ b/src/kernel/uapi/statfs.rs @@ -0,0 +1,20 @@ +use crate::kernel::syscall::UserStruct; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Statfs { + pub f_type: u64, + pub f_bsize: u64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: u64, + pub f_namelen: u64, + pub f_frsize: u64, + pub f_flag: u64, + pub f_spare: [u64; 4], +} + +impl UserStruct for Statfs {} diff --git a/src/kernel/uapi/timespec.rs b/src/kernel/uapi/timespec.rs new file mode 100644 index 0000000..757c4f2 --- /dev/null +++ b/src/kernel/uapi/timespec.rs @@ -0,0 +1,64 @@ +use core::time::Duration; + +use crate::kernel::syscall::UserStruct; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Timespec { + pub tv_sec: u64, // seconds + pub tv_nsec: u64, // nanoseconds +} + +impl UserStruct for Timespec {} + +impl Into for Timespec { + fn into(self) -> Duration { + Duration::new(self.tv_sec as u64, self.tv_nsec as u32) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Timeval { + pub tv_sec: u64, // seconds + pub tv_usec: u64, // microseconds +} + +impl UserStruct for Timeval {} + +impl Into for Timeval { + fn into(self) -> Duration { + Duration::new(self.tv_sec as u64, (self.tv_usec * 1000) as u32) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Timespec32 { + pub tv_sec: i32, // seconds + pub tv_nsec: i32, // nanoseconds +} + +impl UserStruct for Timespec32 {} + +impl Into for Timespec32 { + fn into(self) -> Duration { + Duration::new(self.tv_sec as u64, self.tv_nsec as u32) + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct TimeVal { + pub tv_sec: usize, // seconds + pub tv_usec: usize, // microseconds +} + +impl From for TimeVal { + fn from(dur: Duration) -> Self { + TimeVal { + tv_sec: dur.as_secs() as usize, + tv_usec: (dur.subsec_nanos() / 1000) as usize, + } + } +} diff --git a/src/kernel/usync/futex/futex.rs b/src/kernel/usync/futex/futex.rs new file mode 100644 index 0000000..f50db9f --- /dev/null +++ b/src/kernel/usync/futex/futex.rs @@ -0,0 +1,146 @@ +use alloc::collections::BTreeMap; +use alloc::collections::LinkedList; +use alloc::sync::Arc; + +use crate::kernel::errno::{Errno, SysResult}; +use crate::kernel::event::Event; +use crate::kernel::scheduler; +use crate::kernel::scheduler::Task; +use crate::kernel::scheduler::current; +use crate::klib::SpinLock; + +struct FutexWaitQueueItem { + tcb: Arc, + bitset: u32, +} + +pub struct Futex { + kvalue: &'static i32, + wait_list: LinkedList, +} + +impl Futex { + pub fn new(kvalue: &'static i32) -> Self { + Self { + kvalue, + wait_list: LinkedList::new(), + } + } + + pub fn wait_current(&mut self, expected: i32, bitset: u32) -> SysResult<()> { + if *self.kvalue != expected { + return Err(Errno::EAGAIN); + } + + self.wait_list.push_back(FutexWaitQueueItem { + tcb: current::task().clone(), + bitset, + }); + + Ok(()) + } + + pub fn wake(&mut self, num: usize, mask: u32) -> SysResult { + let mut woken = 0; + let mut cursor = self.wait_list.cursor_front_mut(); + while let Some(item) = cursor.current() { + if (item.bitset & mask) != 0 { + let item = cursor.remove_current().unwrap(); + // { + // let mut state = item.tcb.state().lock(); + // state.event = None; + // } + + scheduler::wakeup_task(item.tcb.clone(), Event::Futex); + + woken += 1; + if woken >= num { + break; + } + } else { + cursor.move_next(); + } + } + + Ok(woken) + } +} + +pub struct FutexManager { + futexes: SpinLock>>, +} + +impl FutexManager { + const fn new() -> Self { + Self { + futexes: SpinLock::new(BTreeMap::new()), + } + } + + fn wait_current(&self, kaddr: usize, expected: i32, mask: u32) -> SysResult<()> { + // kinfo!("wait {:#x}", kaddr); + let mut futexes = self.futexes.lock(); + let futex = futexes.entry(kaddr).or_insert_with(|| SpinLock::new(Futex::new(unsafe { &*(kaddr as *const i32) }))); + + let mut futex = futex.lock(); + futex.wait_current(expected, mask) + } + + fn wake(&self, kaddr: usize, num: usize, mask: u32) -> SysResult { + let futexes = self.futexes.lock(); + // kinfo!("wake {:#x}", kaddr); + if let Some(futex) = futexes.get(&kaddr) { + let mut futex = futex.lock(); + futex.wake(num, mask) + } else { + Ok(0) + } + } + + fn requeue(&self, kaddr: usize, kaddr2: usize, num: usize, val: Option) -> SysResult { + let mut futexes = self.futexes.lock(); + let mut pending = LinkedList::new(); + + let moved = if let Some(futex_spinlock) = futexes.get(&kaddr) { + let mut futex = futex_spinlock.lock(); + if let Some(val) = val { + if *futex.kvalue != val { + return Err(Errno::EAGAIN); + } + } + + let mut moved = 0; + let mut cursor = futex.wait_list.cursor_front_mut(); + while let Some(item) = cursor.remove_current() { + pending.push_back(item); + moved += 1; + if moved >= num { + break; + } + } + moved + } else { + return Ok(0); + }; + + let futex2_spinlock = futexes.entry(kaddr2).or_insert_with(|| SpinLock::new(Futex::new(unsafe { &*(kaddr2 as *const i32) }))); + let mut futex2 = futex2_spinlock.lock(); + futex2.wait_list.append(&mut pending); + + Ok(moved) + } +} + +static FUTEX_MANAGER: FutexManager = FutexManager::new(); + +pub fn wait_current(kaddr: usize, expected: i32, bitset: u32) -> SysResult<()> { + FUTEX_MANAGER.wait_current(kaddr, expected, bitset) +} + +pub fn wake(kaddr: usize, num: usize, mask: u32) -> SysResult { + FUTEX_MANAGER.wake(kaddr, num, mask) +} + +pub fn requeue(kaddr: usize, kaddr2: usize, num: usize, val: Option) -> SysResult { + FUTEX_MANAGER.requeue(kaddr, kaddr2, num, val) +} diff --git a/src/kernel/usync/futex/mod.rs b/src/kernel/usync/futex/mod.rs new file mode 100644 index 0000000..38bb763 --- /dev/null +++ b/src/kernel/usync/futex/mod.rs @@ -0,0 +1,5 @@ +mod futex; +mod robust; + +pub use futex::*; +pub use robust::*; diff --git a/src/kernel/usync/futex/robust.rs b/src/kernel/usync/futex/robust.rs new file mode 100644 index 0000000..59bba62 --- /dev/null +++ b/src/kernel/usync/futex/robust.rs @@ -0,0 +1,28 @@ +use crate::kernel::syscall::UserStruct; +use crate::kernel::task::TCB; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct RobustList { + next: usize, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct RobustListHead { + list: RobustList, + futex_offset: usize, + pending: usize, +} + +impl UserStruct for RobustListHead {} + +impl TCB { + pub fn set_robust_list(&self, uaddr: usize) { + *self.robust_list.lock() = Some(uaddr); + } + + pub fn get_robust_list(&self) -> Option { + *self.robust_list.lock() + } +} diff --git a/src/kernel/usync/mod.rs b/src/kernel/usync/mod.rs new file mode 100644 index 0000000..c3b8a74 --- /dev/null +++ b/src/kernel/usync/mod.rs @@ -0,0 +1 @@ +pub mod futex; \ No newline at end of file diff --git a/src/klib/initcell.rs b/src/klib/initcell.rs new file mode 100644 index 0000000..545630c --- /dev/null +++ b/src/klib/initcell.rs @@ -0,0 +1,40 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::ops::Deref; +use core::sync::atomic::{AtomicBool, Ordering}; + +pub struct InitedCell { + value: UnsafeCell>, + is_inited: AtomicBool, +} + +impl InitedCell { + pub const fn uninit() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::uninit()), + is_inited: AtomicBool::new(false) + } + } + + pub fn init(&self, value: T) { + debug_assert!(self.is_inited.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok(), "InitedCell has already been initialized."); + + unsafe { + *self.value.get() = MaybeUninit::new(value); + } + } +} + +impl Deref for InitedCell { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + debug_assert!(self.is_inited.load(Ordering::Relaxed), "Cannot access uninitialized InitedCell."); + unsafe { + (*self.value.get()).assume_init_ref() + } + } +} + +unsafe impl Sync for InitedCell {} diff --git a/src/klib/kalloc.rs b/src/klib/kalloc.rs new file mode 100644 index 0000000..d0982bc --- /dev/null +++ b/src/klib/kalloc.rs @@ -0,0 +1,51 @@ +use spin::Mutex; +use core::alloc::{GlobalAlloc, Layout}; +use core::ffi::c_void; +use crate::println; + +unsafe extern "C" { + fn init_heap(start: *mut c_void, size: usize); + fn malloc_aligned(align: usize, size: usize) -> *mut c_void; + fn free(ptr: *mut c_void); +} + +struct HeapAllocator { + mutex: Mutex<()>, +} + +impl HeapAllocator { + pub const fn new() -> Self { + HeapAllocator { + mutex: Mutex::new(()), + } + } + + pub fn init(&self, heap_start: usize, heap_size: usize) { + unsafe { + init_heap(heap_start as *mut c_void, heap_size); + println!("Heap initialized successfully!"); + } + } +} + +unsafe impl GlobalAlloc for HeapAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let _ = self.mutex.lock(); + let ptr = unsafe { malloc_aligned(layout.align(), layout.size()) as *mut u8 }; + debug_assert!(!ptr.is_null()); + ptr + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + let _ = self.mutex.lock(); + unsafe { free(ptr as *mut c_void) }; + } +} + +#[global_allocator] +static ALLOCATOR: HeapAllocator = HeapAllocator::new(); + +#[unsafe(link_section = ".text.init")] +pub fn init(heap_start: usize, heap_size: usize) { + ALLOCATOR.init(heap_start, heap_size); +} diff --git a/src/klib/klog.rs b/src/klib/klog.rs new file mode 100644 index 0000000..c16c0c9 --- /dev/null +++ b/src/klib/klog.rs @@ -0,0 +1,148 @@ +use core::panic::PanicInfo; + +use crate::kernel::exit; +use crate::println; + +pub const COLOR_RESET: &str = "\x1b[0m"; +pub const COLOR_RED: &str = "\x1b[31m"; +pub const COLOR_YELLOW: &str = "\x1b[33m"; +pub const COLOR_BLUE: &str = "\x1b[34m"; +pub const COLOR_GREEN: &str = "\x1b[32m"; +pub const COLOR_CYAN: &str = "\x1b[36m"; +pub const COLOR_BOLD: &str = "\x1b[1m"; + +#[cfg(feature = "log-warn")] +#[macro_export] +macro_rules! kwarn { + ($($arg:tt)*) => { + $crate::println!( + "{}{}[{}]{} {} (tid={}) @ {}:{}:{}{}", + $crate::klib::klog::COLOR_BOLD, + $crate::klib::klog::COLOR_YELLOW, + "WARN", + $crate::klib::klog::COLOR_RESET, + format_args!($($arg)*), + $crate::kernel::scheduler::current::tid(), + file!(), + line!(), + column!(), + $crate::klib::klog::COLOR_RESET + ) + }; +} + +#[cfg(not(feature = "log-warn"))] +#[macro_export] +macro_rules! kwarn { + () => {}; + ($($arg:tt)*) => {}; +} + +#[cfg(feature = "log-info")] +#[macro_export] +macro_rules! kinfo { + ($($arg:tt)*) => { + $crate::println!( + "{}{}[{}]{} {} (tid={}) @ {}:{}:{}{}", + $crate::klib::klog::COLOR_BOLD, + $crate::klib::klog::COLOR_BLUE, + "INFO", + $crate::klib::klog::COLOR_RESET, + format_args!($($arg)*), + $crate::kernel::scheduler::current::tid(), + file!(), + line!(), + column!(), + $crate::klib::klog::COLOR_RESET + ) + } +} + +#[cfg(not(feature = "log-info"))] +#[macro_export] +macro_rules! kinfo { + () => {}; + ($($arg:tt)*) => {}; +} + +#[cfg(feature = "log-debug")] +#[macro_export] +macro_rules! kdebug { + ($($arg:tt)*) => { + $crate::println!( + "{}{}[{}]{} {} (tid={}) @ {}:{}:{}{}", + $crate::klib::klog::COLOR_BOLD, + $crate::klib::klog::COLOR_CYAN, + "DEBUG", + $crate::klib::klog::COLOR_RESET, + format_args!($($arg)*), + $crate::kernel::scheduler::current::tid(), + file!(), + line!(), + column!(), + $crate::klib::klog::COLOR_RESET + ); + } +} + +#[cfg(not(feature = "log-debug"))] +#[macro_export] +macro_rules! kdebug { + ($($arg:tt)*) => {}; +} + +#[cfg(feature = "log-trace")] +#[macro_export] +macro_rules! ktrace { + ($($arg:tt)*) => { + $crate::println!( + "{}{}[{}]{} {} (tid={}) @ {}:{}:{}{}", + $crate::klib::klog::COLOR_BOLD, + $crate::klib::klog::COLOR_GREEN, + "TRACE", + $crate::klib::klog::COLOR_RESET, + format_args!($($arg)*), + $crate::kernel::scheduler::current::tid(), + file!(), + line!(), + column!(), + $crate::klib::klog::COLOR_RESET + ); + }; +} +#[cfg(not(feature = "log-trace"))] +#[macro_export] +macro_rules! ktrace { + () => {}; + ($($arg:tt)*) => {}; +} + +#[panic_handler] +pub fn panic_handler(info: &PanicInfo) -> ! { + if let Some(location) = info.location() { + println!( + "{}{}[{}]{} {} (tid={}) @ {}:{}{}", + COLOR_BOLD, + COLOR_RED, + "PANIC", + COLOR_RESET, + info.message(), + crate::kernel::scheduler::current::tid(), + location.file(), + location.line(), + COLOR_RESET + ); + } else { + println!( + "{}{}[{}]{} Unknown location - {}{}", + COLOR_BOLD, + COLOR_RED, + "PANIC", + COLOR_RESET, + info.message(), + COLOR_RESET + ); + } + + exit(); +} \ No newline at end of file diff --git a/src/klib/ksync.rs b/src/klib/ksync.rs new file mode 100644 index 0000000..1b2769a --- /dev/null +++ b/src/klib/ksync.rs @@ -0,0 +1,161 @@ +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; + +#[cfg(not(feature = "no-smp"))] +use core::sync::atomic::AtomicBool; + +use crate::kernel::scheduler::{current, Tid}; + +pub trait LockerTrait { + fn is_locked(&self) -> bool; + fn lock(&self); + fn unlock(&self); +} + +pub struct LockGuard<'a, T, R: LockerTrait> { + data: &'a mut T, + mutex: &'a Mutex, +} + +impl Deref for LockGuard<'_, T, R> { + type Target = T; + fn deref(&self) -> &Self::Target { + self.data + } +} + +impl DerefMut for LockGuard<'_, T, R> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.data + } +} + +impl Drop for LockGuard<'_, T, R> { + fn drop(&mut self) { + self.mutex.unlock(); + } +} + +pub struct Mutex { + data: UnsafeCell, + lock: R, + holder: UnsafeCell, +} + +impl Mutex { + pub fn deadlock_detect(&self) -> bool { + if self.lock.is_locked() { + let tid = current::tid(); + if tid >= 0 && *self.holder() == tid { + return true; + } + } + return false; + } + + pub fn lock(&self) -> LockGuard<'_, T, R> { + #[cfg(feature = "deadlock-detect")] + if self.deadlock_detect() { + let tid = current::tid(); + panic!("Deadlock detected in Mutex: current thread {} is trying to lock a mutex it already holds", tid); + } + + self.lock.lock(); + *self.holder() = current::tid(); + + LockGuard { + data: unsafe { &mut *self.data.get() }, + mutex: self, + } + } + + fn unlock(&self) { + self.lock.unlock(); + } + + fn holder(&self) -> &mut Tid { + unsafe { &mut *self.holder.get() } + } +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +pub struct SpinLocker { + #[cfg(feature = "no-smp")] + lock: UnsafeCell, + + #[cfg(not(feature = "no-smp"))] + lock: AtomicBool, +} + +impl SpinLocker { + pub const fn new() -> Self { + SpinLocker { + #[cfg(feature = "no-smp")] + lock: UnsafeCell::new(false), + + #[cfg(not(feature = "no-smp"))] + lock: AtomicBool::new(false), + } + } +} + +impl LockerTrait for SpinLocker { + fn is_locked(&self) -> bool { + #[cfg(feature = "no-smp")] + { unsafe { *self.lock.get() } } + #[cfg(not(feature = "no-smp"))] + self.lock.load(core::sync::atomic::Ordering::Relaxed) + } + + fn lock(&self) { + #[cfg(feature = "no-smp")] + unsafe { + if *self.lock.get() { + panic!("Deadlock detected in SpinLocker: single-core system cannot re-lock an already locked SpinLocker"); + } + *self.lock.get() = true; + } + #[cfg(not(feature = "no-smp"))] + while self.lock.compare_exchange_weak(false, true, core::sync::atomic::Ordering::Acquire, core::sync::atomic::Ordering::Relaxed).is_err() {} + } + + fn unlock(&self) { + #[cfg(feature = "no-smp")] + unsafe { + *self.lock.get() = false; + } + #[cfg(not(feature = "no-smp"))] + self.lock.store(false, core::sync::atomic::Ordering::Release); + } +} + +pub type SpinLock = Mutex; + +impl SpinLock { + pub const fn new(data: T) -> Self { + SpinLock { + data: UnsafeCell::new(data), + lock: SpinLocker::new(), + holder: UnsafeCell::new(-1), + } + } +} + +#[macro_export] +macro_rules! lock_debug { + ($mutex:expr) => {{ + let mutex = &$mutex; + let current_tid = $crate::kernel::scheduler::current::tid(); + + if mutex.deadlock_detect() { + panic!( + "Deadlock detected in Mutex: current thread {} is trying to lock a mutex it already holds", + current_tid, + ); + } + + mutex.lock() + }}; +} diff --git a/src/klib/mod.rs b/src/klib/mod.rs new file mode 100644 index 0000000..fc22215 --- /dev/null +++ b/src/klib/mod.rs @@ -0,0 +1,9 @@ +pub mod print; +pub mod kalloc; +pub mod klog; +pub mod ksync; +pub mod initcell; +pub mod random; + +pub use ksync::SpinLock; +pub use initcell::InitedCell; diff --git a/src/klib/print.rs b/src/klib/print.rs new file mode 100644 index 0000000..7a67f20 --- /dev/null +++ b/src/klib/print.rs @@ -0,0 +1,33 @@ +use core::fmt::Write; + +use crate::driver::chosen::kconsole::kputs; + +pub struct Writer; +impl Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + kputs(s); + Ok(()) + } +} + +pub fn _print(args: core::fmt::Arguments) { + let mut writer = Writer; + writer.write_fmt(args).unwrap(); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + $crate::klib::print::_print(format_args!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! println { + () => { + $crate::print!("\n") + }; + ($($arg:tt)*) => { + $crate::print!("{}\n", format_args!($($arg)*)) + }; +} \ No newline at end of file diff --git a/src/klib/random.rs b/src/klib/random.rs new file mode 100644 index 0000000..a2fefd2 --- /dev/null +++ b/src/klib/random.rs @@ -0,0 +1,17 @@ +use spin::Lazy; + +use crate::arch; + +use super::SpinLock; + +static STATE: Lazy> = Lazy::new(|| { + let time = arch::get_time_us() as u32; + SpinLock::new(time ^ 0xDEECE66D) +}); + +pub fn random() -> u32 { + // A very simple linear congruential generator (LCG) + let mut state = STATE.lock(); + *state = state.wrapping_mul(1664525).wrapping_add(1013904223); + *state +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a945119 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] +#![feature(alloc_error_handler)] +#![feature(linked_list_cursors)] +#![feature(linked_list_retain)] + +extern crate alloc; + +mod kernel; +mod klib; +mod fs; +mod driver; +mod arch; +// mod platform; diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..f4ff1f3 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "platform-riscv-common")] +mod riscv_common; + +#[cfg(feature = "platform-qemu-virt-riscv64")] +mod qemu_virt_riscv64; + +#[cfg(feature = "platform-qemu-virt-riscv64")] +pub use qemu_virt_riscv64::*; diff --git a/src/platform/qemu_virt_riscv64/addr.rs b/src/platform/qemu_virt_riscv64/addr.rs new file mode 100644 index 0000000..c08a33a --- /dev/null +++ b/src/platform/qemu_virt_riscv64/addr.rs @@ -0,0 +1,10 @@ +// use super::config::KERNEL_VADDR_OFFSET; +// use crate::arch::KADDR_OFFSET; + +// pub fn kaddr_to_paddr(kaddr: usize) -> usize { +// kaddr - KADDR_OFFSET +// } + +// pub fn paddr_to_kaddr(paddr: usize) -> usize { +// paddr + KADDR_OFFSET +// } diff --git a/src/platform/qemu_virt_riscv64/config.rs b/src/platform/qemu_virt_riscv64/config.rs new file mode 100644 index 0000000..c6373ba --- /dev/null +++ b/src/platform/qemu_virt_riscv64/config.rs @@ -0,0 +1,5 @@ +pub const KERNEL_PMEM_TOP: usize = 0x80000000 + 0x8000000; // 256MiB + +pub const TRAMPOLINE_BASE: usize = 0_usize.wrapping_sub(4096); + +pub const TIMER_FREQ: usize = 10000000; diff --git a/src/platform/qemu_virt_riscv64/mod.rs b/src/platform/qemu_virt_riscv64/mod.rs new file mode 100644 index 0000000..5bf1870 --- /dev/null +++ b/src/platform/qemu_virt_riscv64/mod.rs @@ -0,0 +1,2 @@ +pub mod config; +mod addr; diff --git a/src/platform/riscv_common/mod.rs b/src/platform/riscv_common/mod.rs new file mode 100644 index 0000000..ddcf68d --- /dev/null +++ b/src/platform/riscv_common/mod.rs @@ -0,0 +1,5 @@ +// mod sbi; +// mod time; + +// pub use sbi::{shutdown, putchar}; +// pub use time::*; diff --git a/src/platform/riscv_common/sbi.rs b/src/platform/riscv_common/sbi.rs new file mode 100644 index 0000000..3029e10 --- /dev/null +++ b/src/platform/riscv_common/sbi.rs @@ -0,0 +1,42 @@ +struct SBIRet { + _error: usize, + _value: usize, +} + +fn sbi_call(fid: usize, eid: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) -> SBIRet { + let mut error; + let mut value; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") arg0 => error, + inlateout("a1") arg1 => value, + in("a2") arg2, + in("a3") arg3, + in("a4") arg4, + in("a5") arg5, + in("a6") fid, + in("a7") eid, + options(nostack, preserves_flags) + ); + } + SBIRet { + _error: error, + _value: value, + } +} + +pub fn shutdown() -> ! { + sbi_call(0x0, 0x8, 0, 0, 0, 0, 0, 0); + + loop { + unsafe { + core::arch::asm!("wfi"); + } + } +} + +pub fn putchar(c: u8) -> () { + sbi_call(0x0, 0x1, c as usize, 0, 0, 0, 0, 0); +} diff --git a/src/platform/riscv_common/time.rs b/src/platform/riscv_common/time.rs new file mode 100644 index 0000000..99c26f0 --- /dev/null +++ b/src/platform/riscv_common/time.rs @@ -0,0 +1,22 @@ +use crate::platform::config::TIMER_FREQ; + +fn read_time() -> u64 { + let time: u64; + unsafe { core::arch::asm!("csrr {}, time", out(reg) time); } + time +} + +pub fn get_time_us() -> u64 { + read_time() * 1000000 as u64 / TIMER_FREQ as u64 +} + +#[allow(dead_code)] +pub fn clear_timer_interrupt() { + let t = u64::MAX; + unsafe { core::arch::asm!("csrw stimecmp, {}", in(reg) t); } +} + +pub fn set_next_timer_us(us: u64) { + let next_time = read_time() + us * 1000000 as u64 / TIMER_FREQ as u64; + unsafe { core::arch::asm!("csrw stimecmp, {}", in(reg) next_time); } +} diff --git a/usertests/.gitignore b/usertests/.gitignore new file mode 100644 index 0000000..d1d2289 --- /dev/null +++ b/usertests/.gitignore @@ -0,0 +1,5 @@ +build + +# clangd +.cache +compile_commands.json diff --git a/usertests/basic-glibc-static/Makefile b/usertests/basic-glibc-static/Makefile new file mode 100644 index 0000000..6b32310 --- /dev/null +++ b/usertests/basic-glibc-static/Makefile @@ -0,0 +1,34 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +GLIBC_DIR = /home/rache/code/glibc/build/riscv64-linux-gnu +CRT_FILES_BEGIN = $(GLIBC_DIR)/csu/crt1.o $(GLIBC_DIR)/csu/crti.o +CRT_FILES_END = $(GLIBC_DIR)/csu/crtn.o + +CFLAGS += -Wall -Wextra -static -nostdlib -nostartfiles +LDFLAGS += -L$(GLIBC_DIR) -lc -lgcc -lgcc_eh + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(OUTPUT_DIR)/%: $(SRC_DIR)/%.c + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + $(CC) $(CFLAGS) $(CRT_FILES_BEGIN) $< -o $@ $(LDFLAGS) $(CRT_FILES_END) + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) + +show-tests: + @echo "Auto-discovered tests:" + @for test in $(TESTS); do echo " - $$test"; done + @for test in $(TESTS); do echo " - $$test"; done diff --git a/usertests/basic-glibc-static/dummy.c b/usertests/basic-glibc-static/dummy.c new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/usertests/basic-glibc-static/dummy.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc-static/fork.c b/usertests/basic-glibc-static/fork.c new file mode 100644 index 0000000..1fe27f4 --- /dev/null +++ b/usertests/basic-glibc-static/fork.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int main() { + printf("Hello, World!\n"); + int pid = fork(); + if (pid < 0) { + perror("fork failed"); + return 1; + } else if (pid == 0) { + printf("Child process created with PID: %d\n", getpid()); + } else { + // waitpid(pid, NULL, 0); + printf("Parent process with PID: %d created child with PID: %d\n", + getpid(), pid); + waitpid(-1, NULL, 0); + } + + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc-static/forkexec-child.c b/usertests/basic-glibc-static/forkexec-child.c new file mode 100644 index 0000000..3172408 --- /dev/null +++ b/usertests/basic-glibc-static/forkexec-child.c @@ -0,0 +1,12 @@ +#include + +int main(int argc, char *argv[], char *envp[]) { + printf("Hello from the child process!\n"); + for (int i = 0; i < argc; i++) { + printf("Argument %d: %s\n", i, argv[i]); + } + for (int i = 0; envp[i] != NULL; i++) { + printf("Environment variable %d: %s\n", i, envp[i]); + } + return 0; +} diff --git a/usertests/basic-glibc-static/forkexec.c b/usertests/basic-glibc-static/forkexec.c new file mode 100644 index 0000000..d8f63a4 --- /dev/null +++ b/usertests/basic-glibc-static/forkexec.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int main() { + printf("Hello, World!\n"); + int pid = fork(); + if (pid < 0) { + perror("fork failed"); + return 1; + } + + if (pid == 0) { + char *args[] = {"/basic-glibc/forkexec-child", "argv[1]", "argv[2]", NULL}; + char *envp[] = {"env1=var1", "env2=var2", NULL}; + execve("/basic-glibc/forkexec-child", args, envp); + } else { + waitpid(pid, NULL, 0); + printf("Parent process with PID: %d created child with PID: %d\n", + getpid(), pid); + } + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc-static/getdents.c b/usertests/basic-glibc-static/getdents.c new file mode 100644 index 0000000..29881e6 --- /dev/null +++ b/usertests/basic-glibc-static/getdents.c @@ -0,0 +1,62 @@ +#define _GNU_SOURCE +#include /* Defines DT_* constants */ +#include +#include +#include +#include +#include +#include +#include + +struct linux_dirent64 { + uint64_t d_ino; + int64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[0]; +}; + +#define BUF_SIZE 1024 + +int +main(int argc, char *argv[]) +{ + int fd; + char d_type; + char buf[BUF_SIZE]; + ssize_t nread; + struct linux_dirent64 *d; + + fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY); + if (fd == -1) + err(EXIT_FAILURE, "open"); + + for (;;) { + nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE); + if (nread == -1) + err(EXIT_FAILURE, "getdents"); + + if (nread == 0) + break; + + printf("--------------- nread=%ld ---------------\n", nread); + printf("inode# file type d_reclen d_off d_name\n"); + for (size_t bpos = 0; bpos < (size_t)nread;) { + d = (struct linux_dirent64 *) (buf + bpos); + printf("%8lu ", d->d_ino); + d_type = d->d_type; + printf("%-10s ", (d_type == DT_REG) ? "regular" : + (d_type == DT_DIR) ? "directory" : + (d_type == DT_FIFO) ? "FIFO" : + (d_type == DT_SOCK) ? "socket" : + (d_type == DT_LNK) ? "symlink" : + (d_type == DT_BLK) ? "block dev" : + (d_type == DT_CHR) ? "char dev" : "???"); + printf("%4d %10jd %s\n", d->d_reclen, + (intmax_t) d->d_off, d->d_name); + bpos += d->d_reclen; + } + } + + exit(EXIT_SUCCESS); +} diff --git a/usertests/basic-glibc-static/loopsleep.c b/usertests/basic-glibc-static/loopsleep.c new file mode 100644 index 0000000..8fe6429 --- /dev/null +++ b/usertests/basic-glibc-static/loopsleep.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include + +unsigned long long get_us() { + struct timeval tv; + syscall(SYS_gettimeofday, &tv, NULL); + // gettimeofday(&tv, NULL); + return (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec; +} + +void loop_sleep(unsigned long long us) { + unsigned long long end = get_us() + us; + unsigned long long now; + while ((now = get_us()) < end) { + // printf("now = %llu, end = %llu\n", now, end); + } +} + +int main() { + printf("Test loopsleep\n"); + + unsigned int i = 0; + while (1) { + loop_sleep(1000000); // sleep for 1 second + i ++; + printf("%u second passed\n", i); + fflush(stdout); + } + + return 0; +} diff --git a/usertests/basic-glibc-static/mkdir.c b/usertests/basic-glibc-static/mkdir.c new file mode 100644 index 0000000..ac874e1 --- /dev/null +++ b/usertests/basic-glibc-static/mkdir.c @@ -0,0 +1,20 @@ +#include +#include + +int main() { + const char *dirname = "testdir"; + int result = mkdir(dirname, 0755); + if (result == 0) { + printf("Directory '%s' created successfully.\n", dirname); + FILE *f = fopen("testdir", "r"); + if (f) { + fclose(f); + printf("Directory '%s' opened successfully.\n", dirname); + } else { + perror("Failed to open the directory"); + } + } else { + perror("mkdir failed"); + } + return 0; +} diff --git a/usertests/basic-glibc-static/open.c b/usertests/basic-glibc-static/open.c new file mode 100644 index 0000000..142a81b --- /dev/null +++ b/usertests/basic-glibc-static/open.c @@ -0,0 +1,28 @@ +#include + +int main() { + FILE *file = fopen("testopen.txt", "w"); + if (file == NULL) { + perror("Failed to open file"); + return 1; + } + + fprintf(file, "Hello, World!\n"); + fclose(file); + + // file = fopen("testfile.txt", "r"); + // if (file == NULL) { + // perror("Failed to open file"); + // return 1; + // } + + // char buffer[256]; + // if (fgets(buffer, sizeof(buffer), file) != NULL) { + // printf("Read from file: %s", buffer); + // } else { + // perror("Failed to read from file"); + // } + // fclose(file); + + return 0; +} diff --git a/usertests/basic-glibc-static/printf.c b/usertests/basic-glibc-static/printf.c new file mode 100644 index 0000000..4dab592 --- /dev/null +++ b/usertests/basic-glibc-static/printf.c @@ -0,0 +1,8 @@ +#include + +int main() { + const char *s = "Hello World!"; + printf("%s %d \n", s, 42); + + return 0; +} diff --git a/usertests/basic-glibc-static/sleep.c b/usertests/basic-glibc-static/sleep.c new file mode 100644 index 0000000..1caba3d --- /dev/null +++ b/usertests/basic-glibc-static/sleep.c @@ -0,0 +1,19 @@ + +#include +#include + +int main() { + printf("Sleeping for 2 seconds...\n"); + fflush(stdout); + + struct timespec req = {2, 0}; // 2 seconds, 0 nanoseconds + int r = nanosleep(&req, NULL); + if (r != 0) { + perror("sleep"); + return 1; + } + + printf("Awake!\n"); + + return 0; +} diff --git a/usertests/basic-glibc-static/write.c b/usertests/basic-glibc-static/write.c new file mode 100644 index 0000000..aae8233 --- /dev/null +++ b/usertests/basic-glibc-static/write.c @@ -0,0 +1,19 @@ +#include + +int main() { + FILE *file = fopen("testwrite.txt", "w"); + if (file == NULL) { + perror("Failed to open file"); + return 1; + } + + int r = fprintf(file, "Hello, World!\n"); + printf("fprintf returned: %d\n", r); + if (r < 0) { + perror("Failed to write to file"); + } + + fclose(file); + + return 0; +} diff --git a/usertests/basic-glibc/Makefile b/usertests/basic-glibc/Makefile new file mode 100644 index 0000000..8638bf8 --- /dev/null +++ b/usertests/basic-glibc/Makefile @@ -0,0 +1,30 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + @ echo "File to unlink" > $(OUTPUT_DIR)/to_unlink.txt + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) diff --git a/usertests/basic-glibc/bug_poll.c b/usertests/basic-glibc/bug_poll.c new file mode 100644 index 0000000..25799c5 --- /dev/null +++ b/usertests/basic-glibc/bug_poll.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include + +#define __NR_ppoll_time32 73 + +struct timespec32 { + int tv_sec; // seconds + int tv_nsec; // nanoseconds +}; + +int ppoll_time32(struct pollfd *fds, nfds_t nfds, const struct timespec32 *tmo_p) { + return syscall(__NR_ppoll_time32, fds, nfds, tmo_p, NULL, 0); +} + +int main() { + pid_t pid; + + int pipe0[2], pipe1[2]; + if (pipe(pipe0) < 0 || pipe(pipe1) < 0) { + perror("pipe"); + return 1; + } + + pid = fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (pid == 0) { + // Close both write ends in child + close(pipe0[1]); + close(pipe1[1]); + + // poll on read ends + struct pollfd pfds[2]; + pfds[0].fd = pipe0[0]; + pfds[0].events = POLLIN; + pfds[1].fd = pipe1[0]; + pfds[1].events = POLLIN; + + ppoll_time32(pfds, 2, NULL); + + sleep(2); // Ensure parent is waiting in ppoll_time32 + + ppoll_time32(pfds, 1, NULL); + + return 0; + } + + sleep(1); // Ensure child is waiting in ppoll_time32 + + write(pipe0[1], "x", 1); // Wake up child + + sleep(1); // Ensure child is waiting in ppoll_time32 again + + write(pipe1[1], "y", 1); // Wake up child again + + wait(NULL); + + return 0; +} diff --git a/usertests/basic-glibc/dummy.c b/usertests/basic-glibc/dummy.c new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/usertests/basic-glibc/dummy.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc/fork.c b/usertests/basic-glibc/fork.c new file mode 100644 index 0000000..1fe27f4 --- /dev/null +++ b/usertests/basic-glibc/fork.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int main() { + printf("Hello, World!\n"); + int pid = fork(); + if (pid < 0) { + perror("fork failed"); + return 1; + } else if (pid == 0) { + printf("Child process created with PID: %d\n", getpid()); + } else { + // waitpid(pid, NULL, 0); + printf("Parent process with PID: %d created child with PID: %d\n", + getpid(), pid); + waitpid(-1, NULL, 0); + } + + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc/forkexec-child.c b/usertests/basic-glibc/forkexec-child.c new file mode 100644 index 0000000..3172408 --- /dev/null +++ b/usertests/basic-glibc/forkexec-child.c @@ -0,0 +1,12 @@ +#include + +int main(int argc, char *argv[], char *envp[]) { + printf("Hello from the child process!\n"); + for (int i = 0; i < argc; i++) { + printf("Argument %d: %s\n", i, argv[i]); + } + for (int i = 0; envp[i] != NULL; i++) { + printf("Environment variable %d: %s\n", i, envp[i]); + } + return 0; +} diff --git a/usertests/basic-glibc/forkexec.c b/usertests/basic-glibc/forkexec.c new file mode 100644 index 0000000..d8f63a4 --- /dev/null +++ b/usertests/basic-glibc/forkexec.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int main() { + printf("Hello, World!\n"); + int pid = fork(); + if (pid < 0) { + perror("fork failed"); + return 1; + } + + if (pid == 0) { + char *args[] = {"/basic-glibc/forkexec-child", "argv[1]", "argv[2]", NULL}; + char *envp[] = {"env1=var1", "env2=var2", NULL}; + execve("/basic-glibc/forkexec-child", args, envp); + } else { + waitpid(pid, NULL, 0); + printf("Parent process with PID: %d created child with PID: %d\n", + getpid(), pid); + } + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc/fstat.c b/usertests/basic-glibc/fstat.c new file mode 100644 index 0000000..559931d --- /dev/null +++ b/usertests/basic-glibc/fstat.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#include + +#define TEST_FILE "test_fstat_file.txt" +#define TEST_CONTENT "Hello, fstat!" + +int main() { + int fd; + struct stat st; + ssize_t bytes_written; + + // 1. Create and open a new file + printf("Creating file: %s\n", TEST_FILE); + fd = open(TEST_FILE, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd < 0) { + perror("open failed"); + return 1; + } + + // 2. Write content to the file + printf("Writing content: \"%s\"\n", TEST_CONTENT); + bytes_written = write(fd, TEST_CONTENT, strlen(TEST_CONTENT)); + if (bytes_written < 0) { + perror("write failed"); + close(fd); + unlink(TEST_FILE); + return 1; + } + + // 3. Call fstat on the file descriptor + if (fstat(fd, &st) < 0) { + perror("fstat failed"); + close(fd); + unlink(TEST_FILE); + return 1; + } + + // 4. Output fstat results + printf("\n--- fstat results ---\n"); + printf("File Descriptor: %d\n", fd); + printf("Size: %ld bytes\n", st.st_size); + printf("Inode: %ld\n", st.st_ino); + printf("Mode: %o\n", st.st_mode); + printf("Nlink: %ld\n", st.st_nlink); + printf("UID: %d\n", st.st_uid); + printf("GID: %d\n", st.st_gid); + + // Verify size + if (st.st_size == strlen(TEST_CONTENT)) { + printf("\nSUCCESS: File size matches written content length.\n"); + } else { + printf("\nFAILURE: File size mismatch. Expected %ld, got %ld.\n", strlen(TEST_CONTENT), st.st_size); + } + + // Verify mode (basic check for regular file) + if (S_ISREG(st.st_mode)) { + printf("SUCCESS: File is a regular file.\n"); + } else { + printf("FAILURE: File is not reported as a regular file.\n"); + } + + // 5. Cleanup + close(fd); + unlink(TEST_FILE); + printf("Cleaned up %s\n", TEST_FILE); + + return 0; +} diff --git a/usertests/basic-glibc/getdents.c b/usertests/basic-glibc/getdents.c new file mode 100644 index 0000000..540c3fe --- /dev/null +++ b/usertests/basic-glibc/getdents.c @@ -0,0 +1,62 @@ +#define _GNU_SOURCE +#include /* Defines DT_* constants */ +#include +#include +#include +#include +#include +#include +#include + +struct linux_dirent64 { + unsigned long d_ino; + unsigned long d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +#define BUF_SIZE 1024 + +int +main(int argc, char *argv[]) +{ + int fd; + char d_type; + char buf[BUF_SIZE]; + ssize_t nread; + struct linux_dirent64 *d; + + fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY); + if (fd == -1) + err(EXIT_FAILURE, "open"); + + for (;;) { + nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE); + if (nread == -1) + err(EXIT_FAILURE, "getdents"); + + if (nread == 0) + break; + + printf("--------------- nread=%ld ---------------\n", nread); + printf("inode# file type d_reclen d_off d_name\n"); + for (size_t bpos = 0; bpos < (size_t)nread;) { + d = (struct linux_dirent64 *) (buf + bpos); + printf("%8lu ", d->d_ino); + d_type = d->d_type; + printf("%-10s ", (d_type == DT_REG) ? "regular" : + (d_type == DT_DIR) ? "directory" : + (d_type == DT_FIFO) ? "FIFO" : + (d_type == DT_SOCK) ? "socket" : + (d_type == DT_LNK) ? "symlink" : + (d_type == DT_BLK) ? "block dev" : + (d_type == DT_CHR) ? "char dev" : "???"); + printf("%4d %10jd %s\n", d->d_reclen, + (intmax_t) d->d_off, d->d_name); + bpos += d->d_reclen; + } + } + + exit(EXIT_SUCCESS); +} diff --git a/usertests/basic-glibc/kill.c b/usertests/basic-glibc/kill.c new file mode 100644 index 0000000..a3c45c7 --- /dev/null +++ b/usertests/basic-glibc/kill.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +int main() { + pid_t pid; + pid = fork(); + + if (pid < 0) { + perror("fork failed"); + exit(1); + } + + if (pid == 0) { + printf("Child process (PID: %d) running dead loop...\n", getpid()); + fflush(stdout); + while (1); + } + + // Parent process + sleep(1); // Ensure child is running + printf("Parent process (PID: %d) sending SIGKILL to child (PID: %d)...\n", getpid(), pid); + if (kill(pid, SIGKILL) == -1) { + perror("kill failed"); + exit(1); + } + wait(NULL); // Wait for child to terminate + + pid = fork(); + if (pid < 0) { + perror("fork failed"); + exit(1); + } + + if (pid == 0) { + printf("Child process (PID: %d) sleeping for 5 seconds...\n", getpid()); + fflush(stdout); + sleep(5); + printf("SHOULD NOT REACH HERE!\n"); + return -1; + } + + // Parent process + sleep(1); // Ensure child is sleeping + printf("Parent process (PID: %d) sending SIGKILL to child (PID: %d)...\n", getpid(), pid); + if (kill(pid, SIGKILL) == -1) { + perror("kill failed"); + exit(1); + } + wait(NULL); // Wait for child to terminate + return 0; +} diff --git a/usertests/basic-glibc/loopsleep.c b/usertests/basic-glibc/loopsleep.c new file mode 100644 index 0000000..8fe6429 --- /dev/null +++ b/usertests/basic-glibc/loopsleep.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include + +unsigned long long get_us() { + struct timeval tv; + syscall(SYS_gettimeofday, &tv, NULL); + // gettimeofday(&tv, NULL); + return (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec; +} + +void loop_sleep(unsigned long long us) { + unsigned long long end = get_us() + us; + unsigned long long now; + while ((now = get_us()) < end) { + // printf("now = %llu, end = %llu\n", now, end); + } +} + +int main() { + printf("Test loopsleep\n"); + + unsigned int i = 0; + while (1) { + loop_sleep(1000000); // sleep for 1 second + i ++; + printf("%u second passed\n", i); + fflush(stdout); + } + + return 0; +} diff --git a/usertests/basic-glibc/mkdir.c b/usertests/basic-glibc/mkdir.c new file mode 100644 index 0000000..bc28dca --- /dev/null +++ b/usertests/basic-glibc/mkdir.c @@ -0,0 +1,29 @@ +#include +#include + +int main() { + const char *dirname = "testdir"; + int result = mkdir(dirname, 0755); + if (result == 0) { + printf("Directory '%s' created successfully.\n", dirname); + FILE *f = fopen("testdir", "r"); + if (f) { + fclose(f); + printf("Directory '%s' opened successfully.\n", dirname); + + FILE *f = fopen("testdir/file.txt", "w"); + if (f) { + fprintf(f, "Hello, World!\n"); + fclose(f); + printf("File 'testdir/file.txt' created and written successfully.\n"); + } else { + perror("Failed to create file in directory"); + } + } else { + perror("Failed to open the directory"); + } + } else { + perror("mkdir failed"); + } + return 0; +} diff --git a/usertests/basic-glibc/mmap.c b/usertests/basic-glibc/mmap.c new file mode 100644 index 0000000..0420e98 --- /dev/null +++ b/usertests/basic-glibc/mmap.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include + +pid_t myfork() { + fflush(stdout); + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + exit(1); + } + return pid; +} + +int main() { + /* ----- BASIC ------ */ + char *area1 = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (area1 == MAP_FAILED) { + perror("mmap failed"); + return 1; + } + + *(area1 + 20) = 'A'; + printf("area1[20]: %c\n", *(area1 + 20)); + /* ----- BASIC ------ */ + + /* ----- FORK ----- */ + pid_t pid; + int r, wstatus; + + if ((pid = myfork()) == 0) { // Child process + printf("In child process\n"); + printf("area1[20] in child before change: %c\n", *(area1 + 20)); + *(area1 + 20) = 'B'; + printf("area1[20] in child after change: %c\n", *(area1 + 20)); + return 0; + } + + waitpid(pid, NULL, 0); // Wait for child to finish + printf("In parent process\n"); + printf("area1[20] in parent: %c\n", *(area1 + 20)); + /* ----- FORK ----- */ + + /* ----- MUNMAP ----- */ + if ((pid = myfork()) == 0) { // Child process + r = munmap(area1, 4096); + // if (r < 0) { + // perror("munmap failed"); + // return 1; + // } + // *(area1 + 20) = 'C'; // This should trigger a segmentation fault + // printf("area1[20] in child after unmap and change: %c\n", area1[20]); + return 0; + } + waitpid(pid, &wstatus, 0); // Wait for child to finish + printf("Parent: area1[20] after wait: %c\n", *(area1 + 20)); + /* ----- MUNMAP ----- */ + + /* ----- MPROTECT ----- */ + printf("Parent: area1[20] before fork: %c\n", *(area1 + 20)); + if ((pid = myfork()) == 0) { + printf("Children: area1[20] before mprotect: %c\n", area1[20]); + + r = mprotect(area1, 4096, PROT_READ); + if (r < 0) { + perror("mprotect failed"); + return 1; + } + + *(area1 + 20) = 'D'; // This should trigger a segmentation fault + printf("Children: area1[20] after mprotect and change: %c\n", area1[20]); + return 0; + } + + waitpid(pid, &wstatus, 0); // Wait for child to finish + printf("Child exited with status: %d, area1[20]=%c\n", WEXITSTATUS(wstatus), area1[20]); + /* ----- MPROTECT ----- */ + + return 0; +} diff --git a/usertests/basic-glibc/mmap_file_shared.c b/usertests/basic-glibc/mmap_file_shared.c new file mode 100644 index 0000000..8b3c548 --- /dev/null +++ b/usertests/basic-glibc/mmap_file_shared.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define FILE_NAME "test_mmap_shared.txt" +#define FILE_SIZE 4096 + +int main() { + int fd; + char *mapped_area; + pid_t pid; + + // 1. Create a file and write some initial data + fd = open(FILE_NAME, O_RDWR | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + perror("open failed"); + return 1; + } + + // Extend file to FILE_SIZE + if (ftruncate(fd, FILE_SIZE) == -1) { + perror("ftruncate failed"); + close(fd); + return 1; + } + + // Write initial string at the beginning + const char *initial_msg = "Hello, World!"; + if (write(fd, initial_msg, strlen(initial_msg)) != (ssize_t)strlen(initial_msg)) { + perror("write failed"); + close(fd); + return 1; + } + fsync(fd); + + // 2. mmap the file with MAP_SHARED + mapped_area = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mapped_area == MAP_FAILED) { + perror("mmap failed"); + close(fd); + return 1; + } + + printf("Parent: Initial content: %s\n", mapped_area); + fflush(stdout); + + // 3. Fork a child process + pid = fork(); + + if (pid < 0) { + perror("fork failed"); + munmap(mapped_area, FILE_SIZE); + close(fd); + return 1; + } else if (pid == 0) { + // Child process + printf("Child: Reading content: %s\n", mapped_area); + fflush(stdout); + + // Modify the shared memory + const char *child_msg = "Child was here!"; + sprintf(mapped_area, "%s", child_msg); + printf("Child: Modified content to: %s\n", mapped_area); + fflush(stdout); + + // Sync changes to file (optional but good practice for shared mappings) + if (msync(mapped_area, FILE_SIZE, MS_SYNC) == -1) { + perror("msync failed"); + exit(1); + } + + munmap(mapped_area, FILE_SIZE); + close(fd); + exit(0); + } else { + // Parent process + int status; + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + printf("Parent: Child exited successfully.\n"); + } else { + printf("Parent: Child failed.\n"); + } + fflush(stdout); + + // Verify changes in memory + printf("Parent: Content after child modification: %s\n", mapped_area); + fflush(stdout); + + if (strcmp(mapped_area, "Child was here!") == 0) { + printf("Parent: SUCCESS! Memory reflects changes.\n"); + } else { + printf("Parent: FAILURE! Memory does not reflect changes.\n"); + } + fflush(stdout); + + munmap(mapped_area, FILE_SIZE); + close(fd); + + // Verify changes in file + char buffer[100]; + fd = open(FILE_NAME, O_RDONLY); + if (fd < 0) { + perror("open for verification failed"); + return 1; + } + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if (bytes_read >= 0) { + buffer[bytes_read] = '\0'; + printf("Parent: File content verification: %s\n", buffer); + if (strncmp(buffer, "Child was here!", 15) == 0) { + printf("Parent: SUCCESS! File reflects changes.\n"); + } else { + printf("Parent: FAILURE! File does not reflect changes.\n"); + } + } else { + perror("read failed"); + } + fflush(stdout); + + close(fd); + unlink(FILE_NAME); + } + + return 0; +} diff --git a/usertests/basic-glibc/open.c b/usertests/basic-glibc/open.c new file mode 100644 index 0000000..142a81b --- /dev/null +++ b/usertests/basic-glibc/open.c @@ -0,0 +1,28 @@ +#include + +int main() { + FILE *file = fopen("testopen.txt", "w"); + if (file == NULL) { + perror("Failed to open file"); + return 1; + } + + fprintf(file, "Hello, World!\n"); + fclose(file); + + // file = fopen("testfile.txt", "r"); + // if (file == NULL) { + // perror("Failed to open file"); + // return 1; + // } + + // char buffer[256]; + // if (fgets(buffer, sizeof(buffer), file) != NULL) { + // printf("Read from file: %s", buffer); + // } else { + // perror("Failed to read from file"); + // } + // fclose(file); + + return 0; +} diff --git a/usertests/basic-glibc/pipe.c b/usertests/basic-glibc/pipe.c new file mode 100644 index 0000000..f77f88f --- /dev/null +++ b/usertests/basic-glibc/pipe.c @@ -0,0 +1,32 @@ +#include +#include +#include + +int main() { + int pipefd[2]; + char buffer[20]; + if (pipe(pipefd) == -1) { + perror("pipe"); + return 1; + } + + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + return 1; + } + + if (pid == 0) { // Child process + close(pipefd[0]); // Close unused read end + const char *msg = "Hello, Pipe!"; + write(pipefd[1], msg, 12); + close(pipefd[1]); // Close write end after writing + } else { // Parent process + close(pipefd[1]); // Close unused write end + read(pipefd[0], buffer, sizeof(buffer)); + printf("Received message: %s\n", buffer); + close(pipefd[0]); // Close read end after reading + } + + return 0; +} diff --git a/usertests/basic-glibc/ppoll_time32.c b/usertests/basic-glibc/ppoll_time32.c new file mode 100644 index 0000000..232c310 --- /dev/null +++ b/usertests/basic-glibc/ppoll_time32.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include + +#define __NR_ppoll_time32 73 + +struct timespec32 { + int tv_sec; // seconds + int tv_nsec; // nanoseconds +}; + +int ppoll_time32(struct pollfd *fds, nfds_t nfds, const struct timespec32 *tmo_p) { + return syscall(__NR_ppoll, fds, nfds, tmo_p, NULL, 0); +} + +void sleep_seconds(int seconds) { + struct timespec req = {seconds, 0}; + nanosleep(&req, NULL); +} + +int main() { + int pipefd[2]; + if (pipe(pipefd) == -1) { + perror("pipe"); + return 1; + } + + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + return 1; + } + + if (pid == 0) { + // Read pipe + close(pipefd[1]); // Close write end + + sleep_seconds(1); + printf("after sleep\n"); + fflush(stdout); + + dup2(pipefd[0], 100); // Redirect stdin to read end of pipe + + struct pollfd pfd; + pfd.fd = 100; + pfd.events = POLLIN; + + // Should return immediately + if (ppoll_time32(&pfd, 1, NULL) == -1) { + perror("ppoll_time32"); + } + + if (pfd.fd != 100 || !(pfd.revents & POLLIN)) { + fprintf(stderr, "Unexpected poll result: fd=%d, revents=%d\n", pfd.fd, pfd.revents); + return 1; + } + + char buffer[100]; + ssize_t n = read(pipefd[0], buffer, sizeof(buffer)); + if (n == -1) { + perror("read"); + return 1; + } else { + buffer[n] = '\0'; + printf("Child read: %s\n", buffer); + } + + close(pipefd[0]); + } else { + // Write pipe + close(pipefd[0]); // Close read end + + const char *msg = "Hello from parent!"; + if (write(pipefd[1], msg, 18) == -1) { + perror("write"); + return 1; + } + + waitpid(pid, NULL, 0); + + close(pipefd[1]); + } + + return 0; +} diff --git a/usertests/basic-glibc/printf.c b/usertests/basic-glibc/printf.c new file mode 100644 index 0000000..4dab592 --- /dev/null +++ b/usertests/basic-glibc/printf.c @@ -0,0 +1,8 @@ +#include + +int main() { + const char *s = "Hello World!"; + printf("%s %d \n", s, 42); + + return 0; +} diff --git a/usertests/basic-glibc/readdir.c b/usertests/basic-glibc/readdir.c new file mode 100644 index 0000000..b8dd466 --- /dev/null +++ b/usertests/basic-glibc/readdir.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +void list_directory_contents(const char *dir_path) { + DIR *dirp; + struct dirent *entry; + + // 1. 打开目录流 + dirp = opendir(dir_path); + if (dirp == NULL) { + perror("opendir error"); // 如果打开失败,打印错误信息 + return; + } + + while ((entry = readdir(dirp)) != NULL) { + printf("%8lu ", entry->d_ino); + + char d_type = entry->d_type; + printf("%-10s ", (d_type == DT_REG) ? "regular" : + (d_type == DT_DIR) ? "directory" : + (d_type == DT_FIFO) ? "FIFO" : + (d_type == DT_SOCK) ? "socket" : + (d_type == DT_LNK) ? "symlink" : + (d_type == DT_BLK) ? "block dev" : + (d_type == DT_CHR) ? "char dev" : "???"); + printf("%4d %10jd %s\n", entry->d_reclen, entry->d_off, entry->d_name); + } + + if (closedir(dirp) == -1) { + perror("closedir error"); + } +} + +int main(int argc, char *argv[]) { + char *dir_path; + if (argc != 2) { + dir_path = "."; + } else { + dir_path = argv[1]; + } + + list_directory_contents(dir_path); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/usertests/basic-glibc/rename.c b/usertests/basic-glibc/rename.c new file mode 100644 index 0000000..79aac9f --- /dev/null +++ b/usertests/basic-glibc/rename.c @@ -0,0 +1,42 @@ +#include + +static const char *BEFORE = "before_rename.txt"; +static const char *AFTER = "after_rename.txt"; + +int main() { + FILE *before = fopen(BEFORE, "w"); + if (before == NULL) { + perror("fopen before"); + return 1; + } + + if (fprintf(before, "This file will be renamed.\n") < 0) { + perror("fprintf"); + fclose(before); + return 1; + } + + fclose(before); + + rename(BEFORE, AFTER); + + FILE *after = fopen(AFTER, "r"); + if (after == NULL) { + perror("fopen after"); + return 1; + } + + char buffer[256]; + int r; + if ((r = fread(buffer, 1, sizeof(buffer)-1, after)) < 0) { + perror("fread"); + fclose(after); + return 1; + } + buffer[r] = 0; + printf("Read from renamed file: %s", buffer); + + fclose(after); + + return 0; +} diff --git a/usertests/basic-glibc/sigaction.c b/usertests/basic-glibc/sigaction.c new file mode 100644 index 0000000..7bee4db --- /dev/null +++ b/usertests/basic-glibc/sigaction.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +pid_t Fork() { + fflush(stdout); + pid_t pid; + if ((pid = fork()) < 0) { + perror("fork error"); + exit(1); + } + return pid; +} + +int Kill(pid_t pid, int sig) { + if (kill(pid, sig) < 0) { + perror("kill error"); + exit(1); + } + return 0; +} + +void sigaction_quit() { + printf("SIGACTION QUIT received!\n"); + exit(0); +} + +int flag = 1; + +void sigaction_usr1() { + printf("SIGACTION USR1 received!\n"); + fflush(stdout); + flag = 0; +} + +int main() { + pid_t pid; + + // pid = Fork(); + // if (pid == 0) { + // struct sigaction act; + // act.sa_handler = sigaction_quit; + // sigemptyset(&act.sa_mask); + // act.sa_flags = 0; + // if (sigaction(SIGQUIT, &act, NULL) < 0) { + // perror("sigaction error"); + // exit(1); + // } + // while (1) { + // printf("Child process waiting for SIGQUIT...\n"); + // sleep(5); + // } + // } + + // sleep(1); + // printf("Parent process sending SIGQUIT to child process...\n"); + // Kill(pid, SIGQUIT); + // wait(NULL); + + pid = Fork(); + + if (pid == 0) { + struct sigaction act; + act.sa_handler = sigaction_usr1; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGUSR1); + act.sa_flags = 0; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction error"); + exit(1); + } + while (flag) ; + printf("Child process exiting after receiving SIGUSR1...\n"); + return 0; + } + + sleep(1); + Kill(pid, SIGUSR1); + wait(NULL); + + return 0; +} diff --git a/usertests/basic-glibc/sigchld.c b/usertests/basic-glibc/sigchld.c new file mode 100644 index 0000000..3dd8b10 --- /dev/null +++ b/usertests/basic-glibc/sigchld.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include + +void sigchld_handler(int signum) { + printf("[SIGCHLD Handler] Signal %d received. Child process changed state.\n", signum); + // fflush(stdout); +} + +int main() { + struct sigaction sa; + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + return 0; + } + + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return 0; + } + + if (pid == 0) { + struct timespec req = {1, 0}; // 1 second, 0 nanoseconds + int r = nanosleep(&req, NULL); + printf("[Child] Exited\n"); + exit(42); + } else { + printf("[Parent] Wait for PID=%d\n", pid); + + int status; + pid_t result = wait(&status); + + if (result == -1) { + perror("wait"); + } else { + printf("[Parent] wait() success, child PID=%d\n", result); + } + } + + return 0; +} \ No newline at end of file diff --git a/usertests/basic-glibc/sleep.c b/usertests/basic-glibc/sleep.c new file mode 100644 index 0000000..1caba3d --- /dev/null +++ b/usertests/basic-glibc/sleep.c @@ -0,0 +1,19 @@ + +#include +#include + +int main() { + printf("Sleeping for 2 seconds...\n"); + fflush(stdout); + + struct timespec req = {2, 0}; // 2 seconds, 0 nanoseconds + int r = nanosleep(&req, NULL); + if (r != 0) { + perror("sleep"); + return 1; + } + + printf("Awake!\n"); + + return 0; +} diff --git a/usertests/basic-glibc/unlink.c b/usertests/basic-glibc/unlink.c new file mode 100644 index 0000000..df16088 --- /dev/null +++ b/usertests/basic-glibc/unlink.c @@ -0,0 +1,32 @@ +#include +#include + +const char *TO_UNLINK = "to_unlink.txt"; + +int main() { + FILE *fp = fopen(TO_UNLINK, "r"); + if (fp == NULL) { + perror("fopen"); + return 1; + } + + char buffer[256]; + int r; + if ((r = fread(buffer, 1, sizeof(buffer), fp)) < 0) { + perror("fread"); + fclose(fp); + return 1; + } + buffer[r] = 0; + + printf("Read from file: %s", buffer); + + fclose(fp); + + if (unlink(TO_UNLINK) == -1) { + perror("unlink"); + return 1; + } + + return 0; +} diff --git a/usertests/basic-glibc/vfork.c b/usertests/basic-glibc/vfork.c new file mode 100644 index 0000000..0e0f993 --- /dev/null +++ b/usertests/basic-glibc/vfork.c @@ -0,0 +1,64 @@ +#include +#include +#include + +// vfork shares the address space, so this variable will be shared. +volatile int shared_var = 100; + +int main() { + printf("Parent [PID: %d]: initial shared_var = %d\n", getpid(), shared_var); + fflush(stdout); + + pid_t pid = vfork(); + + if (pid < 0) { + // Error + perror("vfork failed"); + return 1; + } else if (pid == 0) { + // Child process + printf("Child [PID: %d]: executing.\n", getpid()); + fflush(stdout); + printf("Child [PID: %d]: original shared_var = %d\n", getpid(), shared_var); + fflush(stdout); + + // Modify the shared variable. The parent will see this change. + shared_var = 200; + printf("Child [PID: %d]: modified shared_var to %d\n", getpid(), shared_var); + fflush(stdout); + + printf("Child [PID: %d]: exiting.\n", getpid()); + fflush(stdout); + // Use _exit() with vfork to prevent flushing parent's stdio buffers + // and other process-level cleanup that would affect the parent. + _exit(0); + } else { + // Parent process + // The parent is suspended until the child calls execve() or _exit(). + printf("Parent [PID: %d]: resumed.\n", getpid()); + fflush(stdout); + printf("Parent [PID: %d]: shared_var is now %d\n", getpid(), shared_var); + fflush(stdout); + + if (shared_var == 200) { + printf("Parent [PID: %d]: Success! The variable was modified by the child.\n", getpid()); + fflush(stdout); + } else { + printf("Parent [PID: %d]: Failure! The variable was not modified by the child.\n", getpid()); + fflush(stdout); + } + + // Wait for the child to ensure it's reaped. + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + printf("Parent [PID: %d]: Child terminated successfully.\n", getpid()); + fflush(stdout); + } else { + printf("Parent [PID: %d]: Child terminated with an error.\n", getpid()); + fflush(stdout); + } + } + + return 0; +} diff --git a/usertests/basic-glibc/waitsleep.c b/usertests/basic-glibc/waitsleep.c new file mode 100644 index 0000000..ef3d526 --- /dev/null +++ b/usertests/basic-glibc/waitsleep.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +int main() { + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + return 1; + } + + if (pid == 0) { + // Child process + printf("Child sleeping for 1 second...\n"); + fflush(stdout); + + struct timespec req = {1, 0}; // 1 second, 0 nanoseconds + int r = nanosleep(&req, NULL); + if (r != 0) { + perror("sleep"); + return 1; + } + + printf("Child awake!\n"); + } else { + // Parent process + waitpid(pid, NULL, 0); // Wait for child to finish + printf("Parent: Child has finished execution.\n"); + } + + return 0; +} diff --git a/usertests/basic-glibc/write.c b/usertests/basic-glibc/write.c new file mode 100644 index 0000000..aae8233 --- /dev/null +++ b/usertests/basic-glibc/write.c @@ -0,0 +1,19 @@ +#include + +int main() { + FILE *file = fopen("testwrite.txt", "w"); + if (file == NULL) { + perror("Failed to open file"); + return 1; + } + + int r = fprintf(file, "Hello, World!\n"); + printf("fprintf returned: %d\n", r); + if (r < 0) { + perror("Failed to write to file"); + } + + fclose(file); + + return 0; +} diff --git a/usertests/basic-musl/Makefile b/usertests/basic-musl/Makefile new file mode 100644 index 0000000..03239f7 --- /dev/null +++ b/usertests/basic-musl/Makefile @@ -0,0 +1,34 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-musl- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) + +show-tests: + @echo "Auto-discovered tests:" + @for test in $(TESTS); do echo " - $$test"; done + @for test in $(TESTS); do echo " - $$test"; done diff --git a/usertests/basic-musl/brk.c b/usertests/basic-musl/brk.c new file mode 100644 index 0000000..0c0a392 --- /dev/null +++ b/usertests/basic-musl/brk.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +void *my_brk(void *addr) { + return (void *)syscall(SYS_brk, addr); +} + +int main() { + int *top = my_brk(NULL); + printf("Before brk: %p\n", top); + + int *new_top = my_brk(top + 1024); + printf("After brk: %p\n", new_top); + + *top = 0x12; + *(top + 1) = 0x34; + + pid_t pid; + if ((pid = fork()) == 0) { + printf("[Children]*%p = %x\n", top, *top); + *(top + 1) = 0x56; + printf("[Children]*%p = %x\n", top + 1, *(top + 1)); + return 0; + } else { + printf("[Parent]*%p = %x\n", top, *top); + wait4(pid, NULL, 0, NULL); + printf("[Parent]*%p = %x\n", top + 1, *(top + 1)); + } + + return 0; +} diff --git a/usertests/basic-musl/dummy.c b/usertests/basic-musl/dummy.c new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/usertests/basic-musl/dummy.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/usertests/basic-musl/forkexec.c b/usertests/basic-musl/forkexec.c new file mode 100644 index 0000000..c54d82f --- /dev/null +++ b/usertests/basic-musl/forkexec.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int main() { + printf("Hello, World!\n"); + int pid = fork(); + if (pid < 0) { + perror("fork failed"); + return 1; + } + + if (pid == 0) { + char *args[] = {"/basic-musl/forkexec-child", "argv[1]", "argv[2]", NULL}; + char *envp[] = {"env1=var1", "env2=var2", NULL}; + execve("/basic-musl/forkexec-child", args, envp); + } else { + waitpid(pid, NULL, 0); + printf("Parent process with PID: %d created child with PID: %d\n", + getpid(), pid); + } + return 0; +} \ No newline at end of file diff --git a/usertests/basic-musl/print.c b/usertests/basic-musl/print.c new file mode 100644 index 0000000..be213c1 --- /dev/null +++ b/usertests/basic-musl/print.c @@ -0,0 +1,7 @@ +#include + +int main() { + printf("Hello, World!\n"); + + return 0; +} diff --git a/usertests/basic-musl/writev.c b/usertests/basic-musl/writev.c new file mode 100644 index 0000000..8cf2f69 --- /dev/null +++ b/usertests/basic-musl/writev.c @@ -0,0 +1,23 @@ +#include +#include +#include + +int main() { + struct iovec iov[2]; + char buf1[] = "Hello, "; + char buf2[] = "World!"; + iov[0].iov_base = buf1; + iov[0].iov_len = sizeof(buf1) - 1; // Exclude null terminator + iov[1].iov_base = buf2; + iov[1].iov_len = sizeof(buf2) - 1; // Exclude null terminator + ssize_t result = writev(1, iov, 2); // Write to standard output + + write(1, buf1, sizeof(buf1) - 1); + write(1, buf2, sizeof(buf2) - 1); + + if (result < 0) { + perror("writev"); + } + + return 0; +} diff --git a/usertests/basic-ulib/Makefile b/usertests/basic-ulib/Makefile new file mode 100644 index 0000000..4e92000 --- /dev/null +++ b/usertests/basic-ulib/Makefile @@ -0,0 +1,35 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +ULIB = ulib/build/$(ARCH)/ulib.a + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +INC_DIR += ulib/include + +CFLAGS += -Wall -Wextra +CFLAGS += -nostdlib -static + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + @ mkdir -p $(OUTPUT_DIR)/$(ARCH) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o $(ULIB) + @ mkdir -p $(OUTPUT_DIR)/$(ARCH) + @ $(CC) $(CFLAGS) $< -o $@ $(ULIB) + +all: ulib-all $(TEST_TARGETS) + +ulib-all: + $(MAKE) -C ulib all + +show-tests: + @echo "Auto-discovered tests:" + @for test in $(TESTS); do echo " - $$test"; done diff --git a/usertests/basic-ulib/args-child.c b/usertests/basic-ulib/args-child.c new file mode 100644 index 0000000..87ae64f --- /dev/null +++ b/usertests/basic-ulib/args-child.c @@ -0,0 +1,15 @@ +#include + +int main(int argc, char **argv, char **envp) { + puts("This args-child."); + + for (int i = 0; i < argc; i++) { + puts(argv[i]); + } + + for (int i = 0; envp[i] != NULL; i++) { + puts(envp[i]); + } + + return 0; +} diff --git a/usertests/basic-ulib/args.c b/usertests/basic-ulib/args.c new file mode 100644 index 0000000..a8b122a --- /dev/null +++ b/usertests/basic-ulib/args.c @@ -0,0 +1,15 @@ +#include +#include + +int main() { + pid_t pid = fork(); + if (pid == 0) { + char *const args[] = {"/basic-ulib/args-child", "args[1]", "args[2]", NULL}; + char *const envp[] = {"ENV_VAR1=value1", "ENV_VAR2=value2", NULL}; + execve(args[0], args, envp); + } + + wait4(pid, NULL, 0, NULL); + + return 0; +} diff --git a/usertests/basic-ulib/brk.c b/usertests/basic-ulib/brk.c new file mode 100644 index 0000000..bcf09db --- /dev/null +++ b/usertests/basic-ulib/brk.c @@ -0,0 +1,13 @@ +#include +#include + +int main() { + char *ptr = sbrk(0); + sbrk(1024); + ptr += 32; + *ptr = 124; + + puts("brk test passed"); + + return 0; +} diff --git a/usertests/basic-ulib/forkexec-child.c b/usertests/basic-ulib/forkexec-child.c new file mode 100644 index 0000000..e345df0 --- /dev/null +++ b/usertests/basic-ulib/forkexec-child.c @@ -0,0 +1,14 @@ +# include +# include + +int main() { + if (fork() == 0) { + // This is the child process + puts("child: Hello from child process!"); + } else { + // This is the parent process + puts("child: Hello from parent process!"); + } + // puts("Hello from child process!"); + return 0; +} \ No newline at end of file diff --git a/usertests/basic-ulib/forkexec.c b/usertests/basic-ulib/forkexec.c new file mode 100644 index 0000000..75d55cb --- /dev/null +++ b/usertests/basic-ulib/forkexec.c @@ -0,0 +1,19 @@ +#include +#include +#include + +int main() { + int pid = fork(); + if (pid == 0) { + execve("/basic-ulib/forkexec-child", NULL, NULL); + puts("Failed to execute child process!"); + return 0; + } else { + puts("Hello from parent process!"); + } + + wait4(0, NULL, 0, NULL); + wait4(0, NULL, 0, NULL); + + return 0; +} \ No newline at end of file diff --git a/usertests/basic-ulib/helloworld.c b/usertests/basic-ulib/helloworld.c new file mode 100644 index 0000000..412c41f --- /dev/null +++ b/usertests/basic-ulib/helloworld.c @@ -0,0 +1,8 @@ +#include + +int main() { + const char *msg = "Hello, World!\n"; + write(1, msg, 14); + + return 0; +} \ No newline at end of file diff --git a/usertests/basic-ulib/read.c b/usertests/basic-ulib/read.c new file mode 100644 index 0000000..920abed --- /dev/null +++ b/usertests/basic-ulib/read.c @@ -0,0 +1,27 @@ +#include + +#include +#include + +int main() { + int fd = open("/basic-ulib/test.txt", O_RDONLY); + if (fd < 0) { + puts("Failed to open file"); + return 1; + } + + char buffer[128]; + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if (bytes_read < 0) { + puts("Failed to read file"); + close(fd); + return 1; + } + + buffer[bytes_read] = '\0'; // Null-terminate the string + puts(buffer); + + close(fd); + + return 0; +} diff --git a/usertests/basic-ulib/ulib/Makefile b/usertests/basic-ulib/ulib/Makefile new file mode 100644 index 0000000..6ec359d --- /dev/null +++ b/usertests/basic-ulib/ulib/Makefile @@ -0,0 +1,41 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +INC_DIR = include +SRC_DIR = src +ARCH_DIR = $(SRC_DIR)/arch/$(ARCH) + +BUILD_DIR = build/$(ARCH) +TARGET = $(BUILD_DIR)/ulib.a + +CC = $(CROSS_COMPILE)gcc +AR = $(CROSS_COMPILE)ar + +CFLAGS += -Wall -Wextra -ffreestanding +CFLAGS += $(addprefix -I, $(INC_DIR)) + +SRCS += $(wildcard $(SRC_DIR)/*.c) +SRCS += $(wildcard $(ARCH_DIR)/*.c) +SRCS += $(wildcard $(ARCH_DIR)/*.S) + +OBJS = $(patsubst $(SRC_DIR)/%, $(BUILD_DIR)/%, $(SRCS:%=%.o)) + +$(TARGET): $(OBJS) + @ mkdir -p $(dir $@) + @ $(AR) rcs $@ $^ + +$(BUILD_DIR)/%.c.o: $(SRC_DIR)/%.c + @ mkdir -p $(dir $@) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/%.S.o: $(SRC_DIR)/%.S + @ mkdir -p $(dir $@) + @ $(CC) $(CFLAGS) -c $< -o $@ + +all: $(TARGET) + +clean: + rm -rf $(BUILD_DIR) + @ echo "Cleaned build directory" + +.PHONY: all clean diff --git a/usertests/basic-ulib/ulib/include/ulib.h b/usertests/basic-ulib/ulib/include/ulib.h new file mode 100644 index 0000000..d363d69 --- /dev/null +++ b/usertests/basic-ulib/ulib/include/ulib.h @@ -0,0 +1,8 @@ +#ifndef __ULIB_H__ +#define __ULIB_H__ + +#include + +uintptr_t __syscall(uintptr_t number, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4); + +#endif \ No newline at end of file diff --git a/usertests/basic-ulib/ulib/src/arch/riscv64/start.S b/usertests/basic-ulib/ulib/src/arch/riscv64/start.S new file mode 100644 index 0000000..7f32fb6 --- /dev/null +++ b/usertests/basic-ulib/ulib/src/arch/riscv64/start.S @@ -0,0 +1,11 @@ + .section .text + .global _start +_start: + ld a0, 0(sp) # Load argc from stack top + addi a1, sp, 8 # argv pointer + + addi t0, a0, 2 # t0 = argc + 2 + slli t0, t0, 3 # t0 = (argc + 2) * 8 + add a2, sp, t0 # envp pointer + + tail __call_main \ No newline at end of file diff --git a/usertests/basic-ulib/ulib/src/arch/riscv64/syscall.S b/usertests/basic-ulib/ulib/src/arch/riscv64/syscall.S new file mode 100644 index 0000000..54fa8b8 --- /dev/null +++ b/usertests/basic-ulib/ulib/src/arch/riscv64/syscall.S @@ -0,0 +1,15 @@ + .global __syscall + .type __syscall, @function + .text + +# uintptr_t __syscall(uintptr_t number, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4); +__syscall: + mv a7, a0 + mv a0, a1 + mv a1, a2 + mv a2, a3 + mv a3, a4 + + ecall + + ret diff --git a/usertests/basic-ulib/ulib/src/stdio.c b/usertests/basic-ulib/ulib/src/stdio.c new file mode 100644 index 0000000..54fd766 --- /dev/null +++ b/usertests/basic-ulib/ulib/src/stdio.c @@ -0,0 +1,12 @@ +#include +#include + +int puts(const char *s) { + size_t len = 0; + while (s[len] != '\0') { + len++; + } + write(1, s, len); + write(1, "\n", 1); + return len + 1; // Return number of characters written including newline +} diff --git a/usertests/basic-ulib/ulib/src/string.c b/usertests/basic-ulib/ulib/src/string.c new file mode 100644 index 0000000..f1f9c36 --- /dev/null +++ b/usertests/basic-ulib/ulib/src/string.c @@ -0,0 +1,9 @@ +#include + +size_t strlen(const char *s) { + const char *p = s; + while (*p) { + p++; + } + return p - s; +} diff --git a/usertests/basic-ulib/ulib/src/ulib.c b/usertests/basic-ulib/ulib/src/ulib.c new file mode 100644 index 0000000..4930af4 --- /dev/null +++ b/usertests/basic-ulib/ulib/src/ulib.c @@ -0,0 +1,91 @@ +#include "ulib.h" + +#include +#include +#include +#include +#include +#include +#include + +void __call_main(int argc, char **argv, char **envp) { + extern int main(int argc, char **argv, char **envp); + + int result = main(argc, argv, envp); + _exit(result); +} + +void _exit(int code) { + __syscall(SYS_exit, code, 0, 0, 0); + while (1); +} + +int openat(int dirfd, const char *path, int flags, ...) { + return __syscall(SYS_openat, dirfd, (uintptr_t)path, flags, 0); +} + +int open(const char *__file, int __oflag, ...) { + return openat(AT_FDCWD, __file, __oflag, 0); +} + +ssize_t read(int fd, void *buf, size_t n) { + return __syscall(SYS_read, fd, (uintptr_t)buf, n, 0); +} + +ssize_t write(int fd, const void *buf, size_t n) { + return __syscall(SYS_write, fd, (uintptr_t)buf, n, 0); +} + +int close(int fd) { + return __syscall(SYS_close, fd, 0, 0, 0); +} + +pid_t fork(void) { + return __syscall(SYS_clone, 0, 0, 0, 0); +} + +int execve(const char *path, char *const *argv, char *const *envp) { + return __syscall(SYS_execve, (uintptr_t)path, (uintptr_t)argv, (uintptr_t)envp, 0); +} + +pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage) { + return __syscall(SYS_wait4, pid, (uintptr_t)status, options, (uintptr_t)rusage); +} + +int sched_yield() { + return __syscall(SYS_sched_yield, 0, 0, 0, 0); +} + +int brk(void *addr) { + uintptr_t requested = (uintptr_t)addr; + uintptr_t result = __syscall(SYS_brk, requested, 0, 0, 0); + + if (result != requested) { + return -1; + } + return 0; +} + +void *sbrk(intptr_t increment) { + static void *current_brk = NULL; + + if (current_brk == NULL) { + uintptr_t current = __syscall(SYS_brk, 0, 0, 0, 0); + current_brk = (void *)current; + } + + if (increment == 0) { + return current_brk; + } + + void *old_brk = current_brk; + uintptr_t new_addr = (uintptr_t)current_brk + increment; + + if (brk((void *)new_addr) < 0) { + return (void *)-1; + } + + current_brk = (void *)new_addr; + return old_brk; +} + diff --git a/usertests/basic-ulib/write.c b/usertests/basic-ulib/write.c new file mode 100644 index 0000000..410f570 --- /dev/null +++ b/usertests/basic-ulib/write.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int main() { + int fd = open("/basic-ulib/test-write.txt", 0, 0644); + if (fd < 0) { + puts("Failed to open file for writing"); + return 1; + } + + const char *msg = "Hello, World!"; + ssize_t bytes_written = write(fd, msg, strlen(msg)); + if (bytes_written < 0) { + puts("Failed to write to file"); + close(fd); + return 1; + } + + close(fd); + + fd = open("/basic-ulib/test-write.txt", 0); + if (fd < 0) { + puts("Failed to reopen file for reading"); + return 1; + } + + char buffer[128]; + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if (bytes_read < 0) { + puts("Failed to read from file"); + close(fd); + return 1; + } + + buffer[bytes_read] = '\0'; // Null-terminate the string + puts(buffer); + + close(fd); + + return 0; +} diff --git a/usertests/build.sh b/usertests/build.sh new file mode 100644 index 0000000..0f3d2eb --- /dev/null +++ b/usertests/build.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +set -e + +ISA=${1:-riscv64} +IMG_SIZE=${IMG_SIZE:-1024} +BUILD_DIR=${BUILD_DIR:-build} + +TESTS=( + # "basic-ulib" + # "basic-musl" + "basic-glibc" + # "filesystem" + # "basic-glibc-static" + "os-func" + # pthread-musl +) + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_test() { + echo -e "${BLUE}[TEST]${NC} $1" +} + +log_info "Build configuration:" +echo " ISA: $ISA" +echo " Image size: ${IMG_SIZE}MB" +echo " Build directory: $BUILD_DIR" +echo " Tests: ${TESTS[*]}" + +log_info "Creating directory structure..." +rm -rf ${BUILD_DIR}/${ISA} +mkdir -p ${BUILD_DIR}/${ISA} +mkdir -p img/${ISA} + +for test in "${TESTS[@]}"; do + TEST_DIR="$test" + OUTPUT_DIR="$test/build/${ISA}/output" + TARGET_DIR="${BUILD_DIR}/${ISA}/$test" + + mkdir -p "$TARGET_DIR" + + if [ ! -d "$TEST_DIR" ]; then + log_warn "Test directory $TEST_DIR does not exist, skipping..." + continue + fi + + log_info "Building $test for $ISA..." + cd "$TEST_DIR" + + if [ -f "Makefile" ] || [ -f "makefile" ]; then + log_info "Running 'make all' in $test directory..." + if make all ISA=$ISA; then + log_info "$test build completed successfully" + else + log_error "Failed to build $test" + cd .. + continue + fi + else + log_warn "No Makefile found in $test, skipping build..." + fi + + cd .. + + if [ ! -d "$OUTPUT_DIR" ]; then + log_warn "Output directory $OUTPUT_DIR does not exist for $test" + continue + fi + + log_info "Collecting files from $OUTPUT_DIR..." + if [ "$(ls -A $OUTPUT_DIR 2>/dev/null)" ]; then + cp -r $OUTPUT_DIR/* "$TARGET_DIR/" + log_info "Files copied successfully for $test ($(ls -1 $OUTPUT_DIR | wc -l) files)" + else + log_warn "No files found in $OUTPUT_DIR for $test" + fi +done + +MUSL_LIBC="/opt/riscv64-linux-musl-cross/riscv64-linux-musl/lib/libc.so" +MUSL_LIBC_DEST="${BUILD_DIR}/${ISA}/lib/ld-musl-riscv64.so.1" + +if [ -f "$MUSL_LIBC" ]; then + log_info "Copying musl libc to build directory..." + mkdir -p "${BUILD_DIR}/${ISA}/lib" + cp "$MUSL_LIBC" "$MUSL_LIBC_DEST" +else + log_error "Musl libc not found at $MUSL_LIBC, skipping copy" +fi + +# GLIBC_LIBC_LD="/home/rache/code/glibc/build/riscv64-linux-gnu/elf/ld.so" +# GLIBC_LIBC_SO="/home/rache/code/glibc/build/riscv64-linux-gnu/libc.so" +GLIBC_LIBC_LD="/usr/riscv64-linux-gnu/lib/ld-linux-riscv64-lp64d.so.1" +GLIBC_LIBC_SO="/usr/riscv64-linux-gnu/lib/libc.so.6" +GLIBC_LIBC_LD_DEST="${BUILD_DIR}/${ISA}/lib/ld-linux-riscv64-lp64d.so.1" +GLIBC_LIBC_SO_DEST="${BUILD_DIR}/${ISA}/lib/libc.so.6" + +mkdir -p "${BUILD_DIR}/${ISA}/lib" + +if [ -f "$GLIBC_LIBC_LD" ]; then + log_info "Copying glibc dynamic linker to build directory..." + cp "$GLIBC_LIBC_LD" "$GLIBC_LIBC_LD_DEST" +else + log_error "Glibc dynamic linker not found at $GLIBC_LIBC_LD, skipping copy" +fi + +if [ -f "$GLIBC_LIBC_SO" ]; then + log_info "Copying glibc libc.so.6 to build directory..." + cp "$GLIBC_LIBC_SO" "$GLIBC_LIBC_SO_DEST" +else + log_error "Glibc libc.so.6 not found at $GLIBC_LIBC_SO, skipping copy" +fi + +IMG_FILE="${BUILD_DIR}/${ISA}.ext4" +log_info "Creating ext4 image: $IMG_FILE" + +[ -f "$IMG_FILE" ] && rm -f "$IMG_FILE" + +dd if=/dev/zero of=$IMG_FILE bs=1M count=$IMG_SIZE 2>/dev/null +/sbin/mkfs.ext4 -b 4096 -F $IMG_FILE >/dev/null 2>&1 + +if sudo mount -o loop $IMG_FILE img/${ISA}; then + sudo cp -r ${BUILD_DIR}/${ISA}/* img/${ISA}/ 2>/dev/null || true + sudo chmod -R 755 img/${ISA}/ + sync + sudo umount img/${ISA} +else + log_error "Failed to mount image" + exit 1 +fi + +log_info "Build completed successfully!" +echo "Generated image: $IMG_FILE" +echo "Image size: $(ls -lh $IMG_FILE | awk '{print $5}')" +echo "" +echo "Contents of ${BUILD_DIR}/${ISA}/:" +for test in "${TESTS[@]}"; do + if [ -d "${BUILD_DIR}/${ISA}/$test" ]; then + echo " $test/:" + ls -lai ${BUILD_DIR}/${ISA}/$test/ | sed 's/^/ /' + fi +done diff --git a/usertests/filesystem/Makefile b/usertests/filesystem/Makefile new file mode 100644 index 0000000..6562557 --- /dev/null +++ b/usertests/filesystem/Makefile @@ -0,0 +1,29 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) diff --git a/usertests/filesystem/tempfile.c b/usertests/filesystem/tempfile.c new file mode 100644 index 0000000..c2ed2c7 --- /dev/null +++ b/usertests/filesystem/tempfile.c @@ -0,0 +1,30 @@ +#include + +const char DATA[] = "Hello, World!"; + +int main() { + FILE *fp = tmpfile(); + if (fp == NULL) { + perror("tmpfile"); + return 1; + } + + size_t written = fwrite(DATA, 1, sizeof(DATA) - 1, fp); + if (written != sizeof(DATA) - 1) { + perror("fwrite"); + return 1; + } + + rewind(fp); + char buffer[1024]; + ssize_t read = fread(buffer, 1, sizeof(buffer), fp); + if (read < 0) { + perror("fread"); + return 1; + } + + buffer[read] = '\0'; + printf("Read from tempfile: %s\n", buffer); + + return 0; +} diff --git a/usertests/filesystem/tmpfs.c b/usertests/filesystem/tmpfs.c new file mode 100644 index 0000000..1a77e75 --- /dev/null +++ b/usertests/filesystem/tmpfs.c @@ -0,0 +1,29 @@ +#include + +const char data[] = "Hello, tmpfs!"; + +int main() { + FILE *f; + f = fopen("/tmp/tmpfile", "w"); + if (f == NULL) { + perror("Failed to open file"); + return 1; + } + + fwrite(data, sizeof(char), sizeof(data) - 1, f); + + fclose(f); + + f = fopen("/tmp/tmpfile", "r"); + if (f == NULL) { + perror("Failed to open file for reading"); + return 1; + } + + char buffer[50]; + size_t n = fread(buffer, sizeof(char), sizeof(buffer) - 1, f); + buffer[n] = '\0'; + printf("Read from tmpfs: %s\n", buffer); + + return 0; +} diff --git a/usertests/filesystem/write.c b/usertests/filesystem/write.c new file mode 100644 index 0000000..8adb727 --- /dev/null +++ b/usertests/filesystem/write.c @@ -0,0 +1,24 @@ +#include +#include +#include + +char data[0x400 * 4]; + +int main() { + int fd = open("/filesystem/a.txt", O_WRONLY); + if (fd < 0) { + perror("open"); + return 1; + } + + for (unsigned int i = 0; i < sizeof(data); i++) { + data[i] = 'A' + (i % 26); + } + + write(fd, data, sizeof(data)); + write(fd, data, 1); + + close(fd); + + return 0; +} \ No newline at end of file diff --git a/usertests/helloworld/.gitignore b/usertests/helloworld/.gitignore new file mode 100644 index 0000000..a8a0dce --- /dev/null +++ b/usertests/helloworld/.gitignore @@ -0,0 +1 @@ +*.bin diff --git a/usertests/helloworld/Makefile b/usertests/helloworld/Makefile new file mode 100644 index 0000000..fd09b74 --- /dev/null +++ b/usertests/helloworld/Makefile @@ -0,0 +1,26 @@ +CROSS_COMPILER ?= riscv64-unknown-elf- +ISA ?= riscv64 + +BUILD_DIR ?= build/$(ISA) +OUTPUT_DIR = $(BUILD_DIR)/output + +TARGET = $(BUILD_DIR)/helloworld.elf +OUTPUT_TARGET = $(OUTPUT_DIR)/helloworld.elf + +SRC = helloworld.S + +CC = $(CROSS_COMPILER)gcc +CFLAGS = -Wall -Werror -nostdlib -ffreestanding -static + +$(TARGET): $(SRC) + @mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -o $(TARGET) $(SRC) + +$(OUTPUT_TARGET): $(TARGET) + @mkdir -p $(OUTPUT_DIR) + cp $(TARGET) $(OUTPUT_TARGET) + +all: $(OUTPUT_TARGET) + +clean: + rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/usertests/helloworld/helloworld.S b/usertests/helloworld/helloworld.S new file mode 100644 index 0000000..170e095 --- /dev/null +++ b/usertests/helloworld/helloworld.S @@ -0,0 +1,21 @@ +.section .text +.global _start + +_start: + li a7, 220 + ecall + + li a0, 1 # stdout + la a1, hello + li a2, 24 + li a7, 64 + + ecall + + li a7, 93 + ecall + +.data +hello: + .string "Hello, World! from user\n" + diff --git a/usertests/os-func/Makefile b/usertests/os-func/Makefile new file mode 100644 index 0000000..095a954 --- /dev/null +++ b/usertests/os-func/Makefile @@ -0,0 +1,34 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) + +show-tests: + @echo "Auto-discovered tests:" + @for test in $(TESTS); do echo " - $$test"; done + @for test in $(TESTS); do echo " - $$test"; done diff --git a/usertests/os-func/swap.c b/usertests/os-func/swap.c new file mode 100644 index 0000000..8de778a --- /dev/null +++ b/usertests/os-func/swap.c @@ -0,0 +1,129 @@ +#include +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#define PGSIZE 4096 // 4KB +#define REGION_SIZE (512UL * 1024 * 1024) // 512MB + +static const size_t POSITIONS[] = {0}; +static const size_t NUM_POSITIONS = sizeof(POSITIONS) / sizeof(POSITIONS[0]); + +int verify_page(uint8_t *page_base, size_t page_num) { + for (size_t k = 0; k < NUM_POSITIONS; ++k) { + size_t idx = POSITIONS[k]; + size_t byte_off = idx * sizeof(uint64_t); + uint64_t *ptr = (uint64_t *)(page_base + byte_off); + uint64_t expected = ((uint64_t)page_num << 32) ^ (uint64_t)idx; + uint64_t got = *ptr; + + if (got != expected) { + fprintf(stderr, " MISMATCH at page %zu(%p), position %zu: expected 0x%016lx, got 0x%016lx\n", + page_num, ptr, idx, expected, got); + fflush(stderr); + return 1; // Mismatch + } + } + return 0; // All positions match +} + +int verify_pages(uint8_t *base, size_t num_pages) { + printf("Verifying pages...\n"); + fflush(stdout); + + for (size_t page = 0; page < num_pages; ++page) { + uint8_t *page_base = base + page * PGSIZE; + if (verify_page(page_base, page) != 0) { + return 1; // Mismatch found + } + + if (page % 1024 == 0) { + printf(" Verified %zu / %zu pages...\n", page, num_pages); + fflush(stdout); + } + } + + return 0; // All pages verified +} + +int main(void) { + // mmap 512MB + void *base = mmap(NULL, REGION_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (base == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + return 1; + } + + size_t num_pages = REGION_SIZE / PGSIZE; + printf("Region size: %zu bytes, pages: %zu, PGSIZE=%d\n", + (size_t)REGION_SIZE, num_pages, PGSIZE); + + // offset_in_bytes = idx * sizeof(uint64_t) + // const size_t POSITIONS[] = {0, 128, 256, 384}; + + printf("Initializing pages (write several uint64_t per page)...\n"); + + for (size_t page = 0; page < num_pages; ++page) { + uint8_t *page_base = (uint8_t *)base + page * PGSIZE; + + for (size_t k = 0; k < NUM_POSITIONS; ++k) { + size_t idx = POSITIONS[k]; + size_t byte_off = idx * sizeof(uint64_t); + + uint64_t *ptr = (uint64_t *)(page_base + byte_off); + + uint64_t value = ((uint64_t)page << 32) ^ (uint64_t)idx; + *ptr = value; + } + + if (page % 1024 == 0) { + printf(" Initialized %zu / %zu pages...\n", page, num_pages); + fflush(stdout); + } + } + + int pass = 0; + for (; pass < 1; ++pass) { + verify_pages((uint8_t *)base, num_pages); + printf(" Pass %d OK\n", pass + 1); + fflush(stdout); + } + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (pid == 0) { + if (verify_pages((uint8_t *)base, num_pages) != 0) { + fprintf(stderr, "Child process verification failed\n"); + return 1; + } + + printf(" Child process verification OK\n"); + fflush(stdout); + + return 0; + } + + for (; pass < 2; ++pass) { + verify_pages((uint8_t *)base, num_pages); + printf(" Pass %d OK\n", pass + 1); + fflush(stdout); + } + + wait(NULL); + + munmap(base, REGION_SIZE); + return 0; +} diff --git a/usertests/os-func/tintr.c b/usertests/os-func/tintr.c new file mode 100644 index 0000000..5c44ecd --- /dev/null +++ b/usertests/os-func/tintr.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +unsigned long long get_us() { + struct timeval tv; + syscall(SYS_gettimeofday, &tv, NULL); + return (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec; +} + +int main() { + printf("Test tinter\n"); + fflush(stdout); + + int pid = fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (pid == 0) { + printf("Child Process\n"); + fflush(stdout); + while (1) { + unsigned long long start = get_us(); + for (volatile unsigned int i = 0; i < 75000000; i++) + ; + unsigned long long end = get_us(); + printf("Child Loop takes %llu us\n", end - start); + fflush(stdout); + } + } else { + printf("Parent Process\n"); + fflush(stdout); + while (1) { + unsigned long long start = get_us(); + for (volatile unsigned int i = 0; i < 100000000; i++) + ; + unsigned long long end = get_us(); + printf("Parent Loop takes %llu us\n", end - start); + fflush(stdout); + } + } + + + + return 0; +} diff --git a/usertests/pthread-musl/Makefile b/usertests/pthread-musl/Makefile new file mode 100644 index 0000000..d3d87ea --- /dev/null +++ b/usertests/pthread-musl/Makefile @@ -0,0 +1,46 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= /opt/riscv64-linux-musl-cross/bin/riscv64-linux-musl- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +# MUSL_TMP = /home/rache/code/musl-1.2.5/build-riscv64-debug + +# CFLAGS += -nostdinc +# CFLAGS += -I$(MUSL_TMP)/include + +# LDFLAGS += -nostdlib +# LDFLAGS += $(MUSL_TMP)/lib/crt1.o +# LDFLAGS += $(MUSL_TMP)/lib/crti.o +# LDFLAGS += -L$(MUSL_TMP)/lib +# LDFLAGS += -lc +# LDFLAGS += $(MUSL_TMP)/lib/crtn.o + +CFLAGS += -Wl,-t + +CFLAGS += -Wall -Wextra +# CFLAGS += --sysroot /home/rache/code/musl-1.2.5/build-riscv64-debug +CFLAGS += -static -no-pie +CFLAGS += -O0 -Og -g -v + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(OUTPUT_DIR)/%: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< $(LDFLAGS) -o $@ + +# $(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o +# $(info + LD $<) +# @ mkdir -p $(OUTPUT_DIR) +# @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) diff --git a/usertests/pthread-musl/cancel1.c b/usertests/pthread-musl/cancel1.c new file mode 100644 index 0000000..d316c82 --- /dev/null +++ b/usertests/pthread-musl/cancel1.c @@ -0,0 +1,27 @@ +#include +#include + +static void *start_async(void *arg) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); + sem_post(arg); + for (;;); + return 0; +} + +int main(void) +{ + pthread_t td; + sem_t sem1; + void *res; + + sem_init(&sem1, 0, 0); + + /* Asynchronous cancellation */ + pthread_create(&td, 0, start_async, &sem1); + while (sem_wait(&sem1)); + pthread_cancel(td); + pthread_join(td, &res); + + return 0; +} \ No newline at end of file diff --git a/usertests/pthread/Makefile b/usertests/pthread/Makefile new file mode 100644 index 0000000..ac10002 --- /dev/null +++ b/usertests/pthread/Makefile @@ -0,0 +1,32 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra +CFLAGS += --sysroot /home/rache/code/glibc-2.42/build/riscv64/install +CFLAGS += -static +CFLAGS += -O0 -Og -g + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) diff --git a/usertests/pthread/cancel0.c b/usertests/pthread/cancel0.c new file mode 100644 index 0000000..138bc1d --- /dev/null +++ b/usertests/pthread/cancel0.c @@ -0,0 +1,27 @@ +#include +#include + +static void *start_async(void *arg) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); + sem_post(arg); + for (;;); + return 0; +} + +int main(void) +{ + pthread_t td; + sem_t sem1; + void *res; + + sem_init(&sem1, 0, 0); + + /* Asynchronous cancellation */ + pthread_create(&td, 0, start_async, &sem1); + while (sem_wait(&sem1)); + pthread_cancel(td); + pthread_join(td, &res); + + return 0; +} \ No newline at end of file diff --git a/usertests/pthread/cancel1.c b/usertests/pthread/cancel1.c new file mode 100644 index 0000000..e6687e4 --- /dev/null +++ b/usertests/pthread/cancel1.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +static sem_t sem_seq; // 用来卡住被测线程,确保先投递 cancel 再放行 +static sem_t sem_test; // 辅助线程用:初值为 1,让辅助线程很快退出,从而 join 基本不阻塞 +static pthread_t td_aux; // 被 join 的目标线程 +static volatile int seqno; // 1: 进入被测调用前;2: 被测调用返回后 + +#define TRY0(x) do{ int _r = (x); if(_r){ \ + fprintf(stderr, "%s failed: %s\n", #x, strerror(_r)); exit(2);} }while(0) +#define TRYM1(x) do{ if((x)==-1){ \ + fprintf(stderr, "%s failed: %s\n", #x, strerror(errno)); exit(2);} }while(0) + +static void* aux_run(void* arg) { + // 初值 1:这里不会阻塞,很快退出,制造“non-blocking join” + // 用 while(..); 的写法避免被 EINTR 打断 + while (sem_wait(&sem_test)) {} + return NULL; +} + +static void* tested_run(void* arg) { + // 1) 禁用取消,避免在卡点(sem_seq)被取消 + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + // 2) 卡在这里,等待主线程:先 cancel 再放行 + while (sem_wait(&sem_seq)) {} + + // 3) 启用取消,准备进入被测调用 + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + seqno = 1; // 马上要进入取消点 + + // 4) 被测调用:non-blocking pthread_join(目标线程已基本退出) + // 由于已有挂起的取消请求,进入这里就应当被取消,通常不会返回到下一行 + TRY0(pthread_join(td_aux, NULL)); + + seqno = 2; // 若能走到这里,说明没在入口被取消(测试失败) + return NULL; +} + +int main(void) { + pthread_t td_tested; + void* res = NULL; + + TRY0(sem_init(&sem_seq, 0, 0)); + TRY0(sem_init(&sem_test, 0, 1)); // 关键:设为 1,制造“non-blocking join” + + // 创建“很快结束”的目标线程 + TRY0(pthread_create(&td_aux, NULL, aux_run, NULL)); + + // 创建被测线程(将在 pthread_join 处命中取消点) + TRY0(pthread_create(&td_tested, NULL, tested_run, NULL)); + + // 先投递取消,再放行到被测调用 + TRY0(pthread_cancel(td_tested)); + TRY0(sem_post(&sem_seq)); + + // 等待被测线程结束,并检查返回值是否为 PTHREAD_CANCELED + TRY0(pthread_join(td_tested, &res)); + + // 收尾:被测线程没有 join 辅助线程,因此主线程来收尸 + TRY0(pthread_join(td_aux, NULL)); + + // 销毁信号量 + TRY0(sem_destroy(&sem_seq)); + TRY0(sem_destroy(&sem_test)); + + int ok = (res == PTHREAD_CANCELED) && (seqno == 1); + printf("[non-blocking pthread_join] %s\n", + ok ? "PASS: 进入 pthread_join 即被取消 (seqno==1)" + : "FAIL: 未在入口被取消 或 seqno!=1"); + + return ok ? 0 : 1; +} diff --git a/usertests/pthread/cond.c b/usertests/pthread/cond.c new file mode 100644 index 0000000..8dc2b2b --- /dev/null +++ b/usertests/pthread/cond.c @@ -0,0 +1,93 @@ +#include +#include + +static void *start_signal(void *arg) +{ + void **args = arg; + pthread_mutex_lock(args[1]); + pthread_cond_signal(args[0]); + pthread_mutex_unlock(args[1]); + return 0; +} + +static void *start_wait(void *arg) +{ + void **args = arg; + pthread_mutex_t *m = args[1]; + pthread_cond_t *c = args[0]; + int *x = args[2]; + + pthread_mutex_lock(m); + while (*x) pthread_cond_wait(c, m); + pthread_mutex_unlock(m); + + return 0; +} + +int main(void) +{ + pthread_t td, td1, td2, td3; + void *res; + pthread_mutex_t mtx; + pthread_cond_t cond; + int foo[1]; + + /* Condition variables */ + pthread_mutex_init(&mtx, 0); + pthread_cond_init(&cond, 0); + pthread_mutex_lock(&mtx); + pthread_create(&td, 0, start_signal, (void *[]){ &cond, &mtx }); + pthread_cond_wait(&cond, &mtx); + pthread_join(td, &res); + // pthread_mutex_unlock(&mtx); + // pthread_mutex_destroy(&mtx); + // pthread_cond_destroy(&cond); + + /* Condition variables with multiple waiters */ + // pthread_mutex_init(&mtx, 0); + // pthread_cond_init(&cond, 0); + // pthread_mutex_lock(&mtx); + // foo[0] = 1; + // pthread_create(&td1, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_create(&td2, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_create(&td3, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_mutex_unlock(&mtx); + // nanosleep(&(struct timespec){.tv_nsec=1000000}, 0); + // foo[0] = 0; + // pthread_mutex_lock(&mtx); + // pthread_cond_signal(&cond); + // pthread_mutex_unlock(&mtx); + // pthread_mutex_lock(&mtx); + // pthread_cond_signal(&cond); + // pthread_mutex_unlock(&mtx); + // pthread_mutex_lock(&mtx); + // pthread_cond_signal(&cond); + // pthread_mutex_unlock(&mtx); + // pthread_join(td1, 0); + // pthread_join(td2, 0); + // pthread_join(td3, 0); + // pthread_mutex_destroy(&mtx); + // pthread_cond_destroy(&cond); + + /* Condition variables with broadcast signals */ + // pthread_mutex_init(&mtx, 0); + // pthread_cond_init(&cond, 0); + // pthread_mutex_lock(&mtx); + // foo[0] = 1; + // pthread_create(&td1, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_create(&td2, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_create(&td3, 0, start_wait, (void *[]){ &cond, &mtx, foo }); + // pthread_mutex_unlock(&mtx); + // nanosleep(&(struct timespec){.tv_nsec=1000000}, 0); + // pthread_mutex_lock(&mtx); + // foo[0] = 0; + // pthread_mutex_unlock(&mtx); + // pthread_cond_broadcast(&cond); + // pthread_join(td1, 0); + // pthread_join(td2, 0); + // pthread_join(td3, 0); + // pthread_mutex_destroy(&mtx); + // pthread_cond_destroy(&cond); + + return 0; +} diff --git a/usertests/signal/Makefile b/usertests/signal/Makefile new file mode 100644 index 0000000..6562557 --- /dev/null +++ b/usertests/signal/Makefile @@ -0,0 +1,29 @@ +ARCH ?= riscv64 +CROSS_COMPILE ?= riscv64-linux-gnu- + +CC = $(CROSS_COMPILE)gcc + +SRC_DIR = . +BUILD_DIR = build/$(ARCH) +OUTPUT_DIR = $(BUILD_DIR)/output + +CFLAGS += -Wall -Wextra + +TESTS = $(basename $(notdir $(wildcard $(SRC_DIR)/*.c))) +TEST_TARGETS = $(addprefix $(OUTPUT_DIR)/, $(TESTS)) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(info + CC $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) -c $< -o $@ + +$(OUTPUT_DIR)/%: $(BUILD_DIR)/%.o + $(info + LD $<) + @ mkdir -p $(OUTPUT_DIR) + @ $(CC) $(CFLAGS) $< -o $@ + +all: $(TEST_TARGETS) + +clean: + $(info + CLEAN) + @ rm -rf $(BUILD_DIR) diff --git a/usertests/signal/sigchld.c b/usertests/signal/sigchld.c new file mode 100644 index 0000000..e69de29 diff --git a/vdso/.gitignore b/vdso/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/vdso/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/vdso/CMakeLists.txt b/vdso/CMakeLists.txt new file mode 100644 index 0000000..9f79be7 --- /dev/null +++ b/vdso/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.16) + +project(kernelx_vdso C ASM) +enable_language(ASM C) + +if(NOT DEFINED ARCH) + message(FATAL_ERROR "ARCH is not defined") +endif() + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_ASM_COMPILER clang) +set(CMAKE_C_COMPILER clang) + +# Use clang as linker to handle RISC-V targets properly +set(CMAKE_LINKER clang) + +# Fix for CMAKE_ASM_CREATE_SHARED_LIBRARY not being set +if(NOT CMAKE_ASM_CREATE_SHARED_LIBRARY) + set(CMAKE_ASM_CREATE_SHARED_LIBRARY + " -o " + ) +endif() + +include(${KERNELX_HOME}/scripts/cmake/env.cmake) + +set(COMMON_FLAGS -Wall -Wextra -fno-common -fno-builtin -nostdlib -ffreestanding -fPIC) +list(APPEND COMMON_FLAGS -g -ggdb -Og) +list(APPEND COMMON_FLAGS ${ARCH_COMMON_FLAGS_LIST}) + +file( + GLOB_RECURSE SRCS + ${CMAKE_SOURCE_DIR}/src/arch/${ARCH}/*.c + ${CMAKE_SOURCE_DIR}/src/arch/${ARCH}/*.S +) + +add_library(vdso SHARED ${SRCS}) +target_compile_options(vdso PRIVATE ${COMMON_FLAGS}) +target_link_options(vdso PRIVATE ${ARCH_COMMON_FLAGS_LIST}) +set_target_properties(vdso PROPERTIES + POSITION_INDEPENDENT_CODE ON + LINKER_LANGUAGE C +) diff --git a/vdso/Makefile b/vdso/Makefile new file mode 100644 index 0000000..c6cbb41 --- /dev/null +++ b/vdso/Makefile @@ -0,0 +1,38 @@ +ifeq ($(KERNELX_HOME),) +$(error KERNELX_HOME is not set) +endif + +ifeq ($(ARCH),) +$(error ARCH is not set) +endif + +include $(KERNELX_HOME)/scripts/env/${ARCH}${ARCH_BITS}.env + +CC=clang + +BUILD_DIR = build/$(ARCH)${ARCH_BITS} +VDSOLIB_BUILD_DIR = $(BUILD_DIR)/lib +VDSOLIB_TARGET = $(VDSOLIB_BUILD_DIR)/libvdso.so +SYM_TARGET = $(BUILD_DIR)/symbols.inc +OBJ_TARGET = $(BUILD_DIR)/vdso.o + +CMAKE_DEFINITIONS += ARCH=$(ARCH) +CMAKE_DEFINITIONS += ARCH_BITS=$(ARCH_BITS) +CMAKE_DEFINITIONS += KERNELX_HOME=$(KERNELX_HOME) +CMAKE_FLAGS += $(addprefix -D, $(CMAKE_DEFINITIONS)) + +all: $(OBJ_TARGET) + +$(OBJ_TARGET): $(VDSOLIB_TARGET) $(SYM_TARGET) + @ mkdir -p $(BUILD_DIR) + @ $(CC) $(CFLAGS) $(ARCH_COMMON_FLAGS) -c vdso.S -o $@ -DVDSO=\"$(VDSO_TARGET)\" + +$(VDSOLIB_TARGET): + @ mkdir -p $(VDSOLIB_BUILD_DIR) + @ cmake -B $(VDSOLIB_BUILD_DIR) $(CMAKE_FLAGS) + @ cmake --build $(VDSOLIB_BUILD_DIR) + +$(SYM_TARGET): $(VDSOLIB_TARGET) + @ python3 scripts/symbols.py --input $< --output $@ + +.PHONY: all elf \ No newline at end of file diff --git a/vdso/scripts/symbols.py b/vdso/scripts/symbols.py new file mode 100644 index 0000000..062dab6 --- /dev/null +++ b/vdso/scripts/symbols.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import subprocess +import re +import argparse +import sys + + +def find_symbol_address_streaming(elf_file, symbols_to_found): + """ + 使用流式处理的方式,在找到符号后立即终止 readelf 进程。 + + :param elf_file: ELF 文件的路径 + :param symbol_name: 要查找的符号名称 + """ + command = ['readelf', '-sW', elf_file] + process = None + + to_found = len(symbols_to_found) + found = 0 + + try: + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 + ) + + line_regex = re.compile(r"^\s*\d+:\s+([0-9a-fA-F]{8,16})\s+(\d+)\s+(\w+)\s+\w+\s+\w+\s+\S+\s+(.+)$") + + for line in process.stdout: + match = line_regex.match(line.strip()) + if not match: + continue + + address = match.group(1) + found_symbol_name = match.group(4).strip() + clean_symbol_name = found_symbol_name.split('@@')[0] + + if clean_symbol_name in symbols_to_found and int(address, 16) != 0: + symbols_to_found[clean_symbol_name] = address + found += 1 + + if found == to_found: + process.terminate() + break + + + _, stderr_output = process.communicate() + + if not found: + if process.returncode != 0 and stderr_output: + print("\nreadelf 报告了错误:", file=sys.stderr) + print(stderr_output, file=sys.stderr) + + + except FileNotFoundError: + print(f"错误: 'readelf' 命令未找到。请确保它已安装并且在你的 PATH 中。", file=sys.stderr) + except Exception as e: + print(f"发生未知错误: {e}", file=sys.stderr) + finally: + if process and process.poll() is None: + process.kill() + + +def write_result_to_file(output_file, symbols): + with open(output_file, 'w') as f: + for symbol, address in symbols.items(): + if address: + f.write(f"pub const {symbol}: usize = 0x{address};\n") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument("--input") + parser.add_argument("--output") + + args = parser.parse_args() + + symbols_to_found = { + "__vdso_sigreturn_trampoline": "", + } + + find_symbol_address_streaming(args.input, symbols_to_found) + write_result_to_file(args.output, symbols_to_found) diff --git a/vdso/src/arch/riscv/signal_trampoline.S b/vdso/src/arch/riscv/signal_trampoline.S new file mode 100644 index 0000000..a6bb0aa --- /dev/null +++ b/vdso/src/arch/riscv/signal_trampoline.S @@ -0,0 +1,8 @@ +#define __NR_rt_sigreturn 139 + + .section .text + .global __vdso_sigreturn_trampoline + +__vdso_sigreturn_trampoline: + li a7, __NR_rt_sigreturn + ecall diff --git a/vdso/vdso.S b/vdso/vdso.S new file mode 100644 index 0000000..1d40e30 --- /dev/null +++ b/vdso/vdso.S @@ -0,0 +1,6 @@ + .global __vdso_start + .global __vdso_end + +__vdso_start: + .incbin VDSO +__vdso_end: