├── .cargo
└── config.toml
├── .gitignore
├── .idea
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── rust.xml
├── vcs.xml
├── vekos.iml
└── workspace.xml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
└── font8x16.bin
├── programs
└── VETests
├── rust-toolchain
├── rust-toolchain.txt
├── src
├── allocator.rs
├── block_cache.rs
├── boot_splash.rs
├── boot_verification.rs
├── buddy_allocator.rs
├── buffer_manager.rs
├── crypto.rs
├── elf.rs
├── font.rs
├── framebuffer.rs
├── fs.rs
├── gdt.rs
├── hash.rs
├── hash_chain.rs
├── inode_cache.rs
├── interrupts.rs
├── key_store.rs
├── main.rs
├── memory.rs
├── merkle_tree.rs
├── operation_proofs.rs
├── page_table.rs
├── page_table_cache.rs
├── priority.rs
├── process.rs
├── proof_storage.rs
├── scheduler.rs
├── scheduler_ml.rs
├── serial.rs
├── shell
│ ├── commands
│ │ ├── ls.rs
│ │ └── mod.rs
│ ├── display.rs
│ ├── executor.rs
│ ├── mod.rs
│ └── parser.rs
├── signals.rs
├── swap.rs
├── syscall.rs
├── time.rs
├── tsc.rs
├── tty.rs
├── verification.rs
├── vga_buffer.rs
└── vkfs.rs
└── x86_64-vekos.json
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | target = "x86_64-vekos.json"
3 |
4 | [unstable]
5 | build-std = ["core", "compiler_builtins", "alloc"]
6 | build-std-features = ["compiler-builtins-mem"]
7 |
8 | [target.'cfg(target_os = "none")']
9 | runner = "bootimage runner"
10 |
11 | [package.metadata.bootimage]
12 | physical-memory-offset = "0xFFFF800000000000"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.idea
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
42 |
43 |
44 |
45 |
46 |
47 |
56 |
57 |
58 |
59 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/rust.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vekos.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {
29 | "lastFilter": {
30 | "state": "OPEN",
31 | "assignee": "KdntNinja"
32 | }
33 | }
34 | {
35 | "selectedUrlAndAccountId": {
36 | "url": "https://github.com/KdntNinja/vekos.git",
37 | "accountId": "f8641087-6f1a-407b-8f0b-f3c7d94cc0d8"
38 | }
39 | }
40 |
41 |
42 |
43 |
44 |
45 |
46 | {
47 | "associatedIndex": 4
48 | }
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {
58 | "keyToString": {
59 | "Cargo.Run vekos.executor": "Run",
60 | "RunOnceActivity.ShowReadmeOnStart": "true",
61 | "RunOnceActivity.git.unshallow": "true",
62 | "RunOnceActivity.rust.reset.selective.auto.import": "true",
63 | "git-widget-placeholder": "main",
64 | "last_opened_file_path": "/home/kaiden/RustroverProjects/vekos",
65 | "node.js.detected.package.eslint": "true",
66 | "node.js.detected.package.tslint": "true",
67 | "node.js.selected.package.eslint": "(autodetect)",
68 | "node.js.selected.package.tslint": "(autodetect)",
69 | "nodejs_package_manager_path": "npm",
70 | "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
71 | "org.rust.first.attach.projects": "true",
72 | "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings",
73 | "vue.rearranger.settings.migration": "true"
74 | }
75 | }
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | $USER_HOME$/.subversion
122 |
123 |
124 |
125 |
126 | 1740597041738
127 |
128 |
129 | 1740597041738
130 |
131 |
132 |
133 |
134 | 1740597405599
135 |
136 |
137 |
138 | 1740597405599
139 |
140 |
141 |
142 | 1740597489235
143 |
144 |
145 |
146 | 1740597489235
147 |
148 |
149 |
150 | 1740598735205
151 |
152 |
153 |
154 | 1740598735205
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "vekos"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | bootloader = { version = "0.9.23", features = ["map_physical_memory"] }
8 | # bootloader = { version = "0.9.23", features = ["map_physical_memory", "vga_320x200"] }
9 | ed25519-compact = { version = "2.0.4", default-features = false }
10 | embedded-graphics = "0.8"
11 | ttf-parser = { version = "0.15", optional = true }
12 | spin = "0.5.2"
13 | volatile = "0.2.6"
14 | x86_64 = { version = "0.14.12", features = ["instructions"] }
15 | lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
16 | pic8259 = "0.10.1"
17 | pc-keyboard = "0.7.0"
18 | micromath = "2.1.0"
19 | uart_16550 = "0.2.0"
20 | array-init = "2.0.0"
21 | sha2 = { version = "0.10.7", default-features = false, features = ["force-soft"] }
22 | digest = { version = "0.10.7", default-features = false }
23 | blake3 = { version = "1.5", default-features = false, features = ["pure"] }
24 | rs_merkle = { version = "1.4", default-features = false }
25 |
26 | [build-dependencies]
27 | xbuild = "0.2.0"
28 |
29 | [package.metadata.bootimage]
30 | test-args = [
31 | "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
32 | "-serial", "stdio",
33 | "-display", "none"
34 | ]
35 | test-success-exit-code = 33
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VEKOS - Verified Experimental Kernel Operating System
2 |
3 | 
4 | 
5 | 
6 |
7 | VEKOS is an experimental operating system written in Rust that focuses on verification and security at its core. This is
8 | the first alpha release (v0.0.1) that demonstrates the basic architecture and key features of the system.
9 |
10 | **Watch the OS showcase by clicking on the thumbnail**
11 |
12 | [](https://youtu.be/U04Ct4uOCgg?feature=shared)
13 |
14 | ## VEKOS Key Features
15 |
16 | - **Verified Operations**: Every filesystem and memory operation is cryptographically verified using a proof system
17 | - **Secure Memory Management**: Buddy allocator with memory zones and COW (Copy-On-Write) support
18 | - **Modern Shell**: Basic shell implementation with command history and line editing
19 | - **Filesystem**: Verified filesystem (VKFS) with Merkle tree verification
20 | - **Process Management**: Basic process management with scheduling and signals
21 | - **Hardware Support**: x86_64 architecture support with proper GDT, IDT, and interrupt handling
22 |
23 | ## Current Functionality
24 |
25 | - **Memory Management**
26 | - Buddy allocation system
27 | - Page table management
28 | - Memory zones (DMA, Normal, HighMem)
29 | - Copy-on-Write support
30 | - Memory pressure handling
31 |
32 | - **Filesystem**
33 | - Basic filesystem operations (create, read, write, delete)
34 | - Directory support
35 | - Verification using Merkle trees
36 | - Buffer cache system
37 | - Inode management
38 |
39 | - **Process Management**
40 | - Basic process creation and management
41 | - Simple scheduler
42 | - Signal handling
43 | - Process groups and sessions
44 |
45 | - **Program Execution**
46 | - Basic program execution using SYSCALL/SYSRET.
47 |
48 | - **Shell**
49 | - Command history
50 | - Line editing
51 | - Basic built-in commands (cd, ls, pwd, help, clear)
52 | - Command parsing with quote handling
53 |
54 | - **Security Features**
55 | - Operation verification through cryptographic proofs
56 | - State transition validation
57 | - Memory isolation
58 | - Privilege levels
59 |
60 | ## Building
61 |
62 | ```bash
63 | # Clone this repository
64 | git clone https://github.com/JGiraldo29/vekos.git
65 | cd vekos
66 |
67 | # Add the rust-src component to the toolchain
68 | rustup component add rust-src
69 |
70 | # Build the kernel
71 | cargo build
72 | ```
73 |
74 | ## Running VEKOS
75 |
76 | Requires [QEMU](https://www.qemu.org/) and [bootimage](https://github.com/rust-osdev/bootimage) installed.
77 |
78 | ```bash
79 | # Add the llvm-tools-preview component to the toolchain
80 | rustup component add llvm-tools-preview
81 |
82 | # Run VEKOS
83 | cargo run
84 | ```
85 |
86 | ## Contributing
87 |
88 | VEKOS is in its early stages and welcomes contributions. Here are some areas where you can help:
89 |
90 | 1. **Core Features**
91 | - Expanding filesystem capabilities
92 | - Improving process scheduling
93 | - Adding device drivers
94 | - Enhancing memory management
95 |
96 | 2. **Documentation**
97 | - Code documentation
98 | - Architecture documentation
99 | - User guides
100 |
101 | 3. **Testing**
102 | - Unit tests
103 | - Integration tests
104 | - Performance benchmarks
105 |
106 | 4. **Bug Fixes**
107 | - Report issues
108 | - Submit pull requests
109 | - Help with code review
110 |
111 | ### Contributing Guidelines
112 |
113 | 1. Fork the repository
114 | 2. Create a feature branch
115 | 3. Write clean, documented code
116 | 4. Ensure all tests pass
117 | 5. Submit a pull request
118 |
119 | ## Known Limitations
120 |
121 | As this is an alpha release (0.0.1), there are several limitations:
122 |
123 | - Limited hardware support
124 | - Basic device driver support
125 | - Experimental verification system
126 | - Limited filesystem features
127 | - Basic shell functionality
128 | - Unsecure code
129 |
130 | ## Future Plans
131 |
132 | - Extended hardware support
133 | - Network stack implementation
134 | - Enhanced security features
135 | - GUI support
136 | - Extended system calls
137 | - Improved documentation
138 |
139 | ## License
140 |
141 | Apache-2.0 license
142 |
143 | ## Acknowledgments
144 |
145 | - The Rust programming language team
146 | - Contributors to the project
147 |
148 | ## Contact
149 |
150 | jgiraldonocua@gmail.com
151 |
152 | ---
153 |
154 | **Note**: VEKOS is currently in alpha stage (0.0.1). While it demonstrates core functionality, it should not be used in
155 | production environments. This is an experimental system focused on exploring verification techniques in operating system
156 | design.
157 |
--------------------------------------------------------------------------------
/assets/font8x16.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JGiraldo29/vekos/86a1c1242174a045f8a5d7af69cfdc499f2962c8/assets/font8x16.bin
--------------------------------------------------------------------------------
/programs/VETests:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JGiraldo29/vekos/86a1c1242174a045f8a5d7af69cfdc499f2962c8/programs/VETests
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly-2024-11-13"
3 |
--------------------------------------------------------------------------------
/rust-toolchain.txt:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly-2024-11-13"
3 |
--------------------------------------------------------------------------------
/src/allocator.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::buddy_allocator::LockedBuddyAllocator;
18 | use crate::serial_println;
19 | use crate::swap::DISK_IO;
20 | use crate::MEMORY_MANAGER;
21 | use core::alloc::Layout;
22 | use core::sync::atomic::{AtomicBool, Ordering};
23 | use x86_64::{
24 | structures::paging::{
25 | mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB,
26 | },
27 | VirtAddr,
28 | };
29 |
30 | #[cfg(test)]
31 | use crate::test_utils;
32 |
33 | #[global_allocator]
34 | pub static ALLOCATOR: LockedBuddyAllocator = LockedBuddyAllocator::new();
35 |
36 | pub static HEAP_INITIALIZED: AtomicBool = AtomicBool::new(false);
37 | pub const HEAP_START: usize = 0x_4444_4444_0000;
38 | pub const HEAP_SIZE: usize = 4 * 1024 * 1024;
39 |
40 | #[alloc_error_handler]
41 | fn alloc_error_handler(layout: Layout) -> ! {
42 | use core::sync::atomic::{AtomicBool, Ordering};
43 | static IN_OOM_HANDLER: AtomicBool = AtomicBool::new(false);
44 |
45 | if IN_OOM_HANDLER.swap(true, Ordering::SeqCst) {
46 | serial_println!("CRITICAL: Recursive OOM detected, halting system");
47 | loop {}
48 | }
49 |
50 | serial_println!("CRITICAL: Memory allocation failed: {:?}", layout);
51 |
52 | {
53 | let mut mm_lock = MEMORY_MANAGER.lock();
54 | if let Some(mm) = mm_lock.as_mut() {
55 | if mm.handle_memory_pressure() {
56 | if let Ok(()) = DISK_IO.lock().sync() {
57 | IN_OOM_HANDLER.store(false, Ordering::SeqCst);
58 |
59 | unsafe {
60 | let ptr = alloc::alloc::alloc(layout);
61 | if !ptr.is_null() {
62 | serial_println!(
63 | "OOM: Memory reclaimed successfully, allocation retry succeeded"
64 | );
65 | loop {}
66 | }
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | {
74 | let mm_lock = MEMORY_MANAGER.lock();
75 | if let Some(mm) = mm_lock.as_ref() {
76 | let stats = mm.get_memory_usage();
77 | serial_println!("Memory state at OOM: {}", stats);
78 | }
79 | }
80 |
81 | serial_println!("CRITICAL: Unable to recover from OOM, halting system");
82 | loop {}
83 | }
84 |
85 | pub fn init_heap(
86 | mapper: &mut impl Mapper,
87 | frame_allocator: &mut impl FrameAllocator,
88 | ) -> Result<(), MapToError> {
89 | let page_range = {
90 | let heap_start = VirtAddr::new(HEAP_START as u64);
91 | let heap_end = heap_start + HEAP_SIZE - 1u64;
92 | let heap_start_page = Page::containing_address(heap_start);
93 | let heap_end_page = Page::containing_address(heap_end);
94 | Page::range_inclusive(heap_start_page, heap_end_page)
95 | };
96 |
97 | for page in page_range {
98 | if mapper.translate_page(page).is_err() {
99 | let frame = frame_allocator
100 | .allocate_frame()
101 | .ok_or(MapToError::FrameAllocationFailed)?;
102 |
103 | let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
104 | unsafe {
105 | mapper.map_to(page, frame, flags, frame_allocator)?.flush();
106 | }
107 | }
108 | }
109 |
110 | unsafe {
111 | ALLOCATOR
112 | .lock()
113 | .init(VirtAddr::new(HEAP_START as u64), HEAP_SIZE)
114 | .expect("Heap initialization failed");
115 | }
116 |
117 | HEAP_INITIALIZED.store(true, Ordering::SeqCst);
118 | Ok(())
119 | }
120 |
--------------------------------------------------------------------------------
/src/block_cache.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::format;
18 | use crate::fs::FSOperation;
19 | use crate::fs::FILESYSTEM;
20 | use crate::hash;
21 | use crate::tsc;
22 | use crate::verification::FSOpType;
23 | use crate::verification::FSProof;
24 | use crate::verification::ProofData;
25 | use crate::verification::{Hash, OperationProof, Verifiable, VerificationError};
26 | use crate::VERIFICATION_REGISTRY;
27 | use alloc::collections::BTreeMap;
28 | use alloc::vec::Vec;
29 | use core::sync::atomic::{AtomicU64, Ordering};
30 | use x86_64::VirtAddr;
31 |
32 | #[derive(Debug)]
33 | pub struct CacheEntry {
34 | data: [u8; 4096],
35 | dirty: bool,
36 | hash: Hash,
37 | }
38 |
39 | #[derive(Debug)]
40 | pub struct BlockCache {
41 | entries: BTreeMap,
42 | state_hash: AtomicU64,
43 | hit_count: AtomicU64,
44 | miss_count: AtomicU64,
45 | }
46 |
47 | impl BlockCache {
48 | pub fn new() -> Self {
49 | Self {
50 | entries: BTreeMap::new(),
51 | state_hash: AtomicU64::new(0),
52 | hit_count: AtomicU64::new(0),
53 | miss_count: AtomicU64::new(0),
54 | }
55 | }
56 |
57 | pub fn get_block(&mut self, block_num: u64) -> Option> {
58 | let filesystem = FILESYSTEM.lock();
59 | let mut buffer_manager = filesystem.superblock.buffer_manager.lock();
60 |
61 | buffer_manager
62 | .get_buffer(block_num)
63 | .map(|buffer| buffer.get_data().to_vec())
64 | }
65 |
66 | pub fn write_block(&mut self, block_num: u64, data: &[u8]) -> Result<(), &'static str> {
67 | if data.len() != 4096 {
68 | return Err("Invalid block size");
69 | }
70 |
71 | let block_hash = hash::hash_memory(VirtAddr::new(data.as_ptr() as u64), data.len());
72 |
73 | let filesystem = FILESYSTEM.lock();
74 | let mut buffer_manager = filesystem.superblock.buffer_manager.lock();
75 |
76 | let prev_state = filesystem.superblock.state_hash();
77 | let proof = OperationProof {
78 | op_id: tsc::read_tsc(),
79 | prev_state,
80 | new_state: Hash(prev_state.0 ^ block_hash.0),
81 | data: ProofData::Filesystem(FSProof {
82 | operation: FSOpType::Modify,
83 | path: format!("block_{}", block_num),
84 | content_hash: block_hash,
85 | prev_state,
86 | new_state: Hash(prev_state.0 ^ block_hash.0),
87 | op: FSOperation::Write {
88 | path: format!("block_{}", block_num),
89 | data: data.to_vec(),
90 | },
91 | }),
92 | signature: [0; 64],
93 | };
94 |
95 | if let Some(buffer) = buffer_manager.get_buffer(block_num) {
96 | buffer.set_data(data);
97 | buffer.mark_dirty();
98 | buffer.unpin();
99 |
100 | let new_data = buffer.get_data();
101 | let verify_hash =
102 | hash::hash_memory(VirtAddr::new(new_data.as_ptr() as u64), new_data.len());
103 |
104 | if verify_hash != block_hash {
105 | return Err("Block verification failed");
106 | }
107 |
108 | VERIFICATION_REGISTRY.lock().register_proof(proof);
109 |
110 | Ok(())
111 | } else {
112 | Err("No buffers available")
113 | }
114 | }
115 |
116 | pub fn invalidate(&mut self, block_num: u64) {
117 | self.entries.remove(&block_num);
118 | }
119 |
120 | pub fn flush(&mut self) -> Vec<(u64, [u8; 4096])> {
121 | let mut dirty_blocks = Vec::new();
122 |
123 | self.entries.retain(|&block_num, entry| {
124 | if entry.dirty {
125 | dirty_blocks.push((block_num, entry.data));
126 | false
127 | } else {
128 | true
129 | }
130 | });
131 |
132 | dirty_blocks
133 | }
134 |
135 | pub fn get_stats(&self) -> (u64, u64) {
136 | (
137 | self.hit_count.load(Ordering::Relaxed),
138 | self.miss_count.load(Ordering::Relaxed),
139 | )
140 | }
141 | }
142 |
143 | impl Verifiable for BlockCache {
144 | fn generate_proof(
145 | &self,
146 | operation: crate::verification::Operation,
147 | ) -> Result {
148 | let prev_state = self.state_hash();
149 |
150 | let mut entry_hashes = Vec::new();
151 | for entry in self.entries.values() {
152 | entry_hashes.push(entry.hash);
153 | }
154 |
155 | let new_state = hash::combine_hashes(&entry_hashes);
156 |
157 | Ok(OperationProof {
158 | op_id: crate::tsc::read_tsc(),
159 | prev_state,
160 | new_state,
161 | data: crate::verification::ProofData::Memory(crate::verification::MemoryProof {
162 | operation: crate::verification::MemoryOpType::Modify,
163 | address: VirtAddr::new(0),
164 | size: 0,
165 | frame_hash: new_state,
166 | }),
167 | signature: [0u8; 64],
168 | })
169 | }
170 |
171 | fn verify_proof(&self, proof: &OperationProof) -> Result {
172 | let current_state = self.state_hash();
173 | Ok(current_state == proof.new_state)
174 | }
175 |
176 | fn state_hash(&self) -> Hash {
177 | Hash(self.state_hash.load(Ordering::SeqCst))
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/boot_splash.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::vga_buffer::{Color, ColorCode, WRITER};
18 | use core::fmt::Write;
19 |
20 | pub struct BootSplash;
21 |
22 | impl BootSplash {
23 | pub fn show_splash() {
24 | let mut writer = WRITER.lock();
25 | let original_color = writer.color_code;
26 |
27 | writer.clear_screen();
28 |
29 | writer.color_code = ColorCode::new(Color::White, Color::Black);
30 | writer
31 | .write_str("\n Verified Experimental Kernel Operating System\n")
32 | .unwrap();
33 | writer
34 | .write_str(" Version 0.0.1 - alpha\n\n")
35 | .unwrap();
36 |
37 | writer.color_code = ColorCode::new(Color::LightGray, Color::Black);
38 | writer
39 | .write_str(" Developed by Juan Miguel Giraldo\n\n")
40 | .unwrap();
41 |
42 | writer.color_code = original_color;
43 | }
44 |
45 | pub fn print_boot_message(msg: &str, status: BootMessageType) {
46 | let mut writer = WRITER.lock();
47 | let original_color = writer.color_code;
48 |
49 | writer.color_code = match status {
50 | BootMessageType::Info => ColorCode::new(Color::White, Color::Black),
51 | BootMessageType::Success => ColorCode::new(Color::LightGreen, Color::Black),
52 | BootMessageType::Warning => ColorCode::new(Color::Yellow, Color::Black),
53 | BootMessageType::Error => ColorCode::new(Color::Red, Color::Black),
54 | };
55 |
56 | let indicator = match status {
57 | BootMessageType::Info => "[*]",
58 | BootMessageType::Success => "[+]",
59 | BootMessageType::Warning => "[!]",
60 | BootMessageType::Error => "[x]",
61 | };
62 |
63 | writer.write_str(indicator).unwrap();
64 |
65 | writer.color_code = ColorCode::new(Color::White, Color::Black);
66 | writer.write_str(" ").unwrap();
67 | writer.write_str(msg).unwrap();
68 | writer.write_str("\n").unwrap();
69 |
70 | writer.color_code = original_color;
71 | }
72 | }
73 |
74 | #[derive(Debug, Clone, Copy)]
75 | pub enum BootMessageType {
76 | Info,
77 | Success,
78 | Warning,
79 | Error,
80 | }
81 |
--------------------------------------------------------------------------------
/src/buffer_manager.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::hash;
18 | use crate::serial_println;
19 | use crate::verification::{Hash, OperationProof, Verifiable, VerificationError};
20 | use alloc::collections::VecDeque;
21 | use alloc::vec::Vec;
22 | use core::sync::atomic::{AtomicU64, Ordering};
23 | use x86_64::VirtAddr;
24 |
25 | const BUFFER_SIZE: usize = 4096;
26 | const MAX_BUFFERS: usize = 256;
27 |
28 | #[derive(Debug)]
29 | pub struct Buffer {
30 | pub(crate) data: [u8; BUFFER_SIZE],
31 | pub(crate) block_num: u64,
32 | pub(crate) dirty: bool,
33 | pub(crate) pinned: bool,
34 | pub(crate) last_access: u64,
35 | pub(crate) access_count: u64,
36 | }
37 |
38 | impl Clone for Buffer {
39 | fn clone(&self) -> Self {
40 | Self {
41 | data: self.data,
42 | block_num: self.block_num,
43 | dirty: self.dirty,
44 | pinned: self.pinned,
45 | last_access: self.last_access,
46 | access_count: self.access_count,
47 | }
48 | }
49 | }
50 |
51 | #[derive(Debug)]
52 | pub struct BufferManager {
53 | buffers: VecDeque,
54 | free_buffers: Vec,
55 | stats: BufferStats,
56 | state_hash: AtomicU64,
57 | }
58 |
59 | #[derive(Debug, Default)]
60 | pub struct BufferStats {
61 | hits: AtomicU64,
62 | misses: AtomicU64,
63 | evictions: AtomicU64,
64 | writes: AtomicU64,
65 | }
66 |
67 | impl Buffer {
68 | pub fn new(block_num: u64) -> Self {
69 | Self {
70 | data: [0; BUFFER_SIZE],
71 | block_num,
72 | dirty: false,
73 | pinned: false,
74 | last_access: 0,
75 | access_count: 0,
76 | }
77 | }
78 |
79 | pub fn pin(&mut self) {
80 | self.pinned = true;
81 | }
82 |
83 | pub fn unpin(&mut self) {
84 | self.pinned = false;
85 | }
86 |
87 | pub fn mark_dirty(&mut self) {
88 | self.dirty = true;
89 | }
90 |
91 | pub fn is_dirty(&self) -> bool {
92 | self.dirty
93 | }
94 |
95 | pub fn get_data(&self) -> &[u8] {
96 | &self.data[..]
97 | }
98 |
99 | pub fn set_data(&mut self, new_data: &[u8]) {
100 | self.data.copy_from_slice(new_data);
101 | }
102 |
103 | pub fn clear(&mut self) {
104 | self.data.fill(0);
105 | self.dirty = false;
106 | self.pinned = false;
107 | self.last_access = 0;
108 | self.access_count = 0;
109 | }
110 | }
111 |
112 | impl BufferManager {
113 | pub fn new() -> Self {
114 | serial_println!("BufferManager: Starting initialization");
115 |
116 | const INITIAL_CAPACITY: usize = 32;
117 |
118 | let mut free_buffers = Vec::with_capacity(INITIAL_CAPACITY);
119 |
120 | for _ in 0..INITIAL_CAPACITY {
121 | free_buffers.push(Buffer::new(0));
122 | }
123 |
124 | serial_println!(
125 | "BufferManager: Created initial {} buffers",
126 | INITIAL_CAPACITY
127 | );
128 |
129 | Self {
130 | buffers: VecDeque::with_capacity(INITIAL_CAPACITY),
131 | free_buffers,
132 | stats: BufferStats::default(),
133 | state_hash: AtomicU64::new(0),
134 | }
135 | }
136 |
137 | fn grow_buffer_pool(&mut self) -> Result<(), &'static str> {
138 | if self.free_buffers.len() >= MAX_BUFFERS {
139 | return Err("Maximum buffer pool size reached");
140 | }
141 |
142 | const GROWTH_CHUNK: usize = 16;
143 | let new_size = core::cmp::min(self.free_buffers.len() + GROWTH_CHUNK, MAX_BUFFERS);
144 |
145 | for _ in self.free_buffers.len()..new_size {
146 | self.free_buffers.push(Buffer::new(0));
147 | }
148 |
149 | Ok(())
150 | }
151 |
152 | pub fn get_buffer(&mut self, block_num: u64) -> Option<&mut Buffer> {
153 | serial_println!("BufferManager: Attempting to get buffer {}", block_num);
154 |
155 | if let Some(index) = self.buffers.iter().position(|b| b.block_num == block_num) {
156 | let buffer = &mut self.buffers[index];
157 | buffer.last_access = crate::tsc::read_tsc();
158 | buffer.access_count += 1;
159 | self.stats.hits.fetch_add(1, Ordering::Relaxed);
160 | Some(buffer)
161 | } else {
162 | self.stats.misses.fetch_add(1, Ordering::Relaxed);
163 |
164 | if self.buffers.len() >= self.free_buffers.len() {
165 | if let Err(_) = self.grow_buffer_pool() {
166 | self.evict_one();
167 | }
168 | }
169 |
170 | let mut new_buffer = Buffer::new(block_num);
171 |
172 | let block_addr = VirtAddr::new(block_num * BUFFER_SIZE as u64);
173 | unsafe {
174 | core::ptr::copy_nonoverlapping(
175 | block_addr.as_ptr::(),
176 | new_buffer.data.as_mut_ptr(),
177 | BUFFER_SIZE,
178 | );
179 | }
180 |
181 | self.buffers.push_back(new_buffer);
182 | let last_index = self.buffers.len() - 1;
183 | Some(&mut self.buffers[last_index])
184 | }
185 | }
186 |
187 | pub fn release_buffer(&mut self, block_num: u64) {
188 | if let Some(pos) = self.buffers.iter().position(|b| b.block_num == block_num) {
189 | let mut buffer = self.buffers.remove(pos).unwrap();
190 | if buffer.is_dirty() {
191 | self.flush_buffer(buffer.block_num, buffer.data);
192 | }
193 | buffer.clear();
194 | self.free_buffers.push(buffer);
195 | }
196 | }
197 |
198 | pub fn flush_all(&mut self) {
199 | let dirty_buffers: Vec<_> = self
200 | .buffers
201 | .iter()
202 | .filter(|b| b.is_dirty())
203 | .map(|b| (b.block_num, b.data.clone()))
204 | .collect();
205 |
206 | for (block_num, data) in dirty_buffers {
207 | self.flush_buffer(block_num, data);
208 | }
209 | }
210 |
211 | fn flush_buffer(&mut self, block_num: u64, data: [u8; BUFFER_SIZE]) {
212 | let block_addr = block_num * BUFFER_SIZE as u64;
213 | unsafe {
214 | core::ptr::copy_nonoverlapping(
215 | data.as_ptr(),
216 | VirtAddr::new(block_addr).as_mut_ptr(),
217 | BUFFER_SIZE,
218 | );
219 | }
220 | self.stats.writes.fetch_add(1, Ordering::Relaxed);
221 | }
222 |
223 | pub fn get_stats(&self) -> (u64, u64, u64, u64) {
224 | (
225 | self.stats.hits.load(Ordering::Relaxed),
226 | self.stats.misses.load(Ordering::Relaxed),
227 | self.stats.evictions.load(Ordering::Relaxed),
228 | self.stats.writes.load(Ordering::Relaxed),
229 | )
230 | }
231 |
232 | pub fn evict_one(&mut self) {
233 | if let Some(idx) = self
234 | .buffers
235 | .iter()
236 | .enumerate()
237 | .filter(|(_, buffer)| !buffer.pinned)
238 | .min_by_key(|(_, buffer)| (buffer.access_count, buffer.last_access))
239 | .map(|(idx, _)| idx)
240 | {
241 | let buffer = &self.buffers[idx];
242 | if buffer.dirty {
243 | let block_num = buffer.block_num;
244 | let data = buffer.data;
245 | self.flush_buffer(block_num, data);
246 | }
247 |
248 | self.buffers.remove(idx);
249 | self.stats.evictions.fetch_add(1, Ordering::Relaxed);
250 | }
251 | }
252 | }
253 |
254 | impl Verifiable for BufferManager {
255 | fn generate_proof(
256 | &self,
257 | operation: crate::verification::Operation,
258 | ) -> Result {
259 | let prev_state = self.state_hash();
260 |
261 | let mut buffer_hashes = Vec::new();
262 | for buffer in &self.buffers {
263 | let buffer_hash =
264 | hash::hash_memory(VirtAddr::new(buffer.data.as_ptr() as u64), BUFFER_SIZE);
265 | buffer_hashes.push(buffer_hash);
266 | }
267 |
268 | let combined_hash = hash::combine_hashes(&buffer_hashes);
269 | let new_state = Hash(prev_state.0 ^ combined_hash.0);
270 |
271 | Ok(OperationProof {
272 | op_id: crate::tsc::read_tsc(),
273 | prev_state,
274 | new_state,
275 | data: crate::verification::ProofData::Memory(crate::verification::MemoryProof {
276 | operation: crate::verification::MemoryOpType::Modify,
277 | address: VirtAddr::new(0),
278 | size: 0,
279 | frame_hash: combined_hash,
280 | }),
281 | signature: [0u8; 64],
282 | })
283 | }
284 |
285 | fn verify_proof(&self, proof: &OperationProof) -> Result {
286 | let current_state = self.state_hash();
287 | Ok(current_state == proof.new_state)
288 | }
289 |
290 | fn state_hash(&self) -> Hash {
291 | Hash(self.state_hash.load(Ordering::SeqCst))
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/src/crypto.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use lazy_static::lazy_static;
18 | use crate::serial_println;
19 | use ed25519_compact::{Signature, PublicKey, Noise};
20 | use spin::Mutex;
21 | use crate::key_store;
22 |
23 | const ED25519_PUBLIC_KEY_LENGTH: usize = 32;
24 | const VKFS_KEY_LENGTH: usize = 64;
25 | const ED25519_SIGNATURE_LENGTH: usize = 64;
26 |
27 | pub struct CryptoVerifier {
28 | verification_key: [u8; VKFS_KEY_LENGTH],
29 | }
30 |
31 | macro_rules! array_ref {
32 | ($arr:expr, $offset:expr, $len:expr) => {{
33 | {
34 | #[inline]
35 | unsafe fn as_array(slice: &[T]) -> &[T; $len] {
36 | &*(slice.as_ptr() as *const [T; $len])
37 | }
38 | let slice = &$arr[$offset..];
39 | debug_assert!(slice.len() >= $len);
40 | unsafe { as_array(slice) }
41 | }
42 | }};
43 | }
44 |
45 |
46 | impl CryptoVerifier {
47 | pub fn new(initial_key: [u8; VKFS_KEY_LENGTH]) -> Self {
48 | Self {
49 | verification_key: initial_key,
50 | }
51 | }
52 |
53 | pub fn set_verification_key(&mut self, key: &[u8; VKFS_KEY_LENGTH]) {
54 | self.verification_key[..].copy_from_slice(key);
55 | }
56 |
57 | pub fn verify_signature(&self, data: &[u8], signature: &[u8; ED25519_SIGNATURE_LENGTH]) -> bool {
58 | serial_println!("Starting ED25519 signature verification");
59 | serial_println!("Data length: {} bytes", data.len());
60 | serial_println!("Signature: {:02x?}...", &signature[..4]);
61 |
62 | if signature.len() != ED25519_SIGNATURE_LENGTH {
63 | serial_println!("Invalid signature length: {}", signature.len());
64 | return false;
65 | }
66 |
67 | let public_key_bytes = array_ref!(self.verification_key, 0, ED25519_PUBLIC_KEY_LENGTH);
68 |
69 | let public_key = match PublicKey::from_slice(public_key_bytes) {
70 | Ok(key) => key,
71 | Err(e) => {
72 | serial_println!("Invalid public key format: {:?}", e);
73 | return false;
74 | }
75 | };
76 |
77 | let sig = match Signature::from_slice(signature) {
78 | Ok(s) => s,
79 | Err(e) => {
80 | serial_println!("Invalid signature format: {:?}", e);
81 | return false;
82 | }
83 | };
84 |
85 | match public_key.verify(data, &sig) {
86 | Ok(_) => {
87 | serial_println!("Signature verification successful");
88 | true
89 | },
90 | Err(e) => {
91 | serial_println!("Signature verification failed: {:?}", e);
92 | false
93 | }
94 | }
95 | }
96 |
97 | pub fn generate_new_keypair(&mut self) -> Result<[u8; ED25519_SIGNATURE_LENGTH], &'static str> {
98 | let mut seed_bytes = [0u8; 32];
99 | if let Some(random_value) = self.get_secure_random() {
100 | seed_bytes[0..8].copy_from_slice(&random_value.to_ne_bytes());
101 |
102 | for i in 1..4 {
103 | if let Some(r) = self.get_secure_random() {
104 | seed_bytes[i*8..(i+1)*8].copy_from_slice(&r.to_ne_bytes());
105 | }
106 | }
107 |
108 | let seed = ed25519_compact::Seed::new(seed_bytes);
109 | let key_pair = ed25519_compact::KeyPair::from_seed(seed);
110 |
111 | let mut vkey = [0u8; VKFS_KEY_LENGTH];
112 | vkey[..ED25519_PUBLIC_KEY_LENGTH].copy_from_slice(key_pair.pk.as_ref());
113 | self.set_verification_key(&vkey);
114 |
115 | let mut private_key = [0u8; ED25519_SIGNATURE_LENGTH];
116 | private_key.copy_from_slice(key_pair.sk.as_ref());
117 | return Ok(private_key);
118 | }
119 |
120 | Err("Failed to generate secure random seed")
121 | }
122 |
123 | fn get_secure_random(&self) -> Option {
124 | if unsafe { core::arch::x86_64::__cpuid(1).ecx & (1 << 30) != 0 } {
125 | let mut val: u64 = 0;
126 | if unsafe { core::arch::x86_64::_rdrand64_step(&mut val) == 1 } {
127 | return Some(val);
128 | }
129 | }
130 |
131 | let tsc = crate::tsc::read_tsc();
132 | Some(tsc.wrapping_mul(0x9e3779b97f4a7c15).rotate_left(17))
133 | }
134 |
135 | pub fn sign_data(&self, data: &[u8], signing_key: &[u8; ED25519_SIGNATURE_LENGTH])
136 | -> Result<[u8; ED25519_SIGNATURE_LENGTH], &'static str> {
137 |
138 | let private_key = match ed25519_compact::SecretKey::from_slice(signing_key) {
139 | Ok(key) => key,
140 | Err(_) => return Err("Invalid signing key"),
141 | };
142 |
143 | let mut noise_bytes = [0u8; 16];
144 | for i in 0..2 {
145 | if let Some(random) = self.get_secure_random() {
146 | let bytes = random.to_ne_bytes();
147 | let start = i * 8;
148 | noise_bytes[start..start+8].copy_from_slice(&bytes);
149 | }
150 | }
151 |
152 | let signature = private_key.sign(data, Some(Noise::new(noise_bytes)));
153 |
154 | let mut sig_bytes = [0u8; ED25519_SIGNATURE_LENGTH];
155 | sig_bytes.copy_from_slice(signature.as_ref());
156 | Ok(sig_bytes)
157 | }
158 |
159 | pub fn get_verification_key(&self) -> Result<[u8; VKFS_KEY_LENGTH], &'static str> {
160 | let mut key_copy = [0u8; VKFS_KEY_LENGTH];
161 | key_copy.copy_from_slice(&self.verification_key);
162 | Ok(key_copy)
163 | }
164 |
165 | pub fn test_verification(&self) -> bool {
166 | serial_println!("Running ED25519 verification test with self-generated keypair");
167 |
168 | let seed = ed25519_compact::Seed::new([
169 | 0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60,
170 | 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4,
171 | 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19,
172 | 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60
173 | ]);
174 |
175 | let key_pair = ed25519_compact::KeyPair::from_seed(seed);
176 | let message = b"test message";
177 |
178 | let signature = key_pair.sk.sign(message, None);
179 |
180 | let mut test_verifier = CryptoVerifier::new([0; VKFS_KEY_LENGTH]);
181 | let mut test_key = [0; VKFS_KEY_LENGTH];
182 | test_key[..32].copy_from_slice(key_pair.pk.as_ref());
183 | test_verifier.set_verification_key(&test_key);
184 |
185 | let signature_bytes = signature.as_ref();
186 | let mut signature_array = [0u8; 64];
187 | signature_array.copy_from_slice(signature_bytes);
188 |
189 | let result = test_verifier.verify_signature(message, &signature_array);
190 |
191 | serial_println!("Test verification result: {}", result);
192 |
193 | let system_result = match key_store::KEY_STORE.lock().get_verification_key() {
194 | Ok(key) => {
195 | let has_real_key = !key.iter().all(|&b| b == 0);
196 | if !has_real_key {
197 | serial_println!("WARNING: System is using zero verification key");
198 | false
199 | } else {
200 | let mut system_test = CryptoVerifier::new([0; VKFS_KEY_LENGTH]);
201 | system_test.set_verification_key(&key);
202 |
203 | let sign_result = match key_store::KEY_STORE.lock().sign_data(message) {
204 | Ok(sig) => {
205 | let verify_result = system_test.verify_signature(message, &sig);
206 | serial_println!("System key verification test: {}", verify_result);
207 | verify_result
208 | },
209 | Err(e) => {
210 | serial_println!("System key signing test failed: {}", e);
211 | false
212 | }
213 | };
214 |
215 | sign_result
216 | }
217 | },
218 | Err(e) => {
219 | serial_println!("Failed to get system verification key: {}", e);
220 | false
221 | }
222 | };
223 |
224 | serial_println!("System verification test: {}", system_result);
225 |
226 | result && system_result
227 | }
228 | }
229 |
230 | fn constant_time_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
231 | let mut result: u8 = 0;
232 |
233 | for i in 0..32 {
234 | result |= a[i] ^ b[i];
235 | }
236 |
237 | result == 0
238 | }
239 |
240 | pub fn init() -> bool {
241 | serial_println!("Initializing cryptographic subsystem");
242 |
243 | let verifier = CRYPTO_VERIFIER.lock();
244 | let test_result = verifier.test_verification();
245 |
246 | if test_result {
247 | serial_println!("Cryptographic subsystem initialization successful");
248 | } else {
249 | serial_println!("WARNING: Cryptographic subsystem initialization failed!");
250 | }
251 |
252 | test_result
253 | }
254 |
255 | lazy_static! {
256 | pub static ref CRYPTO_VERIFIER: Mutex =
257 | Mutex::new(CryptoVerifier::new([0; VKFS_KEY_LENGTH]));
258 | }
259 |
--------------------------------------------------------------------------------
/src/elf.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::memory::MemoryError;
18 | use crate::memory::MemoryManager;
19 | use crate::serial_println;
20 | use core::mem::size_of;
21 | use x86_64::structures::paging::FrameAllocator;
22 | use x86_64::structures::paging::Mapper;
23 | use x86_64::structures::paging::Size4KiB;
24 | use x86_64::structures::paging::{Page, PageTableFlags};
25 | use x86_64::VirtAddr;
26 |
27 | #[repr(C)]
28 | pub struct ElfHeader {
29 | e_ident: [u8; 16],
30 | e_type: u16,
31 | e_machine: u16,
32 | e_version: u32,
33 | e_entry: u64,
34 | e_phoff: u64,
35 | e_shoff: u64,
36 | e_flags: u32,
37 | e_ehsize: u16,
38 | e_phentsize: u16,
39 | e_phnum: u16,
40 | e_shentsize: u16,
41 | e_shnum: u16,
42 | e_shstrndx: u16,
43 | }
44 |
45 | #[repr(C)]
46 | pub struct ProgramHeader {
47 | p_type: u32,
48 | p_flags: u32,
49 | p_offset: u64,
50 | p_vaddr: u64,
51 | p_paddr: u64,
52 | p_filesz: u64,
53 | p_memsz: u64,
54 | p_align: u64,
55 | }
56 |
57 | const PT_LOAD: u32 = 1;
58 | const PF_X: u32 = 0x1;
59 | const PF_W: u32 = 0x2;
60 | const PF_R: u32 = 0x4;
61 |
62 | pub fn load_elf(data: &[u8], memory_manager: &mut MemoryManager) -> Result {
63 | serial_println!("Starting ELF loading");
64 | serial_println!("File size: {} bytes", data.len());
65 |
66 | if data.len() < size_of::() {
67 | return Err(MemoryError::InvalidExecutable);
68 | }
69 |
70 | let header = unsafe { &*(data.as_ptr() as *const ElfHeader) };
71 |
72 | if &header.e_ident[0..4] != b"\x7FELF" {
73 | return Err(MemoryError::InvalidExecutable);
74 | }
75 |
76 | serial_println!("Entry point: {:#x}", header.e_entry);
77 | serial_println!(
78 | "Program headers: {} at offset {:#x}",
79 | header.e_phnum,
80 | header.e_phoff
81 | );
82 |
83 | let ph_offset = header.e_phoff as usize;
84 | let ph_count = header.e_phnum as usize;
85 |
86 | for i in 0..ph_count {
87 | let ph_pos = ph_offset + i * size_of::();
88 | if ph_pos + size_of::() > data.len() {
89 | return Err(MemoryError::InvalidExecutable);
90 | }
91 |
92 | let ph = unsafe { &*(data[ph_pos..].as_ptr() as *const ProgramHeader) };
93 |
94 | if ph.p_type == PT_LOAD {
95 | serial_println!("\nProgram header analysis:");
96 | serial_println!(" Type: LOAD");
97 | serial_println!(" Offset in file: {:#x}", ph.p_offset);
98 | serial_println!(" Virtual Address: {:#x}", ph.p_vaddr);
99 | serial_println!(" Physical Address: {:#x}", ph.p_paddr);
100 | serial_println!(" File size: {:#x}", ph.p_filesz);
101 | serial_println!(" Memory size: {:#x}", ph.p_memsz);
102 | serial_println!(
103 | " Flags: {:#x} ({}{}{})",
104 | ph.p_flags,
105 | if ph.p_flags & PF_R != 0 { "R" } else { "-" },
106 | if ph.p_flags & PF_W != 0 { "W" } else { "-" },
107 | if ph.p_flags & PF_X != 0 { "X" } else { "-" }
108 | );
109 | serial_println!(" Alignment: {:#x}", ph.p_align);
110 |
111 | if ph.p_filesz > 0 {
112 | let segment_data =
113 | &data[ph.p_offset as usize..][..core::cmp::min(16, ph.p_filesz as usize)];
114 | serial_println!(" First bytes in file: {:02x?}", segment_data);
115 | }
116 |
117 | let start_page = Page::containing_address(VirtAddr::new(ph.p_vaddr));
118 | let end_page = Page::containing_address(VirtAddr::new(ph.p_vaddr + ph.p_memsz - 1));
119 |
120 | let mut flags = PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE;
121 |
122 | if ph.p_flags & PF_W != 0 {
123 | flags |= PageTableFlags::WRITABLE;
124 | }
125 |
126 | if ph.p_flags & PF_X == 0 {
127 | flags |= PageTableFlags::NO_EXECUTE;
128 | }
129 |
130 | serial_println!("Mapping pages with flags: {:#x}", flags.bits());
131 |
132 | for page in Page::range_inclusive(start_page, end_page) {
133 | let page_addr = page.start_address().as_u64();
134 |
135 | if let Ok(_) = memory_manager.page_table.translate_page(page) {
136 | serial_println!("Page {:#x} already mapped, skipping", page_addr);
137 | continue;
138 | }
139 |
140 | serial_println!("Mapping new page {:#x}", page_addr);
141 | let frame = memory_manager
142 | .frame_allocator
143 | .allocate_frame()
144 | .ok_or(MemoryError::FrameAllocationFailed)?;
145 |
146 | unsafe {
147 | let temp_flags = flags | PageTableFlags::WRITABLE;
148 | serial_println!(
149 | "Mapping page {:#x} to frame {:#x} with flags {:#x}",
150 | page_addr,
151 | frame.start_address().as_u64(),
152 | temp_flags.bits()
153 | );
154 |
155 | memory_manager.map_page(page, frame, temp_flags)?;
156 |
157 | let dest_ptr = page.start_address().as_mut_ptr::();
158 | core::ptr::write_bytes(dest_ptr, 0, Page::::SIZE as usize);
159 |
160 | let page_offset = page.start_address().as_u64() - ph.p_vaddr;
161 | if page_offset < ph.p_filesz {
162 | let file_offset = ph.p_offset + page_offset;
163 | let copy_size = core::cmp::min(
164 | Page::::SIZE as u64,
165 | ph.p_filesz - page_offset,
166 | ) as usize;
167 |
168 | if file_offset as usize + copy_size <= data.len() {
169 | serial_println!("Copying segment data:");
170 | serial_println!(" From file offset: {:#x}", file_offset);
171 | serial_println!(
172 | " To virtual address: {:#x}",
173 | page.start_address().as_u64()
174 | );
175 | serial_println!(" Size: {:#x} bytes", copy_size);
176 |
177 | let src_slice = &data[file_offset as usize..][..copy_size];
178 | serial_println!(
179 | " Source data: {:02x?}",
180 | &src_slice[..core::cmp::min(16, src_slice.len())]
181 | );
182 |
183 | core::ptr::copy_nonoverlapping(
184 | data.as_ptr().add(file_offset as usize),
185 | dest_ptr,
186 | copy_size,
187 | );
188 |
189 | let dest_slice = core::slice::from_raw_parts(dest_ptr, copy_size);
190 | serial_println!(
191 | " Copied data: {:02x?}",
192 | &dest_slice[..core::cmp::min(16, dest_slice.len())]
193 | );
194 | }
195 | }
196 |
197 | if ph.p_flags & PF_W == 0 {
198 | memory_manager.update_page_flags(page, flags)?;
199 | }
200 | }
201 | }
202 | }
203 | }
204 |
205 | Ok(VirtAddr::new(header.e_entry))
206 | }
207 |
--------------------------------------------------------------------------------
/src/font.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use lazy_static::lazy_static;
18 | use spin::Mutex;
19 |
20 | const FONT_DATA: &[u8; 4096] = include_bytes!("../assets/font8x16.bin");
21 | const GLYPH_HEIGHT: usize = 16;
22 | const GLYPH_WIDTH: usize = 8;
23 |
24 | pub struct Font {
25 | data: &'static [u8; 4096],
26 | }
27 |
28 | impl Font {
29 | pub fn new() -> Self {
30 | Self { data: FONT_DATA }
31 | }
32 |
33 | pub fn get_glyph(&self, c: char) -> &[u8] {
34 | let idx = c as usize * GLYPH_HEIGHT;
35 | &self.data[idx..idx + GLYPH_HEIGHT]
36 | }
37 | }
38 |
39 | lazy_static! {
40 | pub static ref FONT: Mutex = Mutex::new(Font::new());
41 | }
42 |
--------------------------------------------------------------------------------
/src/gdt.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::serial_println;
18 | use lazy_static::lazy_static;
19 | use x86_64::instructions::segmentation::Segment;
20 | use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
21 | use x86_64::structures::tss::TaskStateSegment;
22 | use x86_64::VirtAddr;
23 |
24 | pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
25 | const STACK_SIZE: usize = 4096 * 5;
26 |
27 | #[derive(Debug)]
28 | pub struct Selectors {
29 | pub code_selector: SegmentSelector,
30 | pub data_selector: SegmentSelector,
31 | pub user_code_selector: SegmentSelector,
32 | pub user_data_selector: SegmentSelector,
33 | pub tss_selector: SegmentSelector,
34 | }
35 |
36 | lazy_static! {
37 | static ref TSS: TaskStateSegment = {
38 | let mut tss = TaskStateSegment::new();
39 | tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
40 | static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
41 | let stack_start = VirtAddr::from_ptr(unsafe { &raw const STACK });
42 | let stack_end = stack_start + STACK_SIZE;
43 | stack_end
44 | };
45 | tss
46 | };
47 | pub static ref GDT: (GlobalDescriptorTable, Selectors) = {
48 | serial_println!("DEBUG: Starting GDT initialization...");
49 | let mut gdt = GlobalDescriptorTable::new();
50 |
51 | serial_println!("DEBUG: Adding null descriptor");
52 |
53 | serial_println!("DEBUG: Adding kernel code segment");
54 | let kcode_selector = gdt.add_entry(Descriptor::kernel_code_segment());
55 | serial_println!("DEBUG: Kernel code selector: {:#x}", kcode_selector.0);
56 |
57 | serial_println!("DEBUG: Adding kernel data segment");
58 | let kdata_selector = gdt.add_entry(Descriptor::kernel_data_segment());
59 | serial_println!("DEBUG: Kernel data selector: {:#x}", kdata_selector.0);
60 |
61 | serial_println!("DEBUG: Adding user data segment");
62 | let udata_selector = gdt.add_entry(Descriptor::user_data_segment());
63 | serial_println!("DEBUG: User data selector: {:#x}", udata_selector.0);
64 |
65 | serial_println!("DEBUG: Adding padding descriptor");
66 | gdt.add_entry(Descriptor::user_data_segment());
67 |
68 | serial_println!("DEBUG: Adding user code segment");
69 | let ucode_selector = gdt.add_entry(Descriptor::user_code_segment());
70 | serial_println!("DEBUG: User code selector: {:#x}", ucode_selector.0);
71 |
72 | serial_println!("DEBUG: Adding TSS segment");
73 | let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
74 | serial_println!("DEBUG: TSS selector: {:#x}", tss_selector.0);
75 |
76 | serial_println!("GDT Layout Summary:");
77 | serial_println!("0x00: Null");
78 | serial_println!("0x08: Kernel Code");
79 | serial_println!("0x10: Kernel Data");
80 | serial_println!("0x18: User Data");
81 | serial_println!("0x20: Padding");
82 | serial_println!("0x28: User Code");
83 | serial_println!("0x30: TSS");
84 |
85 | (
86 | gdt,
87 | Selectors {
88 | code_selector: kcode_selector,
89 | data_selector: kdata_selector,
90 | user_code_selector: ucode_selector,
91 | user_data_selector: udata_selector,
92 | tss_selector,
93 | },
94 | )
95 | };
96 | }
97 |
98 | pub fn init() {
99 | use x86_64::instructions::segmentation::{CS, DS, ES, FS, GS, SS};
100 | use x86_64::instructions::tables::load_tss;
101 |
102 | serial_println!("DEBUG: Starting GDT init...");
103 |
104 | GDT.0.load();
105 | serial_println!("DEBUG: GDT loaded successfully");
106 |
107 | unsafe {
108 | CS::set_reg(GDT.1.code_selector);
109 | serial_println!("DEBUG: CS set to {:#x}", CS::get_reg().0);
110 |
111 | DS::set_reg(GDT.1.data_selector);
112 | if DS::get_reg().0 != GDT.1.data_selector.0 {
113 | serial_println!("ERROR: DS not set correctly");
114 | }
115 |
116 | ES::set_reg(GDT.1.data_selector);
117 | if ES::get_reg().0 != GDT.1.data_selector.0 {
118 | serial_println!("ERROR: ES not set correctly");
119 | }
120 |
121 | FS::set_reg(GDT.1.data_selector);
122 | if FS::get_reg().0 != GDT.1.data_selector.0 {
123 | serial_println!("ERROR: FS not set correctly");
124 | }
125 |
126 | GS::set_reg(GDT.1.data_selector);
127 | if GS::get_reg().0 != GDT.1.data_selector.0 {
128 | serial_println!("ERROR: GS not set correctly");
129 | }
130 |
131 | SS::set_reg(GDT.1.data_selector);
132 | if SS::get_reg().0 != GDT.1.data_selector.0 {
133 | serial_println!("ERROR: SS not set correctly");
134 | }
135 |
136 | load_tss(GDT.1.tss_selector);
137 |
138 | serial_println!("Final segment verification:");
139 | serial_println!(
140 | "CS: {:#x} (expected: {:#x})",
141 | CS::get_reg().0,
142 | GDT.1.code_selector.0
143 | );
144 | serial_println!(
145 | "DS: {:#x} (expected: {:#x})",
146 | DS::get_reg().0,
147 | GDT.1.data_selector.0
148 | );
149 | serial_println!(
150 | "ES: {:#x} (expected: {:#x})",
151 | ES::get_reg().0,
152 | GDT.1.data_selector.0
153 | );
154 | serial_println!(
155 | "FS: {:#x} (expected: {:#x})",
156 | FS::get_reg().0,
157 | GDT.1.data_selector.0
158 | );
159 | serial_println!(
160 | "GS: {:#x} (expected: {:#x})",
161 | GS::get_reg().0,
162 | GDT.1.data_selector.0
163 | );
164 | serial_println!(
165 | "SS: {:#x} (expected: {:#x})",
166 | SS::get_reg().0,
167 | GDT.1.data_selector.0
168 | );
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/hash.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::{serial_println, verification::Hash};
18 | use lazy_static::lazy_static;
19 | use spin::Mutex;
20 | use x86_64::VirtAddr;
21 |
22 | lazy_static! {
23 | static ref CPU_FEATURES: Mutex = Mutex::new(CpuFeatures::detect());
24 | }
25 |
26 | struct CpuFeatures {
27 | has_rdrand: bool,
28 | has_sha: bool,
29 | }
30 |
31 | impl CpuFeatures {
32 | fn detect() -> Self {
33 | let has_rdrand = unsafe { core::arch::x86_64::__cpuid(1).ecx & (1 << 30) != 0 };
34 | let has_sha = unsafe { core::arch::x86_64::__cpuid(7).ebx & (1 << 29) != 0 };
35 | Self {
36 | has_rdrand,
37 | has_sha,
38 | }
39 | }
40 | }
41 |
42 | pub fn init() {
43 | let features = CPU_FEATURES.lock();
44 | serial_println!("RDRAND support: {}", features.has_rdrand);
45 | serial_println!("SHA extensions support: {}", features.has_sha);
46 | }
47 |
48 | fn try_hardware_hash(addr: VirtAddr, size: usize) -> Option {
49 | let features = CPU_FEATURES.lock();
50 | if features.has_sha {
51 | unsafe {
52 | let mut hash = 0u64;
53 | let ptr = addr.as_ptr::();
54 |
55 | core::arch::asm!(
56 | "movdqu xmm0, [{0}]",
57 | "sha256msg1 xmm0, xmm1",
58 | "sha256msg2 xmm0, xmm2",
59 | "sha256rnds2 xmm0, xmm3",
60 | in(reg) ptr,
61 | options(nostack, preserves_flags)
62 | );
63 |
64 | core::arch::asm!(
65 | "movq {0}, xmm0",
66 | out(reg) hash,
67 | options(nostack, preserves_flags)
68 | );
69 |
70 | Some(Hash(hash))
71 | }
72 | } else {
73 | None
74 | }
75 | }
76 |
77 | fn compute_fnv1a_hash(addr: VirtAddr, size: usize) -> Hash {
78 | const FNV_PRIME: u64 = 1099511628211;
79 | const FNV_OFFSET: u64 = 14695981039346656037;
80 |
81 | let mut hash = FNV_OFFSET;
82 |
83 | const CHUNK_SIZE: usize = 8;
84 | let chunks = size / CHUNK_SIZE;
85 | let remainder = size % CHUNK_SIZE;
86 |
87 | unsafe {
88 | let ptr = addr.as_ptr::();
89 |
90 | for i in 0..chunks {
91 | let chunk_ptr = ptr.add(i * CHUNK_SIZE) as *const u64;
92 | let chunk = *chunk_ptr;
93 | hash ^= chunk;
94 | hash = hash.wrapping_mul(FNV_PRIME);
95 | }
96 |
97 | for i in 0..remainder {
98 | let byte = *ptr.add(chunks * CHUNK_SIZE + i);
99 | hash ^= byte as u64;
100 | hash = hash.wrapping_mul(FNV_PRIME);
101 | }
102 | }
103 |
104 | Hash(hash)
105 | }
106 |
107 | pub fn hash_memory(addr: VirtAddr, size: usize) -> Hash {
108 | if let Some(hash) = try_hardware_hash(addr, size) {
109 | return hash;
110 | }
111 |
112 | compute_fnv1a_hash(addr, size)
113 | }
114 |
115 | pub fn combine_hashes(hashes: &[Hash]) -> Hash {
116 | if hashes.is_empty() {
117 | return Hash(0);
118 | }
119 |
120 | if hashes.len() == 1 {
121 | return hashes[0];
122 | }
123 |
124 | let mut combined = hashes[0].0;
125 |
126 | for &hash in &hashes[1..] {
127 | combined = combined.rotate_left(17) ^ hash.0;
128 | combined = combined.rotate_right(7) ^ (!hash.0);
129 | }
130 |
131 | combined ^= combined >> 32;
132 | combined = combined.wrapping_mul(0x9e3779b97f4a7c15);
133 |
134 | Hash(combined)
135 | }
136 |
137 | pub fn random_hash() -> Hash {
138 | let features = CPU_FEATURES.lock();
139 | if features.has_rdrand {
140 | if let Some(random) = unsafe { _rdrand64_step() } {
141 | return Hash(random);
142 | }
143 | }
144 |
145 | let tsc = crate::tsc::read_tsc();
146 | Hash(tsc.wrapping_mul(0x9e3779b97f4a7c15))
147 | }
148 |
149 | #[inline]
150 | unsafe fn _rdrand64_step() -> Option {
151 | let mut val: u64 = 0;
152 | if core::arch::x86_64::_rdrand64_step(&mut val) == 1 {
153 | Some(val)
154 | } else {
155 | None
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/hash_chain.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::{
18 | hash,
19 | verification::{Hash, VerificationError},
20 | vkfs::{DirEntry, Directory, Inode},
21 | };
22 | use alloc::vec::Vec;
23 | use x86_64::VirtAddr;
24 |
25 | #[derive(Debug)]
26 | pub struct HashChain {
27 | nodes: Vec,
28 | current_hash: Hash,
29 | }
30 |
31 | #[derive(Debug, Clone)]
32 | struct ChainNode {
33 | hash: Hash,
34 | prev_hash: Hash,
35 | node_type: NodeType,
36 | }
37 |
38 | #[derive(Debug, Clone)]
39 | enum NodeType {
40 | Directory(DirectoryNode),
41 | File(FileNode),
42 | Entry(EntryNode),
43 | }
44 |
45 | #[derive(Debug, Clone)]
46 | struct DirectoryNode {
47 | inode: u32,
48 | entries: Vec,
49 | }
50 |
51 | #[derive(Debug, Clone)]
52 | struct FileNode {
53 | inode: u32,
54 | blocks: Vec,
55 | }
56 |
57 | #[derive(Debug, Clone)]
58 | struct EntryNode {
59 | name: [u8; 255],
60 | name_len: u8,
61 | inode: u32,
62 | }
63 |
64 | impl HashChain {
65 | pub fn new() -> Self {
66 | Self {
67 | nodes: Vec::new(),
68 | current_hash: Hash(0),
69 | }
70 | }
71 |
72 | pub fn verify_chain(&self) -> Result {
73 | let mut current = Hash(0);
74 |
75 | for node in &self.nodes {
76 | if node.prev_hash != current {
77 | return Ok(false);
78 | }
79 |
80 | current = node.hash;
81 | }
82 |
83 | Ok(current == self.current_hash)
84 | }
85 |
86 | pub fn add_directory(&mut self, dir: &Directory) -> Result<(), VerificationError> {
87 | let mut entry_hashes = Vec::new();
88 |
89 | for entry in dir.get_entries() {
90 | let entry_hash = Self::hash_entry(entry);
91 | entry_hashes.push(entry_hash);
92 | }
93 |
94 | let node = ChainNode {
95 | prev_hash: self.current_hash,
96 | hash: hash::combine_hashes(&entry_hashes),
97 | node_type: NodeType::Directory(DirectoryNode {
98 | inode: dir.get_inode_number(),
99 | entries: entry_hashes,
100 | }),
101 | };
102 |
103 | let node_hash = node.hash;
104 | self.nodes.push(node);
105 | self.current_hash = node_hash;
106 | Ok(())
107 | }
108 |
109 | pub fn add_file(&mut self, inode: &Inode) -> Result<(), VerificationError> {
110 | let mut block_hashes = Vec::new();
111 |
112 | for &block in inode.get_direct_blocks() {
113 | if block != 0 {
114 | block_hashes.push(Hash(block as u64));
115 | }
116 | }
117 |
118 | if inode.get_indirect_block() != 0 {
119 | block_hashes.push(Hash(inode.get_indirect_block() as u64));
120 | }
121 |
122 | let node = ChainNode {
123 | prev_hash: self.current_hash,
124 | hash: hash::combine_hashes(&block_hashes),
125 | node_type: NodeType::File(FileNode {
126 | inode: inode
127 | .get_directory()
128 | .ok_or(VerificationError::InvalidState)?
129 | .get_inode_number(),
130 | blocks: block_hashes,
131 | }),
132 | };
133 |
134 | let node_hash = node.hash;
135 | self.nodes.push(node);
136 | self.current_hash = node_hash;
137 | Ok(())
138 | }
139 |
140 | fn hash_entry(entry: &DirEntry) -> Hash {
141 | let entry_data = unsafe {
142 | core::slice::from_raw_parts(
143 | entry as *const _ as *const u8,
144 | core::mem::size_of::(),
145 | )
146 | };
147 |
148 | hash::hash_memory(VirtAddr::new(entry_data.as_ptr() as u64), entry_data.len())
149 | }
150 |
151 | pub fn verify_directory(&self, dir: &Directory) -> Result {
152 | let mut entry_hashes = Vec::new();
153 | for entry in dir.get_entries() {
154 | entry_hashes.push(Self::hash_entry(entry));
155 | }
156 |
157 | let dir_hash = hash::combine_hashes(&entry_hashes);
158 |
159 | Ok(self.nodes.iter().any(|node| match &node.node_type {
160 | NodeType::Directory(dir_node) => {
161 | dir_node.inode == dir.get_inode_number() && node.hash == dir_hash
162 | }
163 | _ => false,
164 | }))
165 | }
166 |
167 | pub fn verify_file(&self, inode: &Inode) -> Result {
168 | let mut block_hashes = Vec::new();
169 | for &block in inode.get_direct_blocks() {
170 | if block != 0 {
171 | block_hashes.push(Hash(block as u64));
172 | }
173 | }
174 |
175 | let file_hash = hash::combine_hashes(&block_hashes);
176 |
177 | Ok(self.nodes.iter().any(|node| match &node.node_type {
178 | NodeType::File(file_node) => {
179 | if let Some(dir) = inode.get_directory() {
180 | file_node.inode == dir.get_inode_number() && node.hash == file_hash
181 | } else {
182 | false
183 | }
184 | }
185 | _ => false,
186 | }))
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/inode_cache.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::fs::FSOperation;
18 | use crate::hash;
19 | use crate::tsc;
20 | use crate::verification::{FSProof, Operation, ProofData};
21 | use crate::verification::{Hash, OperationProof, Verifiable, VerificationError};
22 | use crate::vkfs::Inode;
23 | use alloc::collections::BTreeMap;
24 | use alloc::string::String;
25 | use alloc::vec::Vec;
26 | use core::sync::atomic::{AtomicU64, Ordering};
27 | use x86_64::VirtAddr;
28 |
29 | const MAX_CACHE_ENTRIES: usize = 1024;
30 |
31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
32 | enum CacheEntryStatus {
33 | Clean,
34 | Dirty,
35 | InUse,
36 | }
37 |
38 | #[derive(Debug)]
39 | struct CacheEntry {
40 | inode: Inode,
41 | status: CacheEntryStatus,
42 | last_access: u64,
43 | reference_count: u32,
44 | }
45 |
46 | #[derive(Debug, Default)]
47 | pub struct CacheStats {
48 | hits: AtomicU64,
49 | misses: AtomicU64,
50 | evictions: AtomicU64,
51 | }
52 |
53 | #[derive(Debug)]
54 | pub struct InodeCache {
55 | entries: BTreeMap,
56 | access_counter: AtomicU64,
57 | max_entries: usize,
58 | stats: CacheStats,
59 | state_hash: AtomicU64,
60 | }
61 |
62 | impl InodeCache {
63 | pub fn new() -> Self {
64 | Self {
65 | entries: BTreeMap::new(),
66 | access_counter: AtomicU64::new(0),
67 | max_entries: MAX_CACHE_ENTRIES,
68 | stats: CacheStats::default(),
69 | state_hash: AtomicU64::new(0),
70 | }
71 | }
72 |
73 | pub fn get_inode(&mut self, inode_num: u32) -> Option<&mut Inode> {
74 | if let Some(entry) = self.entries.get_mut(&inode_num) {
75 | entry.last_access = self.access_counter.fetch_add(1, Ordering::SeqCst);
76 | entry.reference_count += 1;
77 | entry.status = CacheEntryStatus::InUse;
78 | entry.inode.access_time = crate::time::Timestamp::now().secs;
79 | self.stats.hits.fetch_add(1, Ordering::Relaxed);
80 | Some(&mut entry.inode)
81 | } else {
82 | self.stats.misses.fetch_add(1, Ordering::Relaxed);
83 | None
84 | }
85 | }
86 |
87 | pub fn touch_access_time(&mut self, inode_num: u32) {
88 | if let Some(entry) = self.entries.get_mut(&inode_num) {
89 | entry.inode.access_time = crate::time::Timestamp::now().secs;
90 | entry.status = CacheEntryStatus::Dirty;
91 | }
92 | }
93 |
94 | pub fn touch_modify_time(&mut self, inode_num: u32) {
95 | if let Some(entry) = self.entries.get_mut(&inode_num) {
96 | entry.inode.modify_time = crate::time::Timestamp::now().secs;
97 | entry.status = CacheEntryStatus::Dirty;
98 | }
99 | }
100 |
101 | pub fn insert_inode(&mut self, inode_num: u32, inode: Inode) {
102 | if self.entries.len() >= self.max_entries {
103 | self.evict_one();
104 | }
105 |
106 | let entry = CacheEntry {
107 | inode,
108 | status: CacheEntryStatus::Clean,
109 | last_access: self.access_counter.fetch_add(1, Ordering::SeqCst),
110 | reference_count: 1,
111 | };
112 |
113 | self.entries.insert(inode_num, entry);
114 | }
115 |
116 | pub fn mark_dirty(&mut self, inode_num: u32) {
117 | if let Some(entry) = self.entries.get_mut(&inode_num) {
118 | entry.status = CacheEntryStatus::Dirty;
119 | }
120 | }
121 |
122 | pub fn release_inode(&mut self, inode_num: u32) {
123 | if let Some(entry) = self.entries.get_mut(&inode_num) {
124 | entry.reference_count = entry.reference_count.saturating_sub(1);
125 | if entry.reference_count == 0 {
126 | entry.status = CacheEntryStatus::Clean;
127 | }
128 | }
129 | }
130 |
131 | pub fn evict_one(&mut self) {
132 | if let Some((&inode_num, _)) = self
133 | .entries
134 | .iter()
135 | .filter(|(_, entry)| entry.status == CacheEntryStatus::Clean)
136 | .min_by_key(|(_, entry)| (entry.reference_count, entry.last_access))
137 | {
138 | self.entries.remove(&inode_num);
139 | self.stats.evictions.fetch_add(1, Ordering::Relaxed);
140 | }
141 | }
142 |
143 | pub fn flush(&mut self) -> Vec<(u32, Inode)> {
144 | let mut dirty_inodes = Vec::new();
145 |
146 | self.entries.retain(|&inode_num, entry| {
147 | if entry.status == CacheEntryStatus::Dirty {
148 | dirty_inodes.push((inode_num, entry.inode.clone()));
149 | false
150 | } else {
151 | true
152 | }
153 | });
154 |
155 | dirty_inodes
156 | }
157 |
158 | pub fn get_stats(&self) -> (u64, u64, u64) {
159 | (
160 | self.stats.hits.load(Ordering::Relaxed),
161 | self.stats.misses.load(Ordering::Relaxed),
162 | self.stats.evictions.load(Ordering::Relaxed),
163 | )
164 | }
165 | }
166 |
167 | impl Verifiable for InodeCache {
168 | fn generate_proof(&self, operation: Operation) -> Result {
169 | let prev_state = Hash(self.state_hash.load(Ordering::SeqCst));
170 |
171 | let mut entry_hashes = Vec::new();
172 | for (inode_num, entry) in &self.entries {
173 | let mut hasher = [0u64; 512];
174 | hasher[0] = *inode_num as u64;
175 | hasher[1] = entry.last_access;
176 | hasher[2] = entry.reference_count as u64;
177 |
178 | entry_hashes.push(hash::hash_memory(
179 | VirtAddr::new(hasher.as_ptr() as u64),
180 | core::mem::size_of_val(&hasher),
181 | ));
182 | }
183 |
184 | let cache_hash = hash::combine_hashes(&entry_hashes);
185 | let new_state = Hash(prev_state.0 ^ cache_hash.0);
186 |
187 | Ok(OperationProof {
188 | op_id: tsc::read_tsc(),
189 | prev_state,
190 | new_state,
191 | data: ProofData::Filesystem(FSProof {
192 | operation: match operation {
193 | Operation::Filesystem { operation_type, .. } => operation_type,
194 | _ => return Err(VerificationError::InvalidOperation),
195 | },
196 | path: String::new(),
197 | content_hash: cache_hash,
198 | prev_state,
199 | new_state,
200 | op: FSOperation::Create {
201 | path: String::new(),
202 | },
203 | }),
204 | signature: [0; 64],
205 | })
206 | }
207 |
208 | fn verify_proof(&self, proof: &OperationProof) -> Result {
209 | let mut entry_hashes = Vec::new();
210 | for (inode_num, entry) in &self.entries {
211 | let mut hasher = [0u64; 512];
212 | hasher[0] = *inode_num as u64;
213 | hasher[1] = entry.last_access;
214 | hasher[2] = entry.reference_count as u64;
215 |
216 | entry_hashes.push(hash::hash_memory(
217 | VirtAddr::new(hasher.as_ptr() as u64),
218 | core::mem::size_of_val(&hasher),
219 | ));
220 | }
221 |
222 | let current_hash = hash::combine_hashes(&entry_hashes);
223 | Ok(current_hash == proof.new_state)
224 | }
225 |
226 | fn state_hash(&self) -> Hash {
227 | Hash(self.state_hash.load(Ordering::SeqCst))
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/key_store.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use alloc::vec::Vec;
18 | use spin::Mutex;
19 | use lazy_static::lazy_static;
20 | use crate::serial_println;
21 | use alloc::string::String;
22 | use crate::fs::{FILESYSTEM, FileSystem, FilePermissions};
23 |
24 | const KEY_STORE_PATH: &str = "/etc/vekos/keys";
25 | const VERIFICATION_KEY_PATH: &str = "/etc/vekos/keys/verification.key";
26 | const SIGNING_KEY_PATH: &str = "/etc/vekos/keys/signing.key";
27 |
28 | const VERIFICATION_KEY_LENGTH: usize = 64;
29 | const SIGNING_KEY_LENGTH: usize = 64;
30 |
31 | pub struct KeyStore {
32 | verification_key: [u8; VERIFICATION_KEY_LENGTH],
33 | signing_key: [u8; SIGNING_KEY_LENGTH],
34 | initialized: bool,
35 | }
36 |
37 | impl KeyStore {
38 | pub fn new() -> Self {
39 | Self {
40 | verification_key: [0u8; VERIFICATION_KEY_LENGTH],
41 | signing_key: [0u8; SIGNING_KEY_LENGTH],
42 | initialized: false,
43 | }
44 | }
45 |
46 | pub fn init_early_boot(&mut self) -> Result<(), &'static str> {
47 | serial_println!("Initializing KeyStore (early boot mode)");
48 |
49 | self.generate_keys()?;
50 | self.initialized = true;
51 | serial_println!("Generated temporary verification keys for early boot");
52 |
53 | Ok(())
54 | }
55 |
56 | pub fn init(&mut self) -> Result<(), &'static str> {
57 | serial_println!("Initializing key management subsystem");
58 |
59 | match self.ensure_key_directory() {
60 | Ok(_) => serial_println!("Key directories verified/created successfully"),
61 | Err(e) => serial_println!("Warning: Could not create key directories: {}", e),
62 | }
63 |
64 | if self.initialized {
65 | if let Err(e) = self.save_keys() {
66 | serial_println!("Note: Could not save existing keys: {}", e);
67 | } else {
68 | serial_println!("Existing keys saved to filesystem");
69 | }
70 | return Ok(());
71 | }
72 |
73 | match self.load_keys() {
74 | Ok(_) => {
75 | serial_println!("Successfully loaded existing keys");
76 | self.initialized = true;
77 | return Ok(());
78 | },
79 | Err(e) => {
80 | serial_println!("Could not load existing keys: {}", e);
81 | serial_println!("Generating new keypair");
82 |
83 | self.generate_keys()?;
84 |
85 | if let Err(e) = self.save_keys() {
86 | serial_println!("Note: Could not save keys: {}", e);
87 | serial_println!("Keys will remain in memory only");
88 | } else {
89 | serial_println!("Keys saved to filesystem");
90 | }
91 |
92 | self.initialized = true;
93 | serial_println!("New keys generated successfully");
94 | Ok(())
95 | }
96 | }
97 | }
98 |
99 | fn ensure_key_directory(&self) -> Result<(), &'static str> {
100 | let mut fs = FILESYSTEM.lock();
101 |
102 | for dir in &["/etc", "/etc/vekos", "/etc/vekos/keys"] {
103 | let permissions = FilePermissions {
104 | read: true,
105 | write: true,
106 | execute: true,
107 | };
108 |
109 | match fs.stat(dir) {
110 | Ok(_) => {
111 | serial_println!("Directory exists: {}", dir);
112 | },
113 | Err(_) => {
114 | match fs.create_directory(dir, permissions) {
115 | Ok(_) => serial_println!("Created directory: {}", dir),
116 | Err(e) => {
117 | serial_println!("Failed to create directory {}: {:?}", dir, e);
118 | return Err("Failed to create key directory");
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | Ok(())
126 | }
127 |
128 | fn load_keys(&mut self) -> Result<(), &'static str> {
129 | let mut fs = FILESYSTEM.lock();
130 |
131 | match fs.read_file(VERIFICATION_KEY_PATH) {
132 | Ok(data) => {
133 | if data.len() != VERIFICATION_KEY_LENGTH {
134 | return Err("Invalid verification key size");
135 | }
136 | self.verification_key.copy_from_slice(&data);
137 | },
138 | Err(_) => return Err("Failed to read verification key"),
139 | }
140 |
141 | match fs.read_file(SIGNING_KEY_PATH) {
142 | Ok(data) => {
143 | if data.len() != SIGNING_KEY_LENGTH {
144 | return Err("Invalid signing key size");
145 | }
146 | self.signing_key.copy_from_slice(&data);
147 | },
148 | Err(_) => return Err("Failed to read signing key"),
149 | }
150 |
151 | Ok(())
152 | }
153 |
154 | fn save_keys(&self) -> Result<(), &'static str> {
155 | let mut fs = FILESYSTEM.lock();
156 |
157 | let read_only = FilePermissions {
158 | read: true,
159 | write: false,
160 | execute: false,
161 | };
162 |
163 | match fs.create_file(VERIFICATION_KEY_PATH, read_only) {
164 | Ok(_) => match fs.write_file(VERIFICATION_KEY_PATH, &self.verification_key) {
165 | Ok(_) => (),
166 | Err(_) => return Err("Failed to write verification key data"),
167 | },
168 | Err(_) => return Err("Failed to create verification key file"),
169 | }
170 |
171 | match fs.create_file(SIGNING_KEY_PATH, read_only) {
172 | Ok(_) => match fs.write_file(SIGNING_KEY_PATH, &self.signing_key) {
173 | Ok(_) => (),
174 | Err(_) => return Err("Failed to write signing key data"),
175 | },
176 | Err(_) => return Err("Failed to create signing key file"),
177 | }
178 |
179 | Ok(())
180 | }
181 |
182 | fn generate_keys(&mut self) -> Result<(), &'static str> {
183 | let mut verifier = crate::crypto::CRYPTO_VERIFIER.lock();
184 |
185 | match verifier.generate_new_keypair() {
186 | Ok(private_key) => {
187 | self.signing_key.copy_from_slice(&private_key);
188 | self.verification_key = verifier.get_verification_key()?;
189 |
190 | Ok(())
191 | },
192 | Err(e) => Err(e),
193 | }
194 | }
195 |
196 | pub fn get_verification_key(&self) -> Result<[u8; VERIFICATION_KEY_LENGTH], &'static str> {
197 | if !self.initialized {
198 | return Err("KeyStore not initialized");
199 | }
200 |
201 | Ok(self.verification_key)
202 | }
203 |
204 | pub fn get_signing_key(&self) -> Result<[u8; SIGNING_KEY_LENGTH], &'static str> {
205 | if !self.initialized {
206 | return Err("KeyStore not initialized");
207 | }
208 |
209 | Ok(self.signing_key)
210 | }
211 |
212 | pub fn sign_data(&self, data: &[u8]) -> Result<[u8; SIGNING_KEY_LENGTH], &'static str> {
213 | if !self.initialized {
214 | return Err("KeyStore not initialized");
215 | }
216 |
217 | let verifier = crate::crypto::CRYPTO_VERIFIER.lock();
218 | verifier.sign_data(data, &self.signing_key)
219 | }
220 | }
221 |
222 | lazy_static! {
223 | pub static ref KEY_STORE: Mutex = Mutex::new(KeyStore::new());
224 | }
225 |
226 | pub fn init() -> Result<(), &'static str> {
227 | serial_println!("Initializing key management subsystem");
228 | let mut key_store = KEY_STORE.lock();
229 | key_store.init()
230 | }
--------------------------------------------------------------------------------
/src/merkle_tree.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::hash;
18 | use crate::hash_chain::HashChain;
19 | use crate::verification::Hash;
20 | use crate::verification::VerificationError;
21 | use crate::vkfs::{DirEntry, Directory, Inode};
22 | use alloc::string::String;
23 | use alloc::vec::Vec;
24 | use x86_64::VirtAddr;
25 |
26 | #[derive(Debug)]
27 | pub struct MerkleNode {
28 | hash: Hash,
29 | children: Vec,
30 | }
31 |
32 | impl MerkleNode {
33 | pub fn new(hash: Hash) -> Self {
34 | Self {
35 | hash,
36 | children: Vec::new(),
37 | }
38 | }
39 |
40 | pub fn add_child(&mut self, node: MerkleNode) {
41 | self.children.push(node);
42 | self.update_hash();
43 | }
44 |
45 | pub fn update_hash(&mut self) {
46 | let mut child_hashes: Vec = self.children.iter().map(|child| child.hash).collect();
47 |
48 | child_hashes.push(self.hash);
49 |
50 | self.hash = hash::combine_hashes(&child_hashes);
51 | }
52 | }
53 |
54 | #[derive(Debug, Clone)]
55 | pub struct DirectoryMerkleTree {
56 | root: MerkleNode,
57 | }
58 |
59 | #[derive(Debug)]
60 | pub struct ConsistencyChecker {
61 | prev_root: Hash,
62 | current_root: Hash,
63 | verification_proofs: Vec,
64 | }
65 |
66 | #[derive(Debug)]
67 | struct MerkleProof {
68 | path: Vec,
69 | leaf_hash: Hash,
70 | index: usize,
71 | }
72 |
73 | #[derive(Debug)]
74 | pub struct HashChainVerifier {
75 | chain: HashChain,
76 | }
77 |
78 | impl ConsistencyChecker {
79 | pub fn new(prev_root: Hash, current_root: Hash) -> Self {
80 | Self {
81 | prev_root,
82 | current_root,
83 | verification_proofs: Vec::new(),
84 | }
85 | }
86 |
87 | pub fn verify_transition(
88 | &self,
89 | dir: &Directory,
90 | inodes: &[Option],
91 | ) -> Result {
92 | let mut tree = DirectoryMerkleTree::new(dir);
93 | tree.build_tree(dir, inodes)?;
94 |
95 | if tree.root_hash() != self.current_root {
96 | return Ok(false);
97 | }
98 |
99 | for proof in &self.verification_proofs {
100 | if !self.verify_proof(proof, dir)? {
101 | return Ok(false);
102 | }
103 | }
104 |
105 | Ok(true)
106 | }
107 |
108 | fn verify_proof(
109 | &self,
110 | proof: &MerkleProof,
111 | dir: &Directory,
112 | ) -> Result {
113 | let mut current_hash = proof.leaf_hash;
114 | let mut index = proof.index;
115 |
116 | for sibling in &proof.path {
117 | current_hash = if index % 2 == 0 {
118 | hash::combine_hashes(&[current_hash, *sibling])
119 | } else {
120 | hash::combine_hashes(&[*sibling, current_hash])
121 | };
122 | index /= 2;
123 | }
124 |
125 | Ok(current_hash == self.prev_root)
126 | }
127 |
128 | pub fn add_proof(&mut self, proof: MerkleProof) {
129 | self.verification_proofs.push(proof);
130 | }
131 |
132 | pub fn clear_proofs(&mut self) {
133 | self.verification_proofs.clear();
134 | }
135 | }
136 |
137 | impl HashChainVerifier {
138 | pub fn new() -> Self {
139 | Self {
140 | chain: HashChain::new(),
141 | }
142 | }
143 |
144 | pub fn verify_directory_chain(&mut self, dir: &Directory) -> Result {
145 | self.chain.add_directory(dir)?;
146 | self.chain.verify_chain()
147 | }
148 |
149 | pub fn verify_file_chain(&mut self, inode: &Inode) -> Result {
150 | self.chain.add_file(inode)?;
151 | self.chain.verify_chain()
152 | }
153 | }
154 |
155 | impl DirectoryMerkleTree {
156 | pub fn new(root_dir: &Directory) -> Self {
157 | let root_hash = Self::compute_directory_hash(root_dir);
158 | Self {
159 | root: MerkleNode::new(root_hash),
160 | }
161 | }
162 |
163 | pub fn build_tree(
164 | &mut self,
165 | root_dir: &Directory,
166 | inodes: &[Option],
167 | ) -> Result<(), VerificationError> {
168 | self.root = self.build_node(root_dir, inodes)?;
169 | Ok(())
170 | }
171 |
172 | pub fn update_node(
173 | &mut self,
174 | dir: &Directory,
175 | inodes: &[Option],
176 | ) -> Result<(), VerificationError> {
177 | let updated_node = self.build_node(dir, inodes)?;
178 | self.root = updated_node;
179 | Ok(())
180 | }
181 |
182 | pub fn update_tree(
183 | &mut self,
184 | dir: &Directory,
185 | inodes: &[Option],
186 | ) -> Result {
187 | self.update_node(dir, inodes)?;
188 | Ok(self.root_hash())
189 | }
190 |
191 | pub fn rebuild_branch(
192 | &mut self,
193 | path: &[String],
194 | dir: &Directory,
195 | inodes: &[Option],
196 | ) -> Result<(), VerificationError> {
197 | if path.is_empty() {
198 | return self.update_node(dir, inodes);
199 | }
200 |
201 | let current = &path[0];
202 | let remaining = &path[1..];
203 |
204 | if let Some(entry) = dir.get_entry(current) {
205 | if let Some(Some(inode)) = inodes.get(entry.get_inode_number() as usize) {
206 | if let Some(ref child_dir) = inode.get_directory() {
207 | self.rebuild_branch(remaining, child_dir, inodes)?;
208 | }
209 | }
210 | }
211 |
212 | self.update_node(dir, inodes)
213 | }
214 |
215 | pub fn verify_consistency(
216 | &self,
217 | dir: &Directory,
218 | inodes: &[Option],
219 | ) -> Result {
220 | let mut current_tree = DirectoryMerkleTree::new(dir);
221 | current_tree.build_tree(dir, inodes)?;
222 |
223 | if self.root_hash() != current_tree.root_hash() {
224 | return Ok(false);
225 | }
226 |
227 | for entry in dir.get_entries() {
228 | if !entry.verify() {
229 | return Ok(false);
230 | }
231 |
232 | if let Some(Some(inode)) = inodes.get(entry.get_inode_number() as usize) {
233 | if let Some(ref child_dir) = inode.get_directory() {
234 | current_tree.build_tree(child_dir, inodes)?;
235 | if !self.verify_node_consistency(child_dir, inodes)? {
236 | return Ok(false);
237 | }
238 | }
239 | }
240 | }
241 |
242 | Ok(true)
243 | }
244 |
245 | fn verify_node_consistency(
246 | &self,
247 | dir: &Directory,
248 | inodes: &[Option],
249 | ) -> Result {
250 | let node_hash = self.compute_node_hash(dir);
251 |
252 | let mut entry_hashes = Vec::new();
253 | for entry in dir.get_entries() {
254 | entry_hashes.push(Self::compute_entry_hash(entry));
255 | }
256 |
257 | let computed_hash = hash::combine_hashes(&entry_hashes);
258 | if computed_hash != node_hash {
259 | return Ok(false);
260 | }
261 |
262 | Ok(true)
263 | }
264 |
265 | fn compute_node_hash(&self, dir: &Directory) -> Hash {
266 | let mut hasher = [0u64; 512];
267 | hasher[0] = dir.get_inode_number() as u64;
268 | hasher[1] = dir.get_parent_inode() as u64;
269 |
270 | hash::hash_memory(
271 | VirtAddr::new(hasher.as_ptr() as u64),
272 | core::mem::size_of_val(&hasher),
273 | )
274 | }
275 |
276 | fn build_node(
277 | &self,
278 | dir: &Directory,
279 | inodes: &[Option],
280 | ) -> Result {
281 | let mut node = MerkleNode::new(Self::compute_directory_hash(dir));
282 |
283 | for entry in dir.get_entries() {
284 | if let Some(Some(inode)) = inodes.get(entry.get_inode_number() as usize) {
285 | if let Some(ref child_dir) = inode.get_directory() {
286 | let child_node = self.build_node(child_dir, inodes)?;
287 | node.add_child(child_node);
288 | } else {
289 | let file_hash = Self::compute_file_hash(inode);
290 | node.add_child(MerkleNode::new(file_hash));
291 | }
292 | }
293 | }
294 |
295 | Ok(node)
296 | }
297 |
298 | pub fn compute_directory_hash(dir: &Directory) -> Hash {
299 | let mut entry_hashes = Vec::new();
300 |
301 | for entry in dir.get_entries() {
302 | entry_hashes.push(Self::compute_entry_hash(entry));
303 | }
304 |
305 | let metadata = [
306 | dir.get_inode_number().to_ne_bytes(),
307 | dir.get_parent_inode().to_ne_bytes(),
308 | ]
309 | .concat();
310 |
311 | let metadata_hash =
312 | hash::hash_memory(VirtAddr::new(metadata.as_ptr() as u64), metadata.len());
313 |
314 | entry_hashes.push(metadata_hash);
315 | hash::combine_hashes(&entry_hashes)
316 | }
317 |
318 | fn compute_entry_hash(entry: &DirEntry) -> Hash {
319 | let entry_data = unsafe {
320 | core::slice::from_raw_parts(
321 | entry as *const _ as *const u8,
322 | core::mem::size_of::(),
323 | )
324 | };
325 |
326 | hash::hash_memory(VirtAddr::new(entry_data.as_ptr() as u64), entry_data.len())
327 | }
328 |
329 | fn compute_file_hash(inode: &Inode) -> Hash {
330 | let mut block_hashes = Vec::new();
331 |
332 | for &block in inode.get_direct_blocks() {
333 | if block != 0 {
334 | block_hashes.push(Hash(block as u64));
335 | }
336 | }
337 |
338 | if inode.get_indirect_block() != 0 {
339 | block_hashes.push(Hash(inode.get_indirect_block() as u64));
340 | }
341 |
342 | if inode.get_double_indirect() != 0 {
343 | block_hashes.push(Hash(inode.get_double_indirect() as u64));
344 | }
345 |
346 | hash::combine_hashes(&block_hashes)
347 | }
348 |
349 | pub fn verify(
350 | &self,
351 | dir: &Directory,
352 | inodes: &[Option],
353 | ) -> Result {
354 | let computed_node = self.build_node(dir, inodes)?;
355 | Ok(computed_node.hash == self.root.hash)
356 | }
357 |
358 | pub fn root_hash(&self) -> Hash {
359 | self.root.hash
360 | }
361 | }
362 |
363 | impl Clone for MerkleNode {
364 | fn clone(&self) -> Self {
365 | Self {
366 | hash: self.hash,
367 | children: self.children.clone(),
368 | }
369 | }
370 | }
371 |
372 | pub fn verify_directory_tree(
373 | root_dir: &Directory,
374 | inodes: &[Option],
375 | ) -> Result {
376 | let mut tree = DirectoryMerkleTree::new(root_dir);
377 | tree.build_tree(root_dir, inodes)?;
378 | Ok(tree.root_hash())
379 | }
380 |
--------------------------------------------------------------------------------
/src/operation_proofs.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::{
18 | hash, tsc,
19 | verification::{Hash, OperationProof, Verifiable, VerificationError},
20 | vkfs::Superblock,
21 | };
22 | use x86_64::VirtAddr;
23 | use crate::verification::FSOpType;
24 | use crate::verification::ProofData;
25 | use alloc::vec::Vec;
26 |
27 | #[derive(Debug, Clone)]
28 | pub struct BlockOperationProof {
29 | pub block_num: u64,
30 | pub block_hash: Hash,
31 | pub prev_state: Hash,
32 | pub new_state: Hash,
33 | pub timestamp: u64,
34 | }
35 |
36 | impl BlockOperationProof {
37 | pub fn new(block_num: u64, block_data: &[u8], prev_state: Hash) -> Self {
38 | let block_hash =
39 | hash::hash_memory(VirtAddr::new(block_data.as_ptr() as u64), block_data.len());
40 | let new_state = Hash(prev_state.0 ^ block_hash.0);
41 |
42 | Self {
43 | block_num,
44 | block_hash,
45 | prev_state,
46 | new_state,
47 | timestamp: tsc::read_tsc(),
48 | }
49 | }
50 |
51 | pub fn verify(&self, block_data: &[u8]) -> bool {
52 | let current_hash =
53 | hash::hash_memory(VirtAddr::new(block_data.as_ptr() as u64), block_data.len());
54 | current_hash == self.block_hash
55 | }
56 | }
57 |
58 | pub trait ProofVerifier {
59 | fn verify_operation_proof(&self, proof: &OperationProof) -> Result;
60 | fn verify_block_proof(&self, proof: &BlockOperationProof) -> Result;
61 | fn generate_block_proof(
62 | &self,
63 | block_num: u64,
64 | ) -> Result;
65 | fn verify_hash_chain(&self, proof: &OperationProof) -> Result;
66 | }
67 |
68 | impl ProofVerifier for Superblock {
69 | fn verify_operation_proof(&self, proof: &OperationProof) -> Result {
70 | let current_state = self.state_hash();
71 | if proof.prev_state != current_state {
72 | return Ok(false);
73 | }
74 |
75 | if !verify_signature(proof) {
76 | return Ok(false);
77 | }
78 |
79 | match &proof.data {
80 | ProofData::Filesystem(fs_proof) => {
81 | let computed_hash = match fs_proof.operation {
82 | FSOpType::Create | FSOpType::Modify => hash::hash_memory(
83 | VirtAddr::new(fs_proof.path.as_ptr() as u64),
84 | fs_proof.path.len(),
85 | ),
86 | FSOpType::Delete => Hash(!current_state.0),
87 | };
88 |
89 | if computed_hash != fs_proof.content_hash {
90 | return Ok(false);
91 | }
92 |
93 | let expected_state = Hash(proof.prev_state.0 ^ computed_hash.0);
94 | if expected_state != proof.new_state {
95 | return Ok(false);
96 | }
97 |
98 | Ok(true)
99 | }
100 | _ => Err(VerificationError::InvalidProof),
101 | }
102 | }
103 |
104 | fn verify_hash_chain(&self, proof: &OperationProof) -> Result {
105 | match &proof.data {
106 | ProofData::Filesystem(fs_proof) => {
107 | let current_hash = fs_proof.prev_state;
108 |
109 | let operation_hash = match fs_proof.operation {
110 | FSOpType::Create | FSOpType::Modify => fs_proof.content_hash,
111 | FSOpType::Delete => Hash(!current_hash.0),
112 | };
113 |
114 | let expected_hash = Hash(current_hash.0 ^ operation_hash.0);
115 | if expected_hash != fs_proof.new_state {
116 | return Ok(false);
117 | }
118 |
119 | Ok(true)
120 | }
121 | _ => Err(VerificationError::InvalidProof),
122 | }
123 | }
124 |
125 | fn verify_block_proof(&self, proof: &BlockOperationProof) -> Result {
126 | let block_data = self
127 | .block_cache
128 | .lock()
129 | .get_block(proof.block_num)
130 | .ok_or(VerificationError::InvalidState)?;
131 |
132 | if !proof.verify(&block_data) {
133 | return Ok(false);
134 | }
135 |
136 | let current_state = self.state_hash();
137 | if proof.prev_state != current_state {
138 | return Ok(false);
139 | }
140 |
141 | let expected_state = Hash(current_state.0 ^ proof.block_hash.0);
142 | if expected_state != proof.new_state {
143 | return Ok(false);
144 | }
145 |
146 | Ok(true)
147 | }
148 |
149 | fn generate_block_proof(
150 | &self,
151 | block_num: u64,
152 | ) -> Result {
153 | let block_data = self
154 | .block_cache
155 | .lock()
156 | .get_block(block_num)
157 | .ok_or(VerificationError::InvalidState)?;
158 |
159 | Ok(BlockOperationProof::new(
160 | block_num,
161 | &block_data,
162 | self.state_hash(),
163 | ))
164 | }
165 | }
166 |
167 | fn verify_signature(proof: &OperationProof) -> bool {
168 | let mut verification_data = Vec::new();
169 | verification_data.extend_from_slice(&proof.op_id.to_ne_bytes());
170 | verification_data.extend_from_slice(&proof.prev_state.0.to_ne_bytes());
171 | verification_data.extend_from_slice(&proof.new_state.0.to_ne_bytes());
172 |
173 | match &proof.data {
174 | ProofData::Memory(mem_proof) => {
175 | verification_data.extend_from_slice(&[0]);
176 | verification_data.extend_from_slice(&(mem_proof.address.as_u64().to_ne_bytes()));
177 | verification_data.extend_from_slice(&(mem_proof.size.to_ne_bytes()));
178 | verification_data.extend_from_slice(&(mem_proof.frame_hash.0.to_ne_bytes()));
179 | },
180 | ProofData::Filesystem(fs_proof) => {
181 | verification_data.extend_from_slice(&[1]);
182 | verification_data.extend_from_slice(fs_proof.path.as_bytes());
183 | verification_data.extend_from_slice(&fs_proof.content_hash.0.to_ne_bytes());
184 | },
185 | ProofData::Process(proc_proof) => {
186 | verification_data.extend_from_slice(&[2]);
187 | verification_data.extend_from_slice(&proc_proof.pid.to_ne_bytes());
188 | verification_data.extend_from_slice(&proc_proof.state_hash.0.to_ne_bytes());
189 | },
190 | ProofData::Boot(boot_proof) => {
191 | verification_data.extend_from_slice(&[3]);
192 | verification_data.extend_from_slice(&boot_proof.stage_hash.0.to_ne_bytes());
193 | },
194 | ProofData::Tile(tile_proof) => {
195 | verification_data.extend_from_slice(&[4]);
196 | verification_data.extend_from_slice(&tile_proof.tile_id.to_ne_bytes());
197 | verification_data.extend_from_slice(&(tile_proof.position.0.to_ne_bytes()));
198 | verification_data.extend_from_slice(&(tile_proof.position.1.to_ne_bytes()));
199 | verification_data.extend_from_slice(&tile_proof.tile_hash.0.to_ne_bytes());
200 | },
201 | ProofData::Generic { operation_type, data_hash } => {
202 | verification_data.extend_from_slice(&[5]);
203 | verification_data.extend_from_slice(operation_type.as_bytes());
204 | verification_data.extend_from_slice(&data_hash.0.to_ne_bytes());
205 | },
206 | }
207 |
208 | let verifier = crate::crypto::CRYPTO_VERIFIER.lock();
209 | verifier.verify_signature(&verification_data, &proof.signature)
210 | }
211 |
--------------------------------------------------------------------------------
/src/page_table.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::{
18 | hash,
19 | verification::{Hash, OperationProof, Verifiable, VerificationError},
20 | };
21 | use alloc::vec::Vec;
22 | use core::sync::atomic::{AtomicU64, Ordering};
23 | use spin::Mutex;
24 | use x86_64::{
25 | structures::paging::{PageTable, PageTableFlags},
26 | VirtAddr,
27 | };
28 |
29 | pub struct PageTableVerifier {
30 | current_hash: AtomicU64,
31 | level_hashes: [Mutex; 4],
32 | }
33 |
34 | impl PageTableVerifier {
35 | pub fn new() -> Self {
36 | Self {
37 | current_hash: AtomicU64::new(0),
38 | level_hashes: [
39 | Mutex::new(Hash(0)),
40 | Mutex::new(Hash(0)),
41 | Mutex::new(Hash(0)),
42 | Mutex::new(Hash(0)),
43 | ],
44 | }
45 | }
46 |
47 | pub fn hash_level(&self, table: &PageTable, level: usize) -> Result {
48 | let mut hasher = [0u64; 512];
49 |
50 | for (i, entry) in table.iter().enumerate() {
51 | let flags = entry.flags();
52 | let addr = entry.addr().as_u64();
53 |
54 | hasher[i] = flags.bits() ^ (addr.rotate_left(17));
55 | }
56 |
57 | let level_hash = hash::hash_memory(
58 | VirtAddr::new(hasher.as_ptr() as u64),
59 | core::mem::size_of_val(&hasher),
60 | );
61 |
62 | *self.level_hashes[level].lock() = level_hash;
63 |
64 | Ok(level_hash)
65 | }
66 |
67 | pub fn verify_level(&self, table: &PageTable, level: usize) -> Result {
68 | let current = self.hash_level(table, level)?;
69 | let previous = *self.level_hashes[level].lock();
70 |
71 | if current != previous {
72 | return Ok(false);
73 | }
74 |
75 | Ok(true)
76 | }
77 |
78 | pub fn hash_hierarchy(&self, root: &PageTable) -> Result {
79 | let mut level_hashes = Vec::with_capacity(4);
80 |
81 | let mut current = root;
82 | for level in 0..4 {
83 | let hash = self.hash_level(current, level)?;
84 | level_hashes.push(hash);
85 |
86 | if level < 3 {
87 | if let Some(entry) = current
88 | .iter()
89 | .find(|e| e.flags().contains(PageTableFlags::PRESENT))
90 | {
91 | let phys = entry.addr();
92 | let virt = VirtAddr::new(phys.as_u64());
93 | current = unsafe { &*(virt.as_ptr()) };
94 | }
95 | }
96 | }
97 |
98 | let combined = hash::combine_hashes(&level_hashes);
99 | self.current_hash.store(combined.0, Ordering::SeqCst);
100 |
101 | Ok(combined)
102 | }
103 |
104 | pub fn verify_hierarchy(&self, root: &PageTable) -> Result {
105 | let current = self.hash_hierarchy(root)?;
106 | let stored = Hash(self.current_hash.load(Ordering::SeqCst));
107 |
108 | if current != stored {
109 | return Ok(false);
110 | }
111 |
112 | let mut current_table = root;
113 | for level in 0..4 {
114 | if !self.verify_level(current_table, level)? {
115 | return Ok(false);
116 | }
117 |
118 | if level < 3 {
119 | if let Some(entry) = current_table
120 | .iter()
121 | .find(|e| e.flags().contains(PageTableFlags::PRESENT))
122 | {
123 | let phys = entry.addr();
124 | let virt = VirtAddr::new(phys.as_u64());
125 | current_table = unsafe { &*(virt.as_ptr()) };
126 | }
127 | }
128 | }
129 |
130 | Ok(true)
131 | }
132 | }
133 |
134 | impl Verifiable for PageTableVerifier {
135 | fn generate_proof(
136 | &self,
137 | operation: crate::verification::Operation,
138 | ) -> Result {
139 | unimplemented!()
140 | }
141 |
142 | fn verify_proof(&self, proof: &OperationProof) -> Result {
143 | unimplemented!()
144 | }
145 |
146 | fn state_hash(&self) -> Hash {
147 | Hash(self.current_hash.load(Ordering::SeqCst))
148 | }
149 | }
150 |
151 | #[cfg(test)]
152 | mod tests {
153 | use super::*;
154 | use x86_64::structures::paging::PageTableFlags;
155 |
156 | #[test_case]
157 | fn test_page_table_hashing() {
158 | let verifier = PageTableVerifier::new();
159 | let mut table = PageTable::new();
160 |
161 | table[0].set_addr(PhysAddr::new(0x1000), PageTableFlags::PRESENT);
162 | table[1].set_addr(
163 | PhysAddr::new(0x2000),
164 | PageTableFlags::PRESENT | PageTableFlags::WRITABLE,
165 | );
166 |
167 | assert!(verifier.hash_level(&table, 0).is_ok());
168 |
169 | table[0].set_addr(PhysAddr::new(0x3000), PageTableFlags::PRESENT);
170 |
171 | let new_hash = verifier.hash_level(&table, 0).unwrap();
172 | assert_ne!(new_hash, *verifier.level_hashes[0].lock());
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/page_table_cache.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::memory::SWAPPED_PAGES;
18 | use crate::serial_println;
19 | use crate::swap::DISK_IO;
20 | use crate::VirtAddr;
21 | use alloc::collections::BTreeMap;
22 | use core::sync::atomic::{AtomicU64, Ordering};
23 | use x86_64::{
24 | structures::paging::{PageTable, PhysFrame},
25 | PhysAddr,
26 | };
27 |
28 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
29 | enum CacheEntryStatus {
30 | Clean,
31 | Dirty,
32 | }
33 |
34 | #[derive(Debug)]
35 | struct CacheEntry {
36 | frame: PhysFrame,
37 | status: CacheEntryStatus,
38 | last_access: u64,
39 | reference_count: u32,
40 | }
41 |
42 | pub struct PageTableCache {
43 | entries: BTreeMap,
44 | access_counter: AtomicU64,
45 | max_entries: usize,
46 | stats: CacheStats,
47 | }
48 |
49 | #[derive(Debug, Default)]
50 | pub struct CacheStats {
51 | hits: AtomicU64,
52 | misses: AtomicU64,
53 | evictions: AtomicU64,
54 | }
55 |
56 | impl PageTableCache {
57 | pub fn new(max_entries: usize) -> Self {
58 | Self {
59 | entries: BTreeMap::new(),
60 | access_counter: AtomicU64::new(0),
61 | max_entries,
62 | stats: CacheStats::default(),
63 | }
64 | }
65 |
66 | pub fn release_page_table(&mut self, frame: PhysFrame) {
67 | if let Some(entry) = self.entries.remove(&frame.start_address()) {
68 | if entry.status == CacheEntryStatus::Dirty {
69 | unsafe {
70 | self.flush_page_table(frame.start_address());
71 | }
72 | }
73 | self.stats.evictions.fetch_add(1, Ordering::Relaxed);
74 | }
75 | }
76 |
77 | pub fn get_or_insert_page_table(&mut self, frame: PhysFrame) -> &mut PageTable {
78 | let phys_addr = frame.start_address();
79 |
80 | if let Some(entry) = self.entries.get_mut(&phys_addr) {
81 | entry.last_access = self.access_counter.fetch_add(1, Ordering::SeqCst);
82 | entry.reference_count += 1;
83 | self.stats.hits.fetch_add(1, Ordering::Relaxed);
84 | unsafe {
85 | return &mut *(phys_addr.as_u64() as *mut PageTable);
86 | }
87 | }
88 |
89 | if self.entries.len() >= self.max_entries {
90 | self.evict_one();
91 | }
92 |
93 | let entry = CacheEntry {
94 | frame,
95 | status: CacheEntryStatus::Clean,
96 | last_access: self.access_counter.fetch_add(1, Ordering::SeqCst),
97 | reference_count: 1,
98 | };
99 |
100 | self.entries.insert(phys_addr, entry);
101 | self.stats.misses.fetch_add(1, Ordering::Relaxed);
102 |
103 | unsafe { &mut *(phys_addr.as_u64() as *mut PageTable) }
104 | }
105 |
106 | pub fn get_page_table(&mut self, frame: PhysFrame) -> Option<&mut PageTable> {
107 | Some(self.get_or_insert_page_table(frame))
108 | }
109 |
110 | pub fn insert_page_table(&mut self, frame: PhysFrame, table: &PageTable) {
111 | let phys_addr = frame.start_address();
112 |
113 | if !self.entries.contains_key(&phys_addr) && self.entries.len() >= self.max_entries {
114 | self.evict_one();
115 | }
116 |
117 | let entry = CacheEntry {
118 | frame,
119 | status: CacheEntryStatus::Clean,
120 | last_access: self.access_counter.fetch_add(1, Ordering::SeqCst),
121 | reference_count: 1,
122 | };
123 |
124 | unsafe {
125 | let dest = phys_addr.as_u64() as *mut PageTable;
126 | core::ptr::copy_nonoverlapping(table as *const PageTable, dest, 1);
127 | }
128 |
129 | self.entries.insert(phys_addr, entry);
130 | }
131 |
132 | pub fn evict_one(&mut self) {
133 | if let Some((&addr, entry)) = self
134 | .entries
135 | .iter()
136 | .min_by_key(|(_, entry)| entry.last_access)
137 | {
138 | let swapped_pages = SWAPPED_PAGES.lock();
139 | if swapped_pages.contains_key(&VirtAddr::new(addr.as_u64())) {
140 | return;
141 | }
142 |
143 | if entry.status == CacheEntryStatus::Dirty {
144 | unsafe {
145 | self.flush_page_table(addr);
146 | }
147 | }
148 |
149 | self.entries.remove(&addr);
150 | self.stats.evictions.fetch_add(1, Ordering::Relaxed);
151 | }
152 | }
153 |
154 | unsafe fn flush_page_table(&self, _phys_addr: PhysAddr) {
155 | core::arch::asm!("mfence");
156 | x86_64::instructions::tlb::flush_all();
157 |
158 | if let Err(_) = DISK_IO.lock().sync() {
159 | serial_println!("Warning: Failed to sync page table to disk");
160 | }
161 | }
162 |
163 | pub fn mark_dirty(&mut self, frame: PhysFrame) {
164 | if let Some(entry) = self.entries.get_mut(&frame.start_address()) {
165 | entry.status = CacheEntryStatus::Dirty;
166 | }
167 | }
168 |
169 | pub fn get_stats(&self) -> (u64, u64, u64) {
170 | (
171 | self.stats.hits.load(Ordering::Relaxed),
172 | self.stats.misses.load(Ordering::Relaxed),
173 | self.stats.evictions.load(Ordering::Relaxed),
174 | )
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/priority.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::process::ProcessId;
18 | use alloc::collections::BinaryHeap;
19 | use alloc::vec::Vec;
20 | use core::cmp::Ordering;
21 |
22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
23 | pub struct ProcessPriority {
24 | priority: u8,
25 | pid: ProcessId,
26 | time_slice: u64,
27 | }
28 |
29 | impl ProcessPriority {
30 | pub fn new(pid: ProcessId, priority: u8) -> Self {
31 | let time_slice = match priority {
32 | 0..=3 => 50,
33 | 4..=7 => 100,
34 | _ => 200,
35 | };
36 |
37 | Self {
38 | pid,
39 | priority,
40 | time_slice,
41 | }
42 | }
43 | }
44 |
45 | impl Ord for ProcessPriority {
46 | fn cmp(&self, other: &Self) -> Ordering {
47 | self.priority
48 | .cmp(&other.priority)
49 | .then_with(|| self.pid.0.cmp(&other.pid.0))
50 | }
51 | }
52 |
53 | impl PartialOrd for ProcessPriority {
54 | fn partial_cmp(&self, other: &Self) -> Option {
55 | Some(self.cmp(other))
56 | }
57 | }
58 |
59 | pub struct PriorityScheduler {
60 | ready_queue: BinaryHeap,
61 | process_priorities: Vec,
62 | }
63 |
64 | impl PriorityScheduler {
65 | pub fn new() -> Self {
66 | Self {
67 | ready_queue: BinaryHeap::new(),
68 | process_priorities: Vec::new(),
69 | }
70 | }
71 |
72 | pub fn add_process(&mut self, pid: ProcessId, priority: u8) {
73 | let process_priority = ProcessPriority::new(pid, priority);
74 | self.process_priorities.push(process_priority);
75 | self.ready_queue.push(process_priority);
76 | }
77 |
78 | pub fn remove_process(&mut self, pid: ProcessId) {
79 | self.process_priorities.retain(|p| p.pid != pid);
80 | let new_queue: BinaryHeap<_> = self.ready_queue.drain().filter(|p| p.pid != pid).collect();
81 | self.ready_queue = new_queue;
82 | }
83 |
84 | pub fn get_next_process(&mut self) -> Option<(ProcessId, u64)> {
85 | self.ready_queue.pop().map(|p| (p.pid, p.time_slice))
86 | }
87 |
88 | pub fn requeue_process(&mut self, pid: ProcessId) {
89 | if let Some(priority) = self
90 | .process_priorities
91 | .iter()
92 | .find(|p| p.pid == pid)
93 | .cloned()
94 | {
95 | self.ready_queue.push(priority);
96 | }
97 | }
98 |
99 | pub fn get_time_slice(&self, pid: ProcessId) -> u64 {
100 | self.process_priorities
101 | .iter()
102 | .find(|p| p.pid == pid)
103 | .map(|p| p.time_slice)
104 | .unwrap_or(100)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/proof_storage.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::lazy_static;
18 | use crate::time::Timestamp;
19 | use crate::verification::{Hash, OperationProof};
20 | use alloc::collections::BTreeMap;
21 | use alloc::vec::Vec;
22 | use core::sync::atomic::{AtomicU64, Ordering};
23 | use spin::Mutex;
24 |
25 | #[derive(Debug)]
26 | pub struct ProofStorage {
27 | proofs: BTreeMap,
28 | current_state: AtomicU64,
29 | max_proofs: usize,
30 | }
31 |
32 | #[derive(Debug)]
33 | struct StoredProof {
34 | proof: OperationProof,
35 | timestamp: u64,
36 | verified: bool,
37 | }
38 |
39 | impl ProofStorage {
40 | pub const MAX_PROOFS: usize = 10000;
41 |
42 | pub fn new() -> Self {
43 | Self {
44 | proofs: BTreeMap::new(),
45 | current_state: AtomicU64::new(0),
46 | max_proofs: Self::MAX_PROOFS,
47 | }
48 | }
49 |
50 | pub fn store_proof(&mut self, proof: OperationProof) -> Result<(), &'static str> {
51 | if self.proofs.len() >= self.max_proofs {
52 | self.cleanup_old_proofs();
53 | }
54 |
55 | let stored = StoredProof {
56 | proof: proof.clone(),
57 | timestamp: Timestamp::now().secs,
58 | verified: false,
59 | };
60 |
61 | self.proofs.insert(proof.op_id, stored);
62 | self.current_state
63 | .store(proof.new_state.0, Ordering::SeqCst);
64 | Ok(())
65 | }
66 |
67 | pub fn get_proof(&self, op_id: u64) -> Option<&OperationProof> {
68 | self.proofs.get(&op_id).map(|stored| &stored.proof)
69 | }
70 |
71 | pub fn verify_chain(&self) -> Result {
72 | let mut current_hash = Hash(0);
73 |
74 | for (_, stored) in self.proofs.iter() {
75 | if stored.proof.prev_state != current_hash {
76 | return Ok(false);
77 | }
78 | current_hash = stored.proof.new_state;
79 | }
80 |
81 | Ok(true)
82 | }
83 |
84 | pub fn cleanup_old_proofs(&mut self) {
85 | let current_time = Timestamp::now().secs;
86 | let one_hour = 3600;
87 |
88 | self.proofs
89 | .retain(|_, stored| current_time - stored.timestamp < one_hour);
90 | }
91 |
92 | pub fn mark_verified(&mut self, op_id: u64) {
93 | if let Some(stored) = self.proofs.get_mut(&op_id) {
94 | stored.verified = true;
95 | }
96 | }
97 |
98 | pub fn get_unverified_proofs(&self) -> Vec {
99 | self.proofs
100 | .iter()
101 | .filter(|(_, stored)| !stored.verified)
102 | .map(|(&op_id, _)| op_id)
103 | .collect()
104 | }
105 |
106 | pub fn current_state(&self) -> Hash {
107 | Hash(self.current_state.load(Ordering::SeqCst))
108 | }
109 | }
110 |
111 | lazy_static! {
112 | pub static ref PROOF_STORAGE: Mutex = Mutex::new(ProofStorage::new());
113 | }
114 |
--------------------------------------------------------------------------------
/src/serial.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use core::fmt::{self, Write};
18 | use lazy_static::lazy_static;
19 | use spin::Mutex;
20 | use uart_16550::SerialPort;
21 |
22 | lazy_static! {
23 | pub static ref SERIAL1: Mutex = {
24 | let mut serial_port = unsafe { SerialPort::new(0x3F8) };
25 | serial_port.init();
26 | Mutex::new(serial_port)
27 | };
28 | }
29 |
30 | #[doc(hidden)]
31 | pub fn _print(args: fmt::Arguments) {
32 | use x86_64::instructions::interrupts;
33 |
34 | interrupts::without_interrupts(|| {
35 | let mut serial_port = SERIAL1.lock();
36 |
37 | let _ = serial_port.write_fmt(args);
38 | });
39 | }
40 |
41 | #[macro_export]
42 | macro_rules! serial_print {
43 | ($($arg:tt)*) => {
44 | $crate::serial::_print(format_args!($($arg)*))
45 | };
46 | }
47 |
48 | #[macro_export]
49 | macro_rules! serial_println {
50 | () => ($crate::serial_print!("\n"));
51 | ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
52 | ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(
53 | concat!($fmt, "\n"), $($arg)*));
54 | }
55 |
--------------------------------------------------------------------------------
/src/shell/commands/ls.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::fs::{FileStats, FileSystem, FILESYSTEM};
18 | use crate::print;
19 | use crate::println;
20 | use crate::serial_println;
21 | use crate::shell::format;
22 | use alloc::string::String;
23 | use alloc::vec::Vec;
24 |
25 | #[derive(Debug, Clone, Copy)]
26 | pub struct LsFlags {
27 | long_format: bool,
28 | all_files: bool,
29 | recursive: bool,
30 | human_readable: bool,
31 | sort_time: bool,
32 | }
33 |
34 | impl Default for LsFlags {
35 | fn default() -> Self {
36 | Self {
37 | long_format: false,
38 | all_files: false,
39 | recursive: false,
40 | human_readable: false,
41 | sort_time: false,
42 | }
43 | }
44 | }
45 |
46 | pub fn parse_ls_flags(args: &[String]) -> (LsFlags, Vec) {
47 | let mut flags = LsFlags::default();
48 | let mut paths = Vec::new();
49 |
50 | for arg in args {
51 | if arg.starts_with('-') {
52 | for c in arg.chars().skip(1) {
53 | match c {
54 | 'l' => flags.long_format = true,
55 | 'a' => flags.all_files = true,
56 | 'R' => flags.recursive = true,
57 | 'h' => flags.human_readable = true,
58 | 't' => flags.sort_time = true,
59 | _ => continue,
60 | }
61 | }
62 | } else {
63 | paths.push(arg.clone());
64 | }
65 | }
66 |
67 | if paths.is_empty() {
68 | paths.push(String::from("."));
69 | }
70 |
71 | (flags, paths)
72 | }
73 |
74 | fn format_permissions(stats: &FileStats) -> String {
75 | let mut perms = String::with_capacity(10);
76 | perms.push(if stats.is_directory { 'd' } else { '-' });
77 | perms.push(if stats.permissions.read { 'r' } else { '-' });
78 | perms.push(if stats.permissions.write { 'w' } else { '-' });
79 | perms.push(if stats.permissions.execute { 'x' } else { '-' });
80 | perms.push_str("------");
81 | perms
82 | }
83 |
84 | fn format_size(size: usize, human_readable: bool) -> String {
85 | if !human_readable {
86 | return format!("{:>8}", size);
87 | }
88 |
89 | let units = ["B", "K", "M", "G", "T"];
90 | let mut size = size as f64;
91 | let mut unit_idx = 0;
92 |
93 | while size >= 1024.0 && unit_idx < units.len() - 1 {
94 | size /= 1024.0;
95 | unit_idx += 1;
96 | }
97 |
98 | if unit_idx == 0 {
99 | format!("{:>4}{:>1}", size as usize, units[unit_idx])
100 | } else {
101 | format!("{:>4.1}{:>1}", size, units[unit_idx])
102 | }
103 | }
104 |
105 | fn format_time(timestamp: u64) -> String {
106 | let secs = timestamp % 60;
107 | let mins = (timestamp / 60) % 60;
108 | let hours = (timestamp / 3600) % 24;
109 | format!("{:02}:{:02}:{:02}", hours, mins, secs)
110 | }
111 |
112 | pub fn list_directory(path: &str, flags: LsFlags) -> Result<(), &'static str> {
113 | serial_println!("Listing directory: {}", path);
114 | let normalized_path = crate::fs::normalize_path(path);
115 | serial_println!("Normalized path: {}", normalized_path);
116 |
117 | let mut fs = FILESYSTEM.lock();
118 |
119 | match fs.stat(path) {
120 | Ok(stats) => {
121 | serial_println!("Path exists, is_directory: {}", stats.is_directory);
122 | if !stats.is_directory {
123 | return Err("Not a directory");
124 | }
125 | }
126 | Err(e) => {
127 | serial_println!("Failed to stat path: {:?}", e);
128 | return Err("No such file or directory");
129 | }
130 | }
131 |
132 | serial_println!("Attempting to list directory entries");
133 | let entries = match fs.list_directory(path) {
134 | Ok(entries) => {
135 | serial_println!("Found {} entries", entries.len());
136 | entries
137 | }
138 | Err(e) => {
139 | serial_println!("Failed to list directory: {:?}", e);
140 | return Err("Failed to read directory");
141 | }
142 | };
143 |
144 | let mut entries = entries
145 | .into_iter()
146 | .filter(|name| flags.all_files || !name.starts_with('.'))
147 | .collect::>();
148 |
149 | serial_println!("Filtered entries: {:?}", entries);
150 |
151 | if entries.is_empty() {
152 | serial_println!("No entries to display");
153 | return Ok(());
154 | }
155 |
156 | entries.sort();
157 |
158 | if flags.long_format {
159 | println!("total {}", entries.len());
160 | for entry in entries {
161 | let full_path = if normalized_path == "/" {
162 | format!("/{}", entry)
163 | } else {
164 | format!("{}/{}", normalized_path, entry)
165 | };
166 |
167 | if let Ok(stats) = fs.stat(&full_path) {
168 | let perms = format_permissions(&stats);
169 | let size = format_size(stats.size, flags.human_readable);
170 | let time = format_time(stats.modified.0.secs);
171 | println!("{} {:>8} {} {}", perms, size, time, entry);
172 | }
173 | }
174 | } else {
175 | for entry in entries {
176 | print!("{} ", entry);
177 | }
178 | println!();
179 | }
180 |
181 | Ok(())
182 | }
183 |
--------------------------------------------------------------------------------
/src/shell/commands/mod.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | pub mod ls;
18 |
--------------------------------------------------------------------------------
/src/shell/display.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use super::ShellError;
18 | use crate::shell::Vec;
19 | use crate::vga_buffer::{Color, ColorCode, BUFFER_HEIGHT, BUFFER_WIDTH, WRITER};
20 | use alloc::string::String;
21 | use core::fmt::Write;
22 | use x86_64::instructions::interrupts;
23 |
24 | pub struct ShellDisplay {
25 | prompt: String,
26 | error_color: ColorCode,
27 | prompt_color: ColorCode,
28 | text_color: ColorCode,
29 | input_buffer: Vec,
30 | cursor_position: usize,
31 | }
32 |
33 | impl ShellDisplay {
34 | pub fn new() -> Self {
35 | Self {
36 | prompt: String::from("> "),
37 | error_color: ColorCode::new(Color::Red, Color::Black),
38 | prompt_color: ColorCode::new(Color::Green, Color::Black),
39 | text_color: ColorCode::new(Color::White, Color::Black),
40 | input_buffer: Vec::new(),
41 | cursor_position: 0,
42 | }
43 | }
44 |
45 | pub fn set_prompt(&mut self, prompt: String) {
46 | self.prompt = prompt;
47 | }
48 |
49 | pub fn render_prompt(&self) -> usize {
50 | interrupts::without_interrupts(|| {
51 | let mut writer = WRITER.lock();
52 | let original_color = writer.color_code;
53 |
54 | writer.color_code = self.prompt_color;
55 | writer.write_str(&self.prompt).unwrap();
56 | writer.color_code = original_color;
57 |
58 | let current_pos = writer.column_position;
59 |
60 | writer.enable_cursor();
61 | writer.set_cursor_position(current_pos, BUFFER_HEIGHT - 1);
62 |
63 | current_pos
64 | })
65 | }
66 |
67 | pub fn clear_screen(&self) {
68 | use x86_64::instructions::interrupts;
69 | interrupts::without_interrupts(|| {
70 | let mut writer = WRITER.lock();
71 | writer.clear_screen();
72 | writer.column_position = 0;
73 | writer.set_cursor_position(0, 0);
74 | writer.enable_cursor();
75 | });
76 | }
77 |
78 | pub fn clear_line(&self) {
79 | interrupts::without_interrupts(|| {
80 | let mut writer = WRITER.lock();
81 | let original_color = writer.color_code;
82 |
83 | writer.column_position = 0;
84 |
85 | for _ in 0..BUFFER_WIDTH {
86 | writer.write_byte(b' ');
87 | }
88 |
89 | writer.column_position = 0;
90 | writer.set_cursor_position(0, BUFFER_HEIGHT - 1);
91 |
92 | writer.color_code = original_color;
93 | });
94 | }
95 |
96 | pub fn move_cursor(&self, position: usize) {
97 | if position < BUFFER_WIDTH {
98 | WRITER.lock().column_position = position;
99 | }
100 | }
101 |
102 | pub fn get_cursor_position(&self) -> usize {
103 | WRITER.lock().column_position
104 | }
105 |
106 | pub fn get_prompt(&self) -> &str {
107 | &self.prompt
108 | }
109 |
110 | pub fn display_error(&self, error: &ShellError) {
111 | use x86_64::instructions::interrupts;
112 | interrupts::without_interrupts(|| {
113 | let mut writer = WRITER.lock();
114 | let original_color = writer.color_code;
115 |
116 | if writer.column_position > 0 {
117 | writer.write_byte(b'\n');
118 | }
119 |
120 | writer.color_code = self.error_color;
121 | writer.write_str("Error: ").unwrap();
122 |
123 | let message = match error {
124 | ShellError::CommandNotFound => "Command not found",
125 | ShellError::InvalidArguments => "Invalid arguments",
126 | ShellError::IOError => "I/O error",
127 | ShellError::PermissionDenied => "Permission denied",
128 | ShellError::PathNotFound => "Path not found",
129 | ShellError::InvalidPath => "Invalid path",
130 | ShellError::EnvironmentError => "Environment error",
131 | ShellError::InternalError => "Internal error",
132 | ShellError::BufferOverflow => "Buffer overflow",
133 | ShellError::SyntaxError => "Syntax error",
134 | ShellError::ExecutionFailed => "Execution failed",
135 | ShellError::NotADirectory => "Not a directory",
136 | ShellError::InvalidExecutable => "Invalid executable format",
137 | };
138 |
139 | writer.write_str(message).unwrap();
140 | writer.color_code = original_color;
141 | writer.write_byte(b'\n');
142 | });
143 | }
144 |
145 | pub fn redraw_line(&self, content: &[u8], cursor_pos: usize) {
146 | interrupts::without_interrupts(|| {
147 | let mut writer = WRITER.lock();
148 | let original_color = writer.color_code;
149 |
150 | writer.column_position = 0;
151 | for _ in 0..BUFFER_WIDTH {
152 | writer.write_byte(b' ');
153 | }
154 |
155 | writer.column_position = 0;
156 |
157 | writer.color_code = self.prompt_color;
158 | writer.write_str(&self.prompt).unwrap();
159 |
160 | let prompt_end = writer.column_position;
161 |
162 | writer.color_code = self.text_color;
163 | for (i, &byte) in content.iter().enumerate() {
164 | if i == cursor_pos {
165 | writer.color_code = ColorCode::new(Color::Black, Color::White);
166 | }
167 |
168 | if writer.column_position < BUFFER_WIDTH {
169 | writer.write_byte(byte);
170 | }
171 |
172 | if i == cursor_pos {
173 | writer.color_code = self.text_color;
174 | }
175 | }
176 |
177 | let final_cursor_pos = prompt_end + cursor_pos;
178 | if final_cursor_pos < BUFFER_WIDTH {
179 | writer.column_position = final_cursor_pos;
180 | writer.set_cursor_position(final_cursor_pos, BUFFER_HEIGHT - 1);
181 | }
182 |
183 | writer.color_code = original_color;
184 | writer.enable_cursor();
185 | });
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/shell/executor.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use super::commands::ls::{list_directory, parse_ls_flags};
18 | use super::ShellResult;
19 | use crate::alloc::string::ToString;
20 | use crate::fs::normalize_path;
21 | use crate::fs::validate_path;
22 | use crate::fs::FileSystem;
23 | use crate::fs::FsError;
24 | use crate::fs::FILESYSTEM;
25 | use crate::println;
26 | use crate::process::PROCESS_LIST;
27 | use crate::scheduler::SCHEDULER;
28 | use crate::serial_println;
29 | use crate::shell::format;
30 | use crate::shell::ExitCode;
31 | use crate::shell::ShellDisplay;
32 | use crate::shell::ShellError;
33 | use crate::Process;
34 | use crate::MEMORY_MANAGER;
35 | use alloc::string::String;
36 | use alloc::vec::Vec;
37 |
38 | pub struct CommandExecutor {
39 | builtins: Vec<(&'static str, fn(&[String]) -> ShellResult)>,
40 | }
41 |
42 | impl CommandExecutor {
43 | pub fn new() -> Self {
44 | let mut executor = Self {
45 | builtins: Vec::new(),
46 | };
47 |
48 | executor.register_builtin("exit", Self::cmd_exit);
49 | executor.register_builtin("clear", Self::cmd_clear);
50 | executor.register_builtin("help", Self::cmd_help);
51 | executor.register_builtin("ls", Self::cmd_ls);
52 | executor.register_builtin("cd", Self::cmd_cd);
53 | executor.register_builtin("pwd", Self::cmd_pwd);
54 |
55 | executor
56 | }
57 |
58 | pub fn register_builtin(&mut self, name: &'static str, handler: fn(&[String]) -> ShellResult) {
59 | self.builtins.push((name, handler));
60 | }
61 |
62 | fn execute_program(&self, path: &str, args: &[String]) -> ShellResult {
63 | let mut fs = FILESYSTEM.lock();
64 | serial_println!("Attempting to read program file: {}", path);
65 | match fs.read_file(path) {
66 | Ok(data) => {
67 | serial_println!("Successfully read program file, size: {} bytes", data.len());
68 | let process_id;
69 | {
70 | let mut mm_lock = MEMORY_MANAGER.lock();
71 | if let Some(mm) = mm_lock.as_mut() {
72 | serial_println!("Got memory manager lock");
73 | match Process::new(mm) {
74 | Ok(mut process) => {
75 | serial_println!("Created new process with ID: {}", process.id().0);
76 | if let Err(e) = process.load_program(&data, mm) {
77 | serial_println!("Failed to load program: {:?}", e);
78 | return Err(ShellError::ExecutionFailed);
79 | }
80 | serial_println!("Successfully loaded program");
81 |
82 | process_id = process.id();
83 |
84 | let mut scheduler = SCHEDULER.lock();
85 | scheduler.add_process(process);
86 | }
87 | Err(_) => return Err(ShellError::ExecutionFailed),
88 | }
89 | } else {
90 | return Err(ShellError::ExecutionFailed);
91 | }
92 | }
93 |
94 | if let Some(mut current) = PROCESS_LIST.lock().get_mut_by_id(process_id) {
95 | current.switch_to_user_mode();
96 | }
97 |
98 | Ok(ExitCode::Success)
99 | }
100 | Err(_) => Err(ShellError::ExecutionFailed),
101 | }
102 | }
103 |
104 | pub fn execute(&self, command: &str, args: &[String]) -> ShellResult {
105 | serial_println!("Shell: Starting command execution for '{}'", command);
106 |
107 | for &(name, handler) in &self.builtins {
108 | if command == name {
109 | return handler(args);
110 | }
111 | }
112 |
113 | let program_path = if command.starts_with('/') {
114 | command.to_string()
115 | } else {
116 | format!("/programs/{}", command)
117 | };
118 |
119 | {
120 | let mut fs = FILESYSTEM.lock();
121 | match fs.stat(&program_path) {
122 | Ok(_) => {
123 | serial_println!("Found program file");
124 | drop(fs);
125 | self.execute_program(&program_path, args)
126 | }
127 | Err(_) => {
128 | serial_println!("Program not found");
129 | Err(ShellError::CommandNotFound)
130 | }
131 | }
132 | }
133 | }
134 |
135 | fn cmd_exit(args: &[String]) -> ShellResult {
136 | let code = args.get(0).and_then(|s| s.parse::().ok()).unwrap_or(0);
137 |
138 | Ok(ExitCode::from_i32(code))
139 | }
140 |
141 | fn cmd_clear(_args: &[String]) -> ShellResult {
142 | let display = ShellDisplay::new();
143 | display.clear_screen();
144 | Ok(ExitCode::Success)
145 | }
146 |
147 | fn cmd_help(_args: &[String]) -> ShellResult {
148 | println!("Available commands:");
149 | println!(" exit [code] - Exit the shell with optional status code");
150 | println!(" clear - Clear the screen");
151 | println!(" help - Show this help message");
152 | println!(" ls [options] [path] List directory contents");
153 | println!(" -l Long format listing");
154 | println!(" -a Show hidden files");
155 | println!(" -R Recursive listing");
156 | println!(" -h Human readable sizes");
157 | println!(" -t Sort by time");
158 | println!(" cd - Change current directory");
159 | println!(" pwd - Print working directory");
160 | Ok(ExitCode::Success)
161 | }
162 |
163 | fn cmd_ls(args: &[String]) -> ShellResult {
164 | serial_println!("Executing ls with args: {:?}", args);
165 |
166 | let (flags, mut paths) = parse_ls_flags(args);
167 |
168 | if paths.is_empty() {
169 | paths.push(String::from("."));
170 | }
171 |
172 | serial_println!("Parsed paths: {:?}", paths);
173 |
174 | for path in &paths {
175 | if paths.len() > 1 {
176 | println!("{}:", path);
177 | }
178 | match list_directory(path, flags) {
179 | Ok(_) => (),
180 | Err(e) => {
181 | println!("ls: {}: {}", path, e);
182 | return Ok(ExitCode::Failure);
183 | }
184 | }
185 | if paths.len() > 1 {
186 | println!();
187 | }
188 | }
189 | Ok(ExitCode::Success)
190 | }
191 |
192 | fn cmd_cd(args: &[String]) -> ShellResult {
193 | let path = args.get(0).map(String::as_str).unwrap_or("/");
194 |
195 | let process_list = PROCESS_LIST.lock();
196 | let current_dir = process_list
197 | .current()
198 | .map(|p| p.current_dir.clone())
199 | .unwrap_or_else(|| String::from("/"));
200 |
201 | serial_println!("CD: Current directory is {}", current_dir);
202 |
203 | let target_path = if path.starts_with('/') {
204 | normalize_path(path)
205 | } else {
206 | normalize_path(&format!("{}/{}", current_dir, path))
207 | };
208 |
209 | serial_println!("CD: Target path is {}", target_path);
210 |
211 | drop(process_list);
212 |
213 | let mut fs = FILESYSTEM.lock();
214 | match validate_path(&mut *fs, &target_path) {
215 | Ok(stats) => {
216 | if !stats.is_directory {
217 | return Err(ShellError::NotADirectory);
218 | }
219 |
220 | drop(fs);
221 |
222 | let mut process_list = PROCESS_LIST.lock();
223 | if let Some(current) = process_list.current_mut() {
224 | current.current_dir = target_path;
225 | Ok(ExitCode::Success)
226 | } else {
227 | Err(ShellError::InternalError)
228 | }
229 | }
230 | Err(FsError::NotADirectory) => Err(ShellError::NotADirectory),
231 | Err(FsError::NotFound) => Err(ShellError::PathNotFound),
232 | Err(_) => Err(ShellError::InvalidPath),
233 | }
234 | }
235 |
236 | fn cmd_pwd(_args: &[String]) -> ShellResult {
237 | serial_println!("PWD command execution started");
238 |
239 | let current_dir = {
240 | let process_list = PROCESS_LIST.lock();
241 | process_list
242 | .current()
243 | .map(|p| p.current_dir.clone())
244 | .unwrap_or_else(|| String::from("/"))
245 | };
246 |
247 | println!("{}", current_dir);
248 | serial_println!("PWD command completed");
249 | Ok(ExitCode::Success)
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/shell/parser.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use alloc::string::String;
18 | use alloc::vec::Vec;
19 | use core::str::Chars;
20 |
21 | #[derive(Debug, Clone, PartialEq)]
22 | pub struct Token {
23 | pub value: String,
24 | pub token_type: TokenType,
25 | }
26 |
27 | #[derive(Debug, Clone, PartialEq)]
28 | pub enum TokenType {
29 | Command,
30 | Argument,
31 | Pipe,
32 | Redirect,
33 | }
34 |
35 | #[derive(Debug)]
36 | pub enum ParseError {
37 | UnterminatedQuote,
38 | InvalidEscape,
39 | EmptyCommand,
40 | InvalidSyntax(String),
41 | }
42 |
43 | pub struct Parser<'a> {
44 | input: Chars<'a>,
45 | current: Option,
46 | tokens: Vec,
47 | }
48 |
49 | impl<'a> Parser<'a> {
50 | pub fn new(input: &'a str) -> Self {
51 | let mut chars = input.chars();
52 | let current = chars.next();
53 | Self {
54 | input: chars,
55 | current,
56 | tokens: Vec::new(),
57 | }
58 | }
59 |
60 | fn advance(&mut self) {
61 | self.current = self.input.next();
62 | }
63 |
64 | fn skip_whitespace(&mut self) {
65 | while let Some(c) = self.current {
66 | if !c.is_whitespace() {
67 | break;
68 | }
69 | self.advance();
70 | }
71 | }
72 |
73 | fn parse_quoted_string(&mut self, quote: char) -> Result {
74 | let mut result = String::new();
75 | self.advance();
76 |
77 | while let Some(c) = self.current {
78 | match c {
79 | c if c == quote => {
80 | self.advance();
81 | return Ok(result);
82 | }
83 | '\\' => {
84 | self.advance();
85 | match self.current {
86 | Some(escaped) => {
87 | result.push(match escaped {
88 | 'n' => '\n',
89 | 't' => '\t',
90 | 'r' => '\r',
91 | '\\' | '\'' | '"' => escaped,
92 | _ => return Err(ParseError::InvalidEscape),
93 | });
94 | self.advance();
95 | }
96 | None => return Err(ParseError::InvalidEscape),
97 | }
98 | }
99 | _ => {
100 | result.push(c);
101 | self.advance();
102 | }
103 | }
104 | }
105 | Err(ParseError::UnterminatedQuote)
106 | }
107 |
108 | fn parse_word(&mut self) -> String {
109 | let mut result = String::new();
110 |
111 | while let Some(c) = self.current {
112 | match c {
113 | c if c.is_whitespace() => {
114 | break;
115 | }
116 | '|' | '>' | '<' => {
117 | break;
118 | }
119 | '\\' => {
120 | self.advance();
121 | if let Some(escaped) = self.current {
122 | result.push(escaped);
123 | }
124 | }
125 | _ => {
126 | result.push(c);
127 | }
128 | }
129 | self.advance();
130 | }
131 |
132 | result
133 | }
134 |
135 | pub fn parse(&mut self) -> Result, ParseError> {
136 | let mut is_first_token = true;
137 | self.tokens.clear();
138 |
139 | while let Some(c) = self.current {
140 | self.skip_whitespace();
141 |
142 | if self.current.is_none() {
143 | break;
144 | }
145 |
146 | let token = match self.current.unwrap() {
147 | '"' | '\'' => {
148 | let value = self.parse_quoted_string(self.current.unwrap())?;
149 | Token {
150 | value,
151 | token_type: if is_first_token {
152 | TokenType::Command
153 | } else {
154 | TokenType::Argument
155 | },
156 | }
157 | }
158 | '|' => {
159 | self.advance();
160 | Token {
161 | value: String::from("|"),
162 | token_type: TokenType::Pipe,
163 | }
164 | }
165 | '>' | '<' => {
166 | self.advance();
167 | Token {
168 | value: String::from(if c == '>' { ">" } else { "<" }),
169 | token_type: TokenType::Redirect,
170 | }
171 | }
172 | _ => {
173 | let word = self.parse_word();
174 | if word.is_empty() {
175 | self.advance();
176 | continue;
177 | }
178 | Token {
179 | value: word,
180 | token_type: if is_first_token {
181 | TokenType::Command
182 | } else {
183 | TokenType::Argument
184 | },
185 | }
186 | }
187 | };
188 |
189 | self.tokens.push(token);
190 | is_first_token = false;
191 | }
192 |
193 | if self.tokens.is_empty() {
194 | return Err(ParseError::EmptyCommand);
195 | }
196 |
197 | self.validate_syntax()?;
198 | Ok(self.tokens.clone())
199 | }
200 |
201 | fn validate_syntax(&self) -> Result<(), ParseError> {
202 | let mut prev_token: Option<&Token> = None;
203 |
204 | for token in &self.tokens {
205 | match (&token.token_type, prev_token.map(|t| &t.token_type)) {
206 | (TokenType::Pipe, Some(TokenType::Pipe)) => {
207 | return Err(ParseError::InvalidSyntax(
208 | "Cannot have two consecutive pipes".into(),
209 | ));
210 | }
211 | (TokenType::Redirect, Some(TokenType::Redirect)) => {
212 | return Err(ParseError::InvalidSyntax(
213 | "Cannot have two consecutive redirects".into(),
214 | ));
215 | }
216 | (TokenType::Pipe, None) => {
217 | return Err(ParseError::InvalidSyntax(
218 | "Cannot start command with pipe".into(),
219 | ));
220 | }
221 | _ => {}
222 | }
223 | prev_token = Some(token);
224 | }
225 |
226 | if let Some(last_token) = self.tokens.last() {
227 | match last_token.token_type {
228 | TokenType::Pipe | TokenType::Redirect => {
229 | return Err(ParseError::InvalidSyntax(
230 | "Cannot end command with pipe or redirect".into(),
231 | ));
232 | }
233 | _ => {}
234 | }
235 | }
236 |
237 | Ok(())
238 | }
239 | }
240 |
241 | #[cfg(test)]
242 | mod tests {
243 | use super::*;
244 |
245 | #[test]
246 | fn test_basic_command() {
247 | let mut parser = Parser::new("ls -la");
248 | let tokens = parser.parse().unwrap();
249 | assert_eq!(tokens.len(), 2);
250 | assert_eq!(tokens[0].value, "ls");
251 | assert_eq!(tokens[0].token_type, TokenType::Command);
252 | assert_eq!(tokens[1].value, "-la");
253 | assert_eq!(tokens[1].token_type, TokenType::Argument);
254 | }
255 |
256 | #[test]
257 | fn test_quoted_strings() {
258 | let mut parser = Parser::new("echo \"Hello World\"");
259 | let tokens = parser.parse().unwrap();
260 | assert_eq!(tokens.len(), 2);
261 | assert_eq!(tokens[1].value, "Hello World");
262 | }
263 |
264 | #[test]
265 | fn test_escaped_characters() {
266 | let mut parser = Parser::new("echo \"Hello\\nWorld\"");
267 | let tokens = parser.parse().unwrap();
268 | assert_eq!(tokens[1].value, "Hello\nWorld");
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/signals.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use alloc::vec::Vec;
18 | use core::sync::atomic::{AtomicBool, Ordering};
19 | use x86_64::VirtAddr;
20 |
21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22 | #[repr(u32)]
23 | pub enum Signal {
24 | SIGHUP = 1,
25 | SIGINT = 2,
26 | SIGQUIT = 3,
27 | SIGILL = 4,
28 | SIGTRAP = 5,
29 | SIGABRT = 6,
30 | SIGBUS = 7,
31 | SIGFPE = 8,
32 | SIGKILL = 9,
33 | SIGSEGV = 11,
34 | SIGPIPE = 13,
35 | SIGALRM = 14,
36 | SIGTERM = 15,
37 | SIGCHLD = 17,
38 | SIGCONT = 18,
39 | SIGSTOP = 19,
40 | SIGTSTP = 20,
41 | }
42 |
43 | impl Signal {
44 | pub fn from_u32(value: u32) -> Option {
45 | use Signal::*;
46 | match value {
47 | 1 => Some(SIGHUP),
48 | 2 => Some(SIGINT),
49 | 3 => Some(SIGQUIT),
50 | 4 => Some(SIGILL),
51 | 5 => Some(SIGTRAP),
52 | 6 => Some(SIGABRT),
53 | 7 => Some(SIGBUS),
54 | 8 => Some(SIGFPE),
55 | 9 => Some(SIGKILL),
56 | 11 => Some(SIGSEGV),
57 | 13 => Some(SIGPIPE),
58 | 14 => Some(SIGALRM),
59 | 15 => Some(SIGTERM),
60 | 17 => Some(SIGCHLD),
61 | 18 => Some(SIGCONT),
62 | 19 => Some(SIGSTOP),
63 | 20 => Some(SIGTSTP),
64 | _ => None,
65 | }
66 | }
67 | }
68 |
69 | #[derive(Debug, Clone, Copy)]
70 | pub struct SignalHandler {
71 | pub handler: VirtAddr,
72 | pub mask: u32,
73 | pub flags: u32,
74 | }
75 |
76 | pub struct SignalState {
77 | pub pending: u32,
78 | pub handlers: [Option; 32],
79 | pub mask: u32,
80 | pub handling_signal: AtomicBool,
81 | }
82 |
83 | impl SignalState {
84 | pub fn new() -> Self {
85 | Self {
86 | pending: 0,
87 | handlers: [None; 32],
88 | mask: 0,
89 | handling_signal: AtomicBool::new(false),
90 | }
91 | }
92 |
93 | pub fn set_handler(
94 | &mut self,
95 | signal: Signal,
96 | handler: SignalHandler,
97 | ) -> Result<(), &'static str> {
98 | let sig_num = signal as usize;
99 | if sig_num >= 32 {
100 | return Err("Invalid signal number");
101 | }
102 |
103 | if signal == Signal::SIGKILL || signal == Signal::SIGSTOP {
104 | return Err("Cannot modify handler for SIGKILL or SIGSTOP");
105 | }
106 |
107 | self.handlers[sig_num] = Some(handler);
108 | Ok(())
109 | }
110 |
111 | pub fn send_signal(&mut self, signal: Signal) -> Result<(), &'static str> {
112 | let sig_num = signal as u32;
113 | if sig_num >= 32 {
114 | return Err("Invalid signal number");
115 | }
116 |
117 | self.pending |= 1 << sig_num;
118 | Ok(())
119 | }
120 |
121 | pub fn get_pending_signals(&self) -> Vec {
122 | let mut signals = Vec::new();
123 | for i in 0..32 {
124 | if (self.pending & (1 << i)) != 0 && (self.mask & (1 << i)) == 0 {
125 | if let Some(signal) = Signal::from_u32(i) {
126 | signals.push(signal);
127 | }
128 | }
129 | }
130 | signals
131 | }
132 |
133 | pub fn clear_signal(&mut self, signal: Signal) {
134 | let sig_num = signal as u32;
135 | self.pending &= !(1 << sig_num);
136 | }
137 |
138 | pub fn set_mask(&mut self, mask: u32) {
139 | self.mask = mask;
140 | }
141 |
142 | pub fn get_handler(&self, signal: Signal) -> Option {
143 | let sig_num = signal as usize;
144 | if sig_num < 32 {
145 | self.handlers[sig_num]
146 | } else {
147 | None
148 | }
149 | }
150 |
151 | pub fn is_handling_signal(&self) -> bool {
152 | self.handling_signal.load(Ordering::SeqCst)
153 | }
154 |
155 | pub fn set_handling_signal(&self, handling: bool) {
156 | self.handling_signal.store(handling, Ordering::SeqCst);
157 | }
158 | }
159 |
160 | pub struct SignalStack {
161 | stack: VirtAddr,
162 | size: usize,
163 | }
164 |
165 | impl SignalStack {
166 | pub fn new(stack: VirtAddr, size: usize) -> Self {
167 | Self { stack, size }
168 | }
169 |
170 | pub fn get_top(&self) -> VirtAddr {
171 | self.stack + self.size
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/swap.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::format;
18 | use crate::memory::SwapPageInfo;
19 | use crate::memory::SWAPPED_PAGES;
20 | use crate::tsc;
21 | use crate::Hash;
22 | use crate::OperationProof;
23 | use crate::Ordering;
24 | use crate::Verifiable;
25 | use crate::VerificationError;
26 | use crate::VirtAddr;
27 | use crate::{
28 | fs::{FileSystem, FILESYSTEM},
29 | hash,
30 | memory::{MemoryError, MemoryManager},
31 | };
32 | use alloc::vec;
33 | use alloc::vec::Vec;
34 | use core::sync::atomic::AtomicU64;
35 | use lazy_static::lazy_static;
36 | use spin::Mutex;
37 | use x86_64::structures::paging::FrameAllocator;
38 | use x86_64::structures::paging::FrameDeallocator;
39 | use x86_64::structures::paging::{Page, PageTableFlags};
40 |
41 | const PAGE_SIZE: usize = 4096;
42 | const MAX_SWAP_PAGES: usize = 1024;
43 |
44 | lazy_static! {
45 | pub static ref DISK_IO: Mutex = Mutex::new(DiskIOManager::new());
46 | }
47 |
48 | #[derive(Clone)]
49 | pub struct SwapEntry {
50 | pub offset: usize,
51 | pub page: Page,
52 | pub flags: PageTableFlags,
53 | pub hash: [u8; 32],
54 | }
55 |
56 | pub struct SwapManager {
57 | free_slots: Vec,
58 | pub used_slots: Vec>,
59 | }
60 |
61 | pub struct DiskIOManager {
62 | current_operation: AtomicU64,
63 | state_hash: AtomicU64,
64 | }
65 |
66 | impl SwapManager {
67 | pub fn new() -> Self {
68 | let mut free_slots = Vec::with_capacity(MAX_SWAP_PAGES);
69 | for i in 0..MAX_SWAP_PAGES {
70 | free_slots.push(i);
71 | }
72 |
73 | Self {
74 | free_slots,
75 | used_slots: vec![None; MAX_SWAP_PAGES],
76 | }
77 | }
78 |
79 | pub fn iter_slots(&self) -> impl Iterator- )> {
80 | self.used_slots.iter().enumerate()
81 | }
82 |
83 | pub fn swap_out(
84 | &mut self,
85 | page: Page,
86 | flags: PageTableFlags,
87 | memory_manager: &mut MemoryManager,
88 | ) -> Result {
89 | let slot = self.free_slots.pop().ok_or(MemoryError::NoSwapSpace)?;
90 |
91 | let offset = slot * PAGE_SIZE;
92 | let virt_addr = page.start_address();
93 | let mut swapped_pages = SWAPPED_PAGES.lock();
94 | swapped_pages.insert(
95 | virt_addr,
96 | SwapPageInfo {
97 | swap_slot: slot,
98 | flags,
99 | last_access: tsc::read_tsc(),
100 | reference_count: 1,
101 | },
102 | );
103 |
104 | let page_data = unsafe { core::slice::from_raw_parts(virt_addr.as_ptr::(), PAGE_SIZE) };
105 |
106 | let page_hash = hash::hash_memory(virt_addr, PAGE_SIZE);
107 |
108 | let disk_io = DISK_IO.lock();
109 | disk_io.write_page(slot as u64, page_data)?;
110 |
111 | let mut hash_bytes = [0u8; 32];
112 | hash_bytes[0..8].copy_from_slice(&page_hash.0.to_ne_bytes());
113 |
114 | let mut hash_bytes = [0u8; 32];
115 | hash_bytes[0..8].copy_from_slice(&page_hash.0.to_ne_bytes());
116 |
117 | self.used_slots[slot] = Some(SwapEntry {
118 | offset,
119 | page,
120 | flags,
121 | hash: hash_bytes,
122 | });
123 |
124 | unsafe {
125 | memory_manager.unmap_page(page)?;
126 | }
127 |
128 | Ok(slot)
129 | }
130 |
131 | pub fn swap_in(
132 | &mut self,
133 | slot: usize,
134 | memory_manager: &mut MemoryManager,
135 | ) -> Result<(), MemoryError> {
136 | let entry = self.used_slots[slot]
137 | .take()
138 | .ok_or(MemoryError::InvalidSwapSlot)?;
139 |
140 | let disk_io = DISK_IO.lock();
141 | let mut page_data = vec![0u8; PAGE_SIZE];
142 | disk_io.read_page(slot as u64, &mut page_data)?;
143 |
144 | let frame = memory_manager
145 | .frame_allocator
146 | .allocate_frame()
147 | .ok_or(MemoryError::FrameAllocationFailed)?;
148 |
149 | unsafe {
150 | memory_manager.map_page(entry.page, frame, entry.flags)?;
151 |
152 | core::ptr::copy_nonoverlapping(
153 | page_data.as_ptr(),
154 | entry.page.start_address().as_mut_ptr::(),
155 | PAGE_SIZE,
156 | );
157 |
158 | let new_hash = hash::hash_memory(entry.page.start_address(), PAGE_SIZE);
159 | let mut verify_bytes = [0u8; 32];
160 | verify_bytes[0..8].copy_from_slice(&new_hash.0.to_ne_bytes());
161 |
162 | if verify_bytes != entry.hash {
163 | memory_manager.unmap_page(entry.page)?;
164 | memory_manager.frame_allocator.deallocate_frame(frame);
165 | return Err(MemoryError::SwapFileError);
166 | }
167 | }
168 |
169 | self.free_slots.push(slot);
170 |
171 | Ok(())
172 | }
173 | }
174 |
175 | impl DiskIOManager {
176 | pub fn new() -> Self {
177 | Self {
178 | current_operation: AtomicU64::new(0),
179 | state_hash: AtomicU64::new(0),
180 | }
181 | }
182 |
183 | pub fn read_page(&self, block: u64, buffer: &mut [u8]) -> Result<(), &'static str> {
184 | if buffer.len() != 4096 {
185 | return Err("Invalid buffer size for page read");
186 | }
187 |
188 | let mut fs = FILESYSTEM.lock();
189 | match fs.read_file(&format!("swap_{}", block)) {
190 | Ok(data) => {
191 | buffer.copy_from_slice(&data[..4096]);
192 | Ok(())
193 | }
194 | Err(_) => Err("Failed to read swap page"),
195 | }
196 | }
197 |
198 | pub fn write_page(&self, block: u64, data: &[u8]) -> Result<(), &'static str> {
199 | if data.len() != 4096 {
200 | return Err("Invalid data size for page write");
201 | }
202 |
203 | let mut fs = FILESYSTEM.lock();
204 | fs.write_file(&format!("swap_{}", block), data)
205 | .map_err(|_| "Failed to write swap page")
206 | }
207 |
208 | pub fn sync(&self) -> Result<(), &'static str> {
209 | let mut fs = FILESYSTEM.lock();
210 | fs.sync().map_err(|_| "Failed to sync pages to disk")
211 | }
212 | }
213 |
214 | impl Verifiable for DiskIOManager {
215 | fn generate_proof(
216 | &self,
217 | operation: crate::verification::Operation,
218 | ) -> Result {
219 | let prev_state = self.state_hash.load(Ordering::SeqCst);
220 | let op_hash = Hash(self.current_operation.load(Ordering::SeqCst));
221 |
222 | Ok(OperationProof {
223 | op_id: crate::tsc::read_tsc(),
224 | prev_state: Hash(prev_state),
225 | new_state: Hash(prev_state ^ op_hash.0),
226 | data: crate::verification::ProofData::Memory(crate::verification::MemoryProof {
227 | operation: crate::verification::MemoryOpType::Modify,
228 | address: VirtAddr::new(0),
229 | size: 0,
230 | frame_hash: op_hash,
231 | }),
232 | signature: [0; 64],
233 | })
234 | }
235 |
236 | fn verify_proof(&self, proof: &OperationProof) -> Result {
237 | let current_state = self.state_hash.load(Ordering::SeqCst);
238 | Ok(Hash(current_state) == proof.new_state)
239 | }
240 |
241 | fn state_hash(&self) -> Hash {
242 | Hash(self.state_hash.load(Ordering::SeqCst))
243 | }
244 | }
245 |
246 | lazy_static! {
247 | pub static ref SWAP_MANAGER: Mutex = Mutex::new(SwapManager::new());
248 | }
249 |
--------------------------------------------------------------------------------
/src/time.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::serial_println;
18 | use alloc::vec::Vec;
19 | use core::sync::atomic::{AtomicU64, Ordering};
20 | use lazy_static::lazy_static;
21 | use spin::Mutex;
22 | use x86_64::instructions::port::Port;
23 |
24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25 | pub struct Timestamp {
26 | pub secs: u64,
27 | pub nanos: u32,
28 | }
29 |
30 | impl Timestamp {
31 | pub fn new(secs: u64, nanos: u32) -> Self {
32 | Self { secs, nanos }
33 | }
34 |
35 | pub fn now() -> Self {
36 | serial_println!("DEBUG: Creating Timestamp::now()");
37 |
38 | let ticks = {
39 | let ticks = SYSTEM_TIME.ticks();
40 | serial_println!("DEBUG: Current ticks: {}", ticks);
41 | ticks
42 | };
43 |
44 | let secs = ticks.checked_div(TICKS_PER_SECOND).unwrap_or(0);
45 | serial_println!("DEBUG: Calculated seconds: {}", secs);
46 |
47 | let nanos = ticks
48 | .checked_rem(TICKS_PER_SECOND)
49 | .and_then(|rem| rem.checked_mul(1_000_000_000))
50 | .and_then(|product| product.checked_div(TICKS_PER_SECOND))
51 | .map(|result| result as u32)
52 | .unwrap_or(0);
53 |
54 | serial_println!("DEBUG: Calculated nanoseconds: {}", nanos);
55 |
56 | let timestamp = Self { secs, nanos };
57 | serial_println!("DEBUG: Timestamp created successfully");
58 | timestamp
59 | }
60 | }
61 |
62 | const TICKS_PER_SECOND: u64 = 1000;
63 |
64 | pub struct SystemTime {
65 | ticks: AtomicU64,
66 | rtc: Mutex,
67 | }
68 |
69 | impl SystemTime {
70 | pub fn new() -> Self {
71 | serial_println!("DEBUG: Starting SystemTime construction");
72 |
73 | let system_time = Self {
74 | ticks: AtomicU64::new(0),
75 | rtc: Mutex::new(RealTimeClock::new()),
76 | };
77 | serial_println!("DEBUG: Base SystemTime structure created");
78 |
79 | serial_println!("DEBUG: SystemTime construction complete");
80 | system_time
81 | }
82 |
83 | pub fn tick(&self) {
84 | self.ticks.fetch_add(1, Ordering::Relaxed);
85 | }
86 |
87 | pub fn ticks(&self) -> u64 {
88 | self.ticks.load(Ordering::Relaxed)
89 | }
90 | }
91 |
92 | struct RealTimeClock {
93 | cmos: Cmos,
94 | last_update: Timestamp,
95 | }
96 |
97 | impl RealTimeClock {
98 | pub fn new() -> Self {
99 | serial_println!("DEBUG: Creating new RealTimeClock");
100 | let rtc = Self {
101 | cmos: Cmos::new(),
102 | last_update: Timestamp { secs: 0, nanos: 0 },
103 | };
104 | serial_println!("DEBUG: RealTimeClock creation complete");
105 | rtc
106 | }
107 | }
108 |
109 | struct Cmos {
110 | addr: Port,
111 | data: Port,
112 | }
113 |
114 | impl Cmos {
115 | pub fn new() -> Self {
116 | Self {
117 | addr: Port::new(0x70),
118 | data: Port::new(0x71),
119 | }
120 | }
121 |
122 | fn read_register(&mut self, reg: u8) -> u8 {
123 | unsafe {
124 | self.addr.write(reg);
125 | self.data.read()
126 | }
127 | }
128 |
129 | pub fn read_time(&mut self) -> Timestamp {
130 | while self.read_register(0x0A) & 0x80 != 0 {}
131 |
132 | let registers = [
133 | self.read_register(0x00),
134 | self.read_register(0x02),
135 | self.read_register(0x04),
136 | self.read_register(0x07),
137 | self.read_register(0x08),
138 | self.read_register(0x09),
139 | ];
140 |
141 | let values: Vec = registers
142 | .iter()
143 | .map(|®| self.bcd_to_binary(reg))
144 | .collect();
145 |
146 | self.date_to_timestamp(
147 | values[5], values[4], values[3], values[2], values[1], values[0],
148 | )
149 | }
150 |
151 | fn bcd_to_binary(&self, bcd: u8) -> u8 {
152 | (bcd & 0xF) + ((bcd >> 4) * 10)
153 | }
154 |
155 | fn date_to_timestamp(
156 | &self,
157 | year: u8,
158 | month: u8,
159 | day: u8,
160 | hour: u8,
161 | minute: u8,
162 | second: u8,
163 | ) -> Timestamp {
164 | let years_since_1970 = 2000u64 + year as u64 - 1970;
165 | let days = years_since_1970 * 365 + years_since_1970 / 4 + day as u64 - 1;
166 |
167 | let month_days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
168 | let days = days + month_days[month as usize - 1] as u64;
169 |
170 | let secs = days * 86400 + hour as u64 * 3600 + minute as u64 * 60 + second as u64;
171 |
172 | Timestamp::new(secs, 0)
173 | }
174 | }
175 |
176 | lazy_static! {
177 | pub static ref SYSTEM_TIME: SystemTime = SystemTime::new();
178 | }
179 |
180 | pub fn init() {
181 | serial_println!("Starting time system initialization...");
182 |
183 | let system_time = SYSTEM_TIME.ticks();
184 | serial_println!("Initial system ticks value: {}", system_time);
185 |
186 | for _ in 0..1000 {
187 | core::hint::spin_loop();
188 | }
189 |
190 | {
191 | let rtc = RealTimeClock::new();
192 | serial_println!("RTC created successfully");
193 |
194 | serial_println!("Time system base initialization complete");
195 | }
196 |
197 | serial_println!("Time system initialization complete");
198 | }
199 |
--------------------------------------------------------------------------------
/src/tsc.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | pub fn read_tsc() -> u64 {
18 | unsafe {
19 | let low: u32;
20 | let high: u32;
21 | core::arch::asm!("rdtsc", out("eax") low, out("edx") high);
22 | ((high as u64) << 32) | (low as u64)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/tty.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use crate::serial_println;
18 | use crate::vga_buffer::WRITER;
19 | use alloc::collections::VecDeque;
20 | use alloc::vec::Vec;
21 | use core::sync::atomic::{AtomicBool, Ordering};
22 | use lazy_static::lazy_static;
23 | use spin::Mutex;
24 |
25 | const INPUT_BUF_SIZE: usize = 1024;
26 | const OUTPUT_BUF_SIZE: usize = 1024;
27 | const MAX_CANON_LINE: usize = 256;
28 |
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30 | pub enum TtyMode {
31 | Canonical,
32 | Raw,
33 | }
34 |
35 | #[derive(Debug, Clone, Copy)]
36 | pub struct TtySettings {
37 | pub echo: bool,
38 | pub mode: TtyMode,
39 | pub process_backspace: bool,
40 | pub signals_enabled: bool,
41 | }
42 |
43 | impl Default for TtySettings {
44 | fn default() -> Self {
45 | Self {
46 | echo: true,
47 | mode: TtyMode::Canonical,
48 | process_backspace: true,
49 | signals_enabled: true,
50 | }
51 | }
52 | }
53 |
54 | pub struct TtyBuffer {
55 | data: VecDeque,
56 | capacity: usize,
57 | }
58 |
59 | impl TtyBuffer {
60 | pub fn new(capacity: usize) -> Self {
61 | Self {
62 | data: VecDeque::with_capacity(capacity),
63 | capacity,
64 | }
65 | }
66 |
67 | pub fn push(&mut self, byte: u8) -> bool {
68 | if self.data.len() < self.capacity {
69 | self.data.push_back(byte);
70 | true
71 | } else {
72 | false
73 | }
74 | }
75 |
76 | pub fn pop(&mut self) -> Option {
77 | self.data.pop_front()
78 | }
79 |
80 | pub fn peek(&self) -> Option {
81 | self.data.front().copied()
82 | }
83 |
84 | pub fn is_empty(&self) -> bool {
85 | self.data.is_empty()
86 | }
87 |
88 | pub fn clear(&mut self) {
89 | self.data.clear();
90 | }
91 |
92 | pub fn len(&self) -> usize {
93 | self.data.len()
94 | }
95 |
96 | pub fn available_space(&self) -> usize {
97 | self.capacity - self.data.len()
98 | }
99 | }
100 |
101 | pub struct LineDiscipline {
102 | canonical_buf: Vec,
103 | settings: TtySettings,
104 | }
105 |
106 | impl LineDiscipline {
107 | pub fn new() -> Self {
108 | Self {
109 | canonical_buf: Vec::with_capacity(MAX_CANON_LINE),
110 | settings: TtySettings::default(),
111 | }
112 | }
113 |
114 | pub fn process_input(
115 | &mut self,
116 | byte: u8,
117 | input_buf: &mut TtyBuffer,
118 | output_buf: &mut TtyBuffer,
119 | ) -> bool {
120 | match self.settings.mode {
121 | TtyMode::Canonical => self.handle_canonical_input(byte, input_buf, output_buf),
122 | TtyMode::Raw => self.handle_raw_input(byte, input_buf, output_buf),
123 | }
124 | }
125 |
126 | fn handle_canonical_input(
127 | &mut self,
128 | byte: u8,
129 | input_buf: &mut TtyBuffer,
130 | output_buf: &mut TtyBuffer,
131 | ) -> bool {
132 | match byte {
133 | b'\r' | b'\n' => {
134 | if self.settings.echo {
135 | output_buf.push(b'\n');
136 | WRITER.lock().write_byte(b'\n');
137 | }
138 | for &b in &self.canonical_buf {
139 | input_buf.push(b);
140 | }
141 | input_buf.push(b'\n');
142 | self.canonical_buf.clear();
143 | true
144 | }
145 | 8 | 127 => {
146 | if self.settings.process_backspace && !self.canonical_buf.is_empty() {
147 | self.canonical_buf.pop();
148 | if self.settings.echo {
149 | WRITER.lock().write_byte(8);
150 | WRITER.lock().write_byte(b' ');
151 | WRITER.lock().write_byte(8);
152 |
153 | output_buf.push(8);
154 | output_buf.push(b' ');
155 | output_buf.push(8);
156 | }
157 | }
158 | false
159 | }
160 | _ => {
161 | if self.canonical_buf.len() < MAX_CANON_LINE {
162 | self.canonical_buf.push(byte);
163 | if self.settings.echo {
164 | output_buf.push(byte);
165 | WRITER.lock().write_byte(byte);
166 | }
167 | }
168 | false
169 | }
170 | }
171 | }
172 |
173 | fn handle_raw_input(
174 | &mut self,
175 | byte: u8,
176 | input_buf: &mut TtyBuffer,
177 | output_buf: &mut TtyBuffer,
178 | ) -> bool {
179 | if self.settings.echo {
180 | output_buf.push(byte);
181 | }
182 | input_buf.push(byte);
183 | true
184 | }
185 | }
186 |
187 | pub struct Tty {
188 | input_buffer: TtyBuffer,
189 | output_buffer: TtyBuffer,
190 | line_discipline: LineDiscipline,
191 | settings: TtySettings,
192 | locked: AtomicBool,
193 | }
194 |
195 | impl Tty {
196 | pub fn new() -> Self {
197 | Self {
198 | input_buffer: TtyBuffer::new(INPUT_BUF_SIZE),
199 | output_buffer: TtyBuffer::new(OUTPUT_BUF_SIZE),
200 | line_discipline: LineDiscipline::new(),
201 | settings: TtySettings::default(),
202 | locked: AtomicBool::new(false),
203 | }
204 | }
205 |
206 | pub fn write(&mut self, buf: &[u8]) -> usize {
207 | let mut written = 0;
208 | for &byte in buf {
209 | if self.output_buffer.push(byte) {
210 | written += 1;
211 | WRITER.lock().write_byte(byte);
212 | } else {
213 | break;
214 | }
215 | }
216 | written
217 | }
218 |
219 | pub fn read(&mut self, buf: &mut [u8]) -> usize {
220 | let mut read = 0;
221 | while read < buf.len() {
222 | if let Some(byte) = self.input_buffer.pop() {
223 | buf[read] = byte;
224 | read += 1;
225 | if byte == b'\n' && self.settings.mode == TtyMode::Canonical {
226 | break;
227 | }
228 | } else {
229 | break;
230 | }
231 | }
232 | read
233 | }
234 |
235 | pub fn process_input(&mut self, byte: u8) {
236 | if !self.locked.load(Ordering::SeqCst) {
237 | self.line_discipline.process_input(
238 | byte,
239 | &mut self.input_buffer,
240 | &mut self.output_buffer,
241 | );
242 | }
243 | }
244 |
245 | pub fn set_mode(&mut self, mode: TtyMode) {
246 | self.settings.mode = mode;
247 | self.line_discipline.settings.mode = mode;
248 | }
249 |
250 | pub fn set_echo(&mut self, echo: bool) {
251 | self.settings.echo = echo;
252 | self.line_discipline.settings.echo = echo;
253 | }
254 |
255 | pub fn lock(&self) {
256 | self.locked.store(true, Ordering::SeqCst);
257 | }
258 |
259 | pub fn unlock(&self) {
260 | self.locked.store(false, Ordering::SeqCst);
261 | }
262 |
263 | pub fn flush_output(&mut self) {
264 | while let Some(byte) = self.output_buffer.pop() {
265 | WRITER.lock().write_byte(byte);
266 | }
267 | }
268 | }
269 |
270 | lazy_static! {
271 | pub static ref CONSOLE_TTY: Mutex = Mutex::new(Tty::new());
272 | }
273 |
274 | pub fn write_tty(buf: &[u8]) -> usize {
275 | CONSOLE_TTY.lock().write(buf)
276 | }
277 |
278 | pub fn read_tty(buf: &mut [u8]) -> usize {
279 | let mut tty = CONSOLE_TTY.lock();
280 |
281 | if tty.settings.mode == TtyMode::Canonical {
282 | if !tty.input_buffer.data.contains(&b'\n') {
283 | return 0;
284 | }
285 | }
286 |
287 | tty.read(buf)
288 | }
289 |
290 | pub fn process_keyboard_input(byte: u8) {
291 | CONSOLE_TTY.lock().process_input(byte);
292 | }
293 |
294 | pub fn init() {
295 | serial_println!("Initializing TTY subsystem...");
296 | let mut tty = CONSOLE_TTY.lock();
297 | tty.set_mode(TtyMode::Canonical);
298 | tty.set_echo(true);
299 | serial_println!("TTY initialization complete");
300 | }
301 |
--------------------------------------------------------------------------------
/src/vga_buffer.rs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023-2024 Juan Miguel Giraldo
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | use core::fmt;
18 | use lazy_static::lazy_static;
19 | use spin::Mutex;
20 | use volatile::Volatile;
21 |
22 | #[allow(dead_code)]
23 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
24 | #[repr(u8)]
25 | pub enum Color {
26 | Black = 0,
27 | Blue = 1,
28 | Green = 2,
29 | Cyan = 3,
30 | Red = 4,
31 | Magenta = 5,
32 | Brown = 6,
33 | LightGray = 7,
34 | DarkGray = 8,
35 | LightBlue = 9,
36 | LightGreen = 10,
37 | LightCyan = 11,
38 | LightRed = 12,
39 | Pink = 13,
40 | Yellow = 14,
41 | White = 15,
42 | }
43 |
44 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
45 | #[repr(transparent)]
46 | pub struct ColorCode(u8);
47 |
48 | impl ColorCode {
49 | pub fn new(foreground: Color, background: Color) -> ColorCode {
50 | ColorCode((background as u8) << 4 | (foreground as u8))
51 | }
52 | }
53 |
54 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55 | #[repr(C)]
56 | struct ScreenChar {
57 | ascii_character: u8,
58 | color_code: ColorCode,
59 | }
60 |
61 | pub const BUFFER_WIDTH: usize = 80;
62 | pub const BUFFER_HEIGHT: usize = 25;
63 |
64 | struct Buffer {
65 | chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT],
66 | }
67 |
68 | pub struct Writer {
69 | pub column_position: usize,
70 | pub color_code: ColorCode,
71 | buffer: &'static mut Buffer,
72 | }
73 |
74 | impl Writer {
75 | pub fn clear_screen(&mut self) {
76 | for row in 0..BUFFER_HEIGHT {
77 | self.clear_row(row);
78 | }
79 | self.column_position = 0;
80 | }
81 |
82 | pub fn write_byte(&mut self, byte: u8) {
83 | match byte {
84 | b'\n' => self.new_line(),
85 | byte => {
86 | if self.column_position >= BUFFER_WIDTH {
87 | self.new_line();
88 | }
89 |
90 | let row = BUFFER_HEIGHT - 1;
91 | let col = self.column_position;
92 |
93 | let color_code = self.color_code;
94 | self.buffer.chars[row][col].write(ScreenChar {
95 | ascii_character: byte,
96 | color_code,
97 | });
98 | self.column_position += 1;
99 | }
100 | }
101 | }
102 |
103 | pub fn set_cursor_position(&mut self, x: usize, y: usize) {
104 | let pos = y * BUFFER_WIDTH + x;
105 | unsafe {
106 | x86_64::instructions::port::Port::new(0x3D4).write(0x0Fu8);
107 | x86_64::instructions::port::Port::new(0x3D5).write((pos & 0xFF) as u8);
108 | x86_64::instructions::port::Port::new(0x3D4).write(0x0Eu8);
109 | x86_64::instructions::port::Port::new(0x3D5).write(((pos >> 8) & 0xFF) as u8);
110 | }
111 | }
112 |
113 | pub fn enable_cursor(&mut self) {
114 | unsafe {
115 | x86_64::instructions::port::Port::new(0x3D4).write(0x0Au8);
116 | x86_64::instructions::port::Port::new(0x3D5).write(0x00u8);
117 | x86_64::instructions::port::Port::new(0x3D4).write(0x0Bu8);
118 | x86_64::instructions::port::Port::new(0x3D5).write(0x0Fu8);
119 | }
120 | }
121 |
122 | pub fn new_line(&mut self) {
123 | for row in 1..BUFFER_HEIGHT {
124 | for col in 0..BUFFER_WIDTH {
125 | let character = self.buffer.chars[row][col].read();
126 | self.buffer.chars[row - 1][col].write(character);
127 | }
128 | }
129 | self.clear_row(BUFFER_HEIGHT - 1);
130 | self.column_position = 0;
131 | }
132 |
133 | fn clear_row(&mut self, row: usize) {
134 | let blank = ScreenChar {
135 | ascii_character: b' ',
136 | color_code: self.color_code,
137 | };
138 | for col in 0..BUFFER_WIDTH {
139 | self.buffer.chars[row][col].write(blank);
140 | }
141 | }
142 |
143 | pub fn flush(&mut self) {
144 | self.set_cursor_position(self.column_position, BUFFER_HEIGHT - 1);
145 | }
146 | }
147 |
148 | lazy_static! {
149 | pub static ref WRITER: Mutex = Mutex::new(Writer {
150 | column_position: 0,
151 | color_code: ColorCode::new(Color::Yellow, Color::Black),
152 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
153 | });
154 | }
155 |
156 | impl fmt::Write for Writer {
157 | fn write_str(&mut self, s: &str) -> fmt::Result {
158 | for byte in s.bytes() {
159 | self.write_byte(byte);
160 | }
161 | Ok(())
162 | }
163 | }
164 |
165 | #[macro_export]
166 | macro_rules! println {
167 | () => ($crate::print!("\n"));
168 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
169 | }
170 |
171 | #[macro_export]
172 | macro_rules! print {
173 | ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
174 | }
175 |
176 | pub fn _print(args: fmt::Arguments) {
177 | use core::fmt::Write;
178 | WRITER.lock().write_fmt(args).unwrap();
179 | }
180 |
--------------------------------------------------------------------------------
/x86_64-vekos.json:
--------------------------------------------------------------------------------
1 | {
2 | "llvm-target": "x86_64-unknown-none",
3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
4 | "arch": "x86_64",
5 | "target-endian": "little",
6 | "target-pointer-width": "64",
7 | "target-c-int-width": "32",
8 | "os": "none",
9 | "executables": true,
10 | "linker-flavor": "ld.lld",
11 | "linker": "rust-lld",
12 | "panic-strategy": "abort",
13 | "disable-redzone": true,
14 | "features": "-mmx,-sse,+soft-float",
15 | "cpu": "x86-64",
16 | "position-independent-executables": false,
17 | "max-atomic-width": 64,
18 | "tls-model": "initial-exec"
19 | }
--------------------------------------------------------------------------------