├── .github └── workflows │ └── check.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── dmesg ├── Cargo.toml └── src │ └── main.rs ├── fdisk ├── Cargo.toml └── src │ ├── crc32.rs │ ├── disk.rs │ ├── guid.rs │ ├── lib.rs │ ├── main.rs │ └── partition.rs ├── insmod ├── Cargo.toml └── src │ └── main.rs ├── login ├── Cargo.toml └── src │ └── main.rs ├── lsmod ├── Cargo.toml └── src │ └── main.rs ├── mkfs ├── Cargo.toml └── src │ ├── ext2.rs │ └── main.rs ├── mount ├── Cargo.toml └── src │ └── main.rs ├── nologin ├── Cargo.toml └── src │ └── main.rs ├── powerctl ├── Cargo.toml └── src │ ├── main.rs │ └── power.rs ├── ps ├── Cargo.toml └── src │ ├── format.rs │ ├── main.rs │ ├── process │ ├── mod.rs │ └── status_parser.rs │ └── util.rs ├── rmmod ├── Cargo.toml └── src │ └── main.rs ├── su ├── Cargo.toml └── src │ └── main.rs ├── umount ├── Cargo.toml └── src │ └── main.rs ├── usrgrp ├── Cargo.toml └── src │ └── main.rs └── utils ├── Cargo.toml └── src ├── disk.rs ├── lib.rs ├── prompt.rs ├── user.rs └── util.rs /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | on: push 3 | jobs: 4 | build: 5 | runs-on: [self-hosted, linux] 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: Debug 9 | run: cargo +nightly build 10 | - name: Release 11 | run: cargo +nightly build --release 12 | test: 13 | runs-on: [self-hosted, linux] 14 | needs: build 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Test 18 | run: cargo +nightly test 19 | format: 20 | runs-on: [self-hosted, linux] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: cargo +nightly fmt --check 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "argon2" 7 | version = "0.5.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" 10 | dependencies = [ 11 | "base64ct", 12 | "blake2", 13 | "cpufeatures", 14 | "password-hash", 15 | ] 16 | 17 | [[package]] 18 | name = "base64ct" 19 | version = "1.6.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 22 | 23 | [[package]] 24 | name = "blake2" 25 | version = "0.10.6" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 28 | dependencies = [ 29 | "digest", 30 | ] 31 | 32 | [[package]] 33 | name = "block-buffer" 34 | version = "0.10.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 37 | dependencies = [ 38 | "generic-array", 39 | ] 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "1.0.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 46 | 47 | [[package]] 48 | name = "cpufeatures" 49 | version = "0.2.11" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" 52 | dependencies = [ 53 | "libc", 54 | ] 55 | 56 | [[package]] 57 | name = "crypto-common" 58 | version = "0.1.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 61 | dependencies = [ 62 | "generic-array", 63 | "typenum", 64 | ] 65 | 66 | [[package]] 67 | name = "digest" 68 | version = "0.10.7" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 71 | dependencies = [ 72 | "block-buffer", 73 | "crypto-common", 74 | "subtle", 75 | ] 76 | 77 | [[package]] 78 | name = "dmesg" 79 | version = "0.1.0" 80 | 81 | [[package]] 82 | name = "fdisk" 83 | version = "0.1.0" 84 | dependencies = [ 85 | "libc", 86 | "utils", 87 | ] 88 | 89 | [[package]] 90 | name = "generic-array" 91 | version = "0.14.7" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 94 | dependencies = [ 95 | "typenum", 96 | "version_check", 97 | ] 98 | 99 | [[package]] 100 | name = "getrandom" 101 | version = "0.2.11" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 104 | dependencies = [ 105 | "cfg-if", 106 | "libc", 107 | "wasi", 108 | ] 109 | 110 | [[package]] 111 | name = "insmod" 112 | version = "0.1.0" 113 | dependencies = [ 114 | "libc", 115 | "utils", 116 | ] 117 | 118 | [[package]] 119 | name = "libc" 120 | version = "0.2.150" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 123 | 124 | [[package]] 125 | name = "login" 126 | version = "0.1.0" 127 | dependencies = [ 128 | "libc", 129 | "utils", 130 | ] 131 | 132 | [[package]] 133 | name = "lsmod" 134 | version = "0.1.0" 135 | 136 | [[package]] 137 | name = "mkfs" 138 | version = "0.1.0" 139 | dependencies = [ 140 | "utils", 141 | ] 142 | 143 | [[package]] 144 | name = "mount" 145 | version = "0.1.0" 146 | dependencies = [ 147 | "libc", 148 | ] 149 | 150 | [[package]] 151 | name = "nologin" 152 | version = "0.1.0" 153 | 154 | [[package]] 155 | name = "password-hash" 156 | version = "0.5.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 159 | dependencies = [ 160 | "base64ct", 161 | "rand_core", 162 | "subtle", 163 | ] 164 | 165 | [[package]] 166 | name = "powerctl" 167 | version = "0.1.0" 168 | dependencies = [ 169 | "libc", 170 | "utils", 171 | ] 172 | 173 | [[package]] 174 | name = "ps" 175 | version = "0.1.0" 176 | dependencies = [ 177 | "utils", 178 | ] 179 | 180 | [[package]] 181 | name = "rand_core" 182 | version = "0.6.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 185 | dependencies = [ 186 | "getrandom", 187 | ] 188 | 189 | [[package]] 190 | name = "rmmod" 191 | version = "0.1.0" 192 | dependencies = [ 193 | "libc", 194 | "utils", 195 | ] 196 | 197 | [[package]] 198 | name = "su" 199 | version = "0.1.0" 200 | dependencies = [ 201 | "utils", 202 | ] 203 | 204 | [[package]] 205 | name = "subtle" 206 | version = "2.5.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 209 | 210 | [[package]] 211 | name = "typenum" 212 | version = "1.17.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 215 | 216 | [[package]] 217 | name = "umount" 218 | version = "0.1.0" 219 | dependencies = [ 220 | "libc", 221 | ] 222 | 223 | [[package]] 224 | name = "usrgrp" 225 | version = "0.1.0" 226 | 227 | [[package]] 228 | name = "utils" 229 | version = "0.1.0" 230 | dependencies = [ 231 | "argon2", 232 | "libc", 233 | "rand_core", 234 | ] 235 | 236 | [[package]] 237 | name = "version_check" 238 | version = "0.9.4" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 241 | 242 | [[package]] 243 | name = "wasi" 244 | version = "0.11.0+wasi-snapshot-preview1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 247 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | 5 | members = [ 6 | "dmesg", 7 | "fdisk", 8 | "insmod", 9 | "login", 10 | "lsmod", 11 | "mkfs", 12 | "mount", 13 | "nologin", 14 | "powerctl", 15 | "ps", 16 | "rmmod", 17 | "su", 18 | "umount", 19 | "usrgrp", 20 | "utils", 21 | ] 22 | 23 | [profile.release] 24 | panic = "abort" 25 | strip = true 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luc Lenôtre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | logo 5 | 6 |

7 | 8 | [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge&logo=book)](./LICENSE) 9 | ![Continuous integration](https://img.shields.io/github/actions/workflow/status/llenotre/maestro-utils/check.yml?style=for-the-badge&logo=github) 10 | 11 | 12 | 13 | # About 14 | 15 | maestro-utils provides utility commands for the Maestro operating system: 16 | - User management: 17 | - su 18 | - passwd 19 | - useradd 20 | - userdel 21 | - usermod 22 | - groupadd 23 | - groupdel 24 | - groupmod 25 | - login 26 | - nologin 27 | - Kernel utilities: 28 | - dmesg 29 | - mount 30 | - umount 31 | - insmod 32 | - rmmod 33 | - lsmod 34 | - Filesystem utilities: 35 | - fdisk 36 | - sfdisk 37 | - mkfs 38 | - fsck 39 | - System power: 40 | - halt 41 | - poweroff 42 | - shutdown 43 | - reboot 44 | -------------------------------------------------------------------------------- /dmesg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dmesg" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /dmesg/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `dmesg` command allows to print the kernel's logs. 2 | 3 | use std::env; 4 | 5 | /// The path to the kmsg device file. 6 | const KMSG_PATH: &str = "/dev/kmsg"; 7 | 8 | fn main() { 9 | let _args: Vec = env::args().collect(); 10 | // TODO parse arguments 11 | 12 | // TODO read non blocking from file 13 | // TODO for each line: 14 | // - split once with `;` 15 | // - split left with `,`, then retrieve time, facility and level 16 | // - format and print 17 | } 18 | -------------------------------------------------------------------------------- /fdisk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fdisk" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "fdisk" 8 | path = "src/lib.rs" 9 | 10 | [[bin]] 11 | name = "fdisk" 12 | path = "src/main.rs" 13 | 14 | [dependencies] 15 | libc = "*" 16 | utils = { path = "../utils" } 17 | -------------------------------------------------------------------------------- /fdisk/src/crc32.rs: -------------------------------------------------------------------------------- 1 | //! TODO doc 2 | 3 | // TODO 4 | 5 | /// Computes the lookup table for the given generator polynomial. 6 | /// 7 | /// Arguments: 8 | /// - `table` is filled with the table's values. 9 | /// - `polynom` is the polynom. 10 | pub fn compute_lookuptable(table: &mut [u32; 256], polynom: u32) { 11 | // Little endian 12 | let mut i = table.len() / 2; 13 | let mut crc = 1; 14 | 15 | while i > 0 { 16 | if crc & 1 != 0 { 17 | crc = (crc >> 1) ^ polynom; 18 | } else { 19 | crc >>= 1; 20 | } 21 | 22 | for j in (0..table.len()).step_by(2 * i) { 23 | table[i ^ j] = crc ^ table[j]; 24 | } 25 | 26 | i >>= 1; 27 | } 28 | } 29 | 30 | /// Computes the CRC32 checksum on the given data `data` with the given table 31 | /// `table` for the wanted generator polynomial. 32 | pub fn compute(data: &[u8], table: &[u32; 256]) -> u32 { 33 | // Sarwate algorithm 34 | let mut crc = !0u32; 35 | 36 | for b in data { 37 | let i = ((crc as usize) ^ (*b as usize)) & 0xff; 38 | crc = table[i] ^ (crc >> 8); 39 | } 40 | 41 | !crc 42 | } 43 | 44 | // TODO Test CRC32 45 | -------------------------------------------------------------------------------- /fdisk/src/disk.rs: -------------------------------------------------------------------------------- 1 | //! TODO doc 2 | 3 | use crate::partition::PartitionTable; 4 | use libc::c_long; 5 | use libc::ioctl; 6 | use std::fmt; 7 | use std::fs; 8 | use std::fs::{File, OpenOptions}; 9 | use std::io; 10 | use std::io::Error; 11 | use std::os::fd::AsRawFd; 12 | use std::path::Path; 13 | use std::path::PathBuf; 14 | use utils::util::ByteSize; 15 | 16 | /// ioctl command: Read a partitions table. 17 | const BLKRRPART: c_long = 0x125f; 18 | 19 | /// Structure representing a disk, containing partitions. 20 | pub struct Disk { 21 | /// The path to the disk's device file. 22 | dev_path: PathBuf, 23 | /// The size of the disk in number of sectors. 24 | size: u64, 25 | /// The open device. 26 | dev: File, 27 | 28 | /// The partition table. 29 | pub partition_table: PartitionTable, 30 | } 31 | 32 | impl Disk { 33 | /// Tells whether the device file at the given path is a valid disk. 34 | /// 35 | /// This function is meant to be used when listing disks. 36 | fn is_valid(path: &Path) -> bool { 37 | let Some(path_str) = path.as_os_str().to_str() else { 38 | return false; 39 | }; 40 | 41 | if path_str.starts_with("/dev/sd") && !path_str.contains(|c: char| c.is_numeric()) { 42 | return true; 43 | } 44 | if path_str.starts_with("/dev/hd") && !path_str.contains(|c: char| c.is_numeric()) { 45 | return true; 46 | } 47 | if path_str.starts_with("/dev/nvme0n") && !path_str.contains('p') { 48 | // FIXME 49 | return true; 50 | } 51 | 52 | // TODO Add floppy, cdrom, etc... 53 | 54 | false 55 | } 56 | 57 | /// Reads a disk's informations from the given device path `dev_path`. 58 | /// 59 | /// If the path doesn't point to a valid device, the function returns None. 60 | pub fn read(dev_path: PathBuf) -> io::Result> { 61 | let mut dev = OpenOptions::new().read(true).write(true).open(&dev_path)?; 62 | let Ok(size) = utils::disk::get_disk_size(&dev) else { 63 | return Ok(None); 64 | }; 65 | let partition_table = PartitionTable::read(&mut dev, size)?; 66 | Ok(Some(Self { 67 | dev_path, 68 | size, 69 | dev, 70 | 71 | partition_table, 72 | })) 73 | } 74 | 75 | /// Writes the partition table to the disk. 76 | pub fn write(&mut self) -> io::Result<()> { 77 | self.partition_table.write(&mut self.dev, self.size) 78 | } 79 | 80 | /// Lists disks present on the system. 81 | pub fn list() -> io::Result> { 82 | fs::read_dir("/dev")? 83 | .filter_map(|dev| match dev { 84 | Ok(dev) => { 85 | let dev_path = dev.path(); 86 | if Self::is_valid(&dev_path) { 87 | Some(Ok(dev_path)) 88 | } else { 89 | None 90 | } 91 | } 92 | Err(e) => Some(Err(e)), 93 | }) 94 | .collect() 95 | } 96 | 97 | /// Returns the path to the device file of the disk. 98 | pub fn get_path(&self) -> &Path { 99 | &self.dev_path 100 | } 101 | 102 | /// Returns the size of the disk in number of sectors. 103 | pub fn get_size(&self) -> u64 { 104 | self.size 105 | } 106 | } 107 | 108 | impl fmt::Display for Disk { 109 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 110 | let sector_size = 512; // TODO check if this value can be different 111 | let byte_size = self.size * sector_size; 112 | 113 | writeln!( 114 | fmt, 115 | "Disk {}: {}, {byte_size} bytes, {} sectors", 116 | self.dev_path.display(), 117 | ByteSize(byte_size), 118 | self.size 119 | )?; 120 | writeln!(fmt, "Disk model: TODO")?; // TODO 121 | writeln!( 122 | fmt, 123 | "Units: sectors of 1 * {sector_size} = {sector_size} bytes", 124 | )?; 125 | writeln!( 126 | fmt, 127 | "Sector size (logical/physical): {sector_size} bytes / {sector_size} bytes" 128 | )?; 129 | writeln!( 130 | fmt, 131 | "I/O size (minimum/optimal): {sector_size} bytes / {sector_size} bytes", 132 | )?; 133 | writeln!(fmt, "Disklabel type: {}", self.partition_table.table_type)?; 134 | writeln!(fmt, "Disk identifier: TODO")?; // TODO 135 | 136 | if !self.partition_table.partitions.is_empty() { 137 | writeln!(fmt, "\nDevice\tStart\tEnd\tSectors\tSize\tType")?; 138 | } 139 | 140 | for p in &self.partition_table.partitions { 141 | writeln!( 142 | fmt, 143 | "/dev/TODO\t{}\t{}\t{}\t{}\tTODO", // TODO 144 | p.start, 145 | p.start + p.size, 146 | p.size, 147 | ByteSize(p.size) 148 | )?; 149 | } 150 | 151 | Ok(()) 152 | } 153 | } 154 | 155 | /// Makes the kernel read the partition table for the given device. 156 | /// 157 | /// If called on a non-device file, the function does nothing. 158 | pub fn read_partitions(path: &Path) -> io::Result<()> { 159 | let dev = File::open(path)?; 160 | let ret = unsafe { ioctl(dev.as_raw_fd(), BLKRRPART as _, 0) }; 161 | if ret < 0 { 162 | let err = Error::last_os_error(); 163 | match err.raw_os_error() { 164 | // Inappropriate ioctl for device 165 | // This error is ignored because ioctl will fail when called for non-device files 166 | Some(25) => {} 167 | _ => return Err(err), 168 | } 169 | } 170 | Ok(()) 171 | } 172 | -------------------------------------------------------------------------------- /fdisk/src/guid.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of a Globally Unique IDentifier used in GPT partition tables. 2 | 3 | use std::fmt; 4 | use std::str::FromStr; 5 | 6 | /// Type representing a Globally Unique IDentifier. 7 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 8 | #[repr(C, packed)] 9 | pub struct GUID(pub [u8; 16]); 10 | 11 | impl FromStr for GUID { 12 | type Err = (); 13 | 14 | fn from_str(s: &str) -> Result { 15 | if s.len() != 36 { 16 | return Err(()); 17 | } 18 | if s.chars().any(|c| !c.is_alphanumeric() && c != '-') { 19 | return Err(()); 20 | } 21 | 22 | // Parse 23 | let iter = s 24 | .chars() 25 | .filter_map(|c| match c { 26 | c @ '0'..='9' => Some(c as u8 - b'0'), 27 | c @ 'A'..='F' => Some(c as u8 - b'A' + 10), 28 | c @ 'a'..='f' => Some(c as u8 - b'a' + 10), 29 | _ => None, 30 | }) 31 | .array_chunks::<2>() 32 | .map(|c| c[0] * 16 + c[1]) 33 | .enumerate(); 34 | // Fill array while reordering 35 | let mut guid = Self([0; 16]); 36 | for (i, b) in iter { 37 | // Reverse necessary parts 38 | let index = match i { 39 | 0..4 => 4 - i - 1, 40 | 4..6 => 6 - i - 1 + 4, 41 | 6..8 => 8 - i - 1 + 6, 42 | _ => i, 43 | }; 44 | guid.0[index] = b; 45 | } 46 | Ok(guid) 47 | } 48 | } 49 | 50 | impl GUID { 51 | /// Generates a random GUID. 52 | pub fn random() -> Self { 53 | let mut buf = [0; 16]; 54 | utils::util::get_random(&mut buf); 55 | Self(buf) 56 | } 57 | } 58 | 59 | impl fmt::Display for GUID { 60 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | for i in (0..4).rev() { 62 | write!(fmt, "{:02x}", self.0[i])?; 63 | } 64 | write!(fmt, "-")?; 65 | 66 | for i in 0..2 { 67 | for j in (0..2).rev() { 68 | write!(fmt, "{:02x}", self.0[4 + i * 2 + j])?; 69 | } 70 | write!(fmt, "-")?; 71 | } 72 | 73 | for i in 8..10 { 74 | write!(fmt, "{:02x}", self.0[i])?; 75 | } 76 | write!(fmt, "-")?; 77 | 78 | for i in 10..16 { 79 | write!(fmt, "{:02x}", self.0[i])?; 80 | } 81 | 82 | Ok(()) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | 90 | #[test] 91 | pub fn guid_parse_valid() { 92 | let guid = GUID::from_str("c12a7328-f81f-11d2-ba4b-00a0c93ec93b").unwrap(); 93 | assert_eq!( 94 | guid.0, 95 | [ 96 | 0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11, 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 97 | 0xc9, 0x3b 98 | ] 99 | ); 100 | 101 | let guid = GUID::from_str("C12A7328-F81F-11D2-BA4B-00A0C93EC93B").unwrap(); 102 | assert_eq!( 103 | guid.0, 104 | [ 105 | 0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11, 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 106 | 0xc9, 0x3b 107 | ] 108 | ); 109 | } 110 | 111 | #[test] 112 | pub fn guid_parse_invalid() { 113 | GUID::from_str("c12a7328f81f11d2ba4b00a0c93ec93b").unwrap_err(); 114 | GUID::from_str("c12a7328f81f11d2ba4b00a0c93ec93").unwrap_err(); 115 | GUID::from_str("c12a7328f81f11d2ba4b00a0c93ec93$").unwrap_err(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /fdisk/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `fdisk` library allows to use the functions of the fdisk command. 2 | 3 | #![feature(exclusive_range_pattern)] 4 | #![feature(iter_array_chunks)] 5 | 6 | pub mod crc32; 7 | pub mod disk; 8 | pub mod guid; 9 | pub mod partition; 10 | -------------------------------------------------------------------------------- /fdisk/src/main.rs: -------------------------------------------------------------------------------- 1 | //! `fdisk` is an utility command used to manipulate disk partition tables. 2 | //! 3 | //! The `sfdisk` is also implemented in the same program, it has the purpose as `fdisk`, except it 4 | //! uses scripting instead of prompting. 5 | 6 | #![feature(exclusive_range_pattern)] 7 | #![feature(iter_array_chunks)] 8 | 9 | mod crc32; 10 | mod disk; 11 | mod guid; 12 | mod partition; 13 | 14 | use crate::partition::PartitionTable; 15 | use disk::Disk; 16 | use std::env; 17 | use std::fs; 18 | use std::fs::OpenOptions; 19 | use std::io; 20 | use std::io::Write; 21 | use std::path::Path; 22 | use std::path::PathBuf; 23 | use std::process::exit; 24 | use std::str::FromStr; 25 | use utils::prompt::prompt; 26 | 27 | /// Structure storing command line arguments. 28 | #[derive(Default)] 29 | struct Args { 30 | /// The name of the current program used in command line. 31 | prog: String, 32 | /// Tells whether the command is run in scripting mode. 33 | script: bool, 34 | 35 | /// If true, print command line help. 36 | help: bool, 37 | 38 | /// If true, list partitions instead of modifying the table. 39 | list: bool, 40 | 41 | /// The list of disk devices. 42 | disks: Vec, 43 | } 44 | 45 | impl Args { 46 | /// Tells whether arguments are valid. 47 | fn is_valid(&self) -> bool { 48 | if self.help || self.list { 49 | return true; 50 | } 51 | self.disks.len() == 1 52 | } 53 | } 54 | 55 | fn parse_args() -> Args { 56 | let mut args: Args = Default::default(); 57 | 58 | let mut iter = env::args(); 59 | args.prog = iter.next().unwrap_or("fdisk".to_owned()); 60 | args.script = args.prog.split('/').last() == Some("sfdisk"); 61 | 62 | while let Some(arg) = iter.next() { 63 | match arg.as_str() { 64 | "-h" | "--help" => args.help = true, 65 | "-l" | "--list" => args.list = true, 66 | 67 | // TODO implement other options 68 | _ => args.disks.push(arg.into()), 69 | } 70 | } 71 | 72 | args 73 | } 74 | 75 | /// Prints command usage. 76 | /// 77 | /// `prog` is the name of the current program. 78 | fn print_usage(prog: &str) { 79 | eprintln!("{prog}: bad usage"); 80 | eprintln!("Try '{prog} --help' for more information."); 81 | } 82 | 83 | /// Prints command help. 84 | /// 85 | /// - `prog` is the name of the current program. 86 | /// - `script` tells whether the program is run as `sfdisk`. 87 | fn print_help(prog: &str, script: bool) { 88 | println!(); 89 | println!("Usage:"); 90 | println!(" {prog} [options] [disks...]"); 91 | println!(); 92 | println!("Prints the list of partitions or modify it."); 93 | println!(); 94 | println!("Options:"); 95 | println!(" -h, --help\tPrints help."); 96 | println!(" -l, --list\tLists partitions."); 97 | } 98 | 99 | /// Prints help for fdisk's internal commands. 100 | fn print_cmd_help() { 101 | println!(); 102 | println!("Help:"); 103 | println!(); 104 | println!(" DOS (MBR)"); 105 | println!(" a toggle a bootable flag"); 106 | println!(); 107 | println!(" Generic"); 108 | println!(" d delete a partition"); 109 | println!(" F list free unpartitioned space"); 110 | println!(" l list known partition types"); 111 | println!(" n add a new partition"); 112 | println!(" p print the partition table"); 113 | println!(" t change a partition type"); 114 | println!(" v verify the partition table"); 115 | println!(" i print information about a partition"); 116 | println!(); 117 | println!(" Misc"); 118 | println!(" m print this menu"); 119 | println!(); 120 | println!(" Script"); 121 | println!(" I load disk layout from sfdisk script file"); 122 | println!(" O dump disk layout to sfdisk script file"); 123 | println!(); 124 | println!(" Save & Exit"); 125 | println!(" w write table to disk and exit"); 126 | println!(" q quit without saving changes"); 127 | println!(); 128 | println!(" Create a new label"); 129 | println!(" g create a new empty GPT partition table"); 130 | println!(" o create a new empty DOS partition table"); 131 | println!(); 132 | } 133 | 134 | /// Imports the script in the file at the given path and applies it to the given disk. 135 | fn import_script(disk: &mut Disk, path: &Path) -> io::Result<()> { 136 | let script = fs::read_to_string(path)?; 137 | // TODO handle error 138 | disk.partition_table = PartitionTable::from_str(&script).unwrap(); 139 | Ok(()) 140 | } 141 | 142 | /// Exports the given disk as a script to the file at the given path. 143 | fn export_script(disk: &Disk, path: &Path) -> io::Result<()> { 144 | let mut script_file = OpenOptions::new() 145 | .create(true) 146 | .write(true) 147 | .truncate(true) 148 | .open(path)?; 149 | let serialized = disk.partition_table.serialize(path); 150 | script_file.write_all(serialized.as_bytes())?; 151 | script_file.flush()?; 152 | Ok(()) 153 | } 154 | 155 | /// TODO doc 156 | /// 157 | /// If modifications have been made, the function returns `true`. 158 | fn handle_cmd(cmd: &str, disk_path: &Path, disk: &mut Disk) { 159 | match cmd { 160 | "a" => { 161 | // TODO check whether partition table type supports boot flag. If not, error 162 | // TODO select partition and toggle boot flag 163 | } 164 | 165 | "d" => { 166 | // TODO: 167 | // - If only one partition is present, use it and print `Selected partition 1` 168 | // - Else, prompt `Partition number (1,2,... default <>): ` 169 | // - Delete partition and print `Partition <> has been deleted.` 170 | } 171 | 172 | "F" => todo!(), // TODO 173 | 174 | "l" => disk.partition_table.table_type.print_partition_types(), 175 | 176 | "n" => { 177 | let _new_partition = disk.partition_table.table_type.prompt_new_partition(); 178 | // TODO insert new partition to disk 179 | } 180 | 181 | "p" => println!("{disk}\n"), 182 | 183 | "t" => { 184 | // TODO: 185 | // - If only one partition is present, use it and print `Selected partition 1` 186 | // - Else, prompt `Partition number (1,2,... default <>): ` 187 | // - Prompt `Partition type (type L to list all): ` 188 | // - On L: partition_table_type.print_partition_types() 189 | // - Change type and print `Changed type of partition `` to ``` 190 | } 191 | 192 | "v" => todo!(), // TODO 193 | 194 | "i" => todo!(), // TODO 195 | 196 | "m" => print_cmd_help(), 197 | 198 | "I" => { 199 | if let Some(script_path) = prompt(Some("Enter script file name: "), false) { 200 | let script_path = PathBuf::from(script_path); 201 | 202 | match import_script(disk, &script_path) { 203 | Ok(_) => println!("\nScript successfully applied.\n"), 204 | Err(e) => eprintln!("cannot import script {}: {e}", script_path.display()), 205 | } 206 | } 207 | } 208 | 209 | "O" => { 210 | if let Some(script_path) = prompt(Some("Enter script file name: "), false) { 211 | let script_path = PathBuf::from(script_path); 212 | 213 | match export_script(disk, &script_path) { 214 | Ok(_) => println!("\nScript successfully saved.\n"), 215 | Err(e) => eprintln!("cannot export script {}: {e}", script_path.display()), 216 | } 217 | } 218 | } 219 | 220 | "w" => { 221 | // TODO ask only if modifications have been made 222 | let prompt_str = format!("Write changes to `{}`? (y/n) ", disk_path.display()); 223 | let confirm = prompt(Some(&prompt_str), false) 224 | .map(|s| s == "y") 225 | .unwrap_or(false); 226 | if !confirm { 227 | return; 228 | } 229 | 230 | match disk.write() { 231 | Ok(_) => println!("The partition table has been altered."), 232 | Err(e) => { 233 | eprintln!("cannot write to disk `{}`: {e}", disk_path.display()); 234 | exit(1); 235 | } 236 | } 237 | 238 | match disk::read_partitions(disk.get_path()) { 239 | Ok(_) => println!("Syncing disks."), 240 | Err(e) => { 241 | eprintln!( 242 | "cannot read partition table from `{}`: {e}", 243 | disk_path.display() 244 | ); 245 | exit(1); 246 | } 247 | } 248 | 249 | exit(0); 250 | } 251 | 252 | "q" => exit(0), 253 | 254 | "g" => { 255 | // TODO: 256 | // - Remove all partitions 257 | // - If the partition table type is not the same: 258 | // - Change partition table type 259 | // - Print `Created a new GPT disklabel (GUID: <>)\n` 260 | } 261 | 262 | "o" => { 263 | // TODO: 264 | // - Remove all partitions 265 | // - If the partition table type is not the same: 266 | // - Change partition table type 267 | // - Print `Created a new DOS disklabel (identifier: <>)\n` 268 | } 269 | 270 | _ => eprintln!("{cmd}: unknown command"), 271 | } 272 | 273 | println!(); 274 | } 275 | 276 | fn main() { 277 | let args = parse_args(); 278 | 279 | if !args.is_valid() { 280 | print_usage(&args.prog); 281 | exit(1); 282 | } 283 | if args.help { 284 | print_help(&args.prog, args.script); 285 | exit(0); 286 | } 287 | 288 | if args.list { 289 | let iter = if !args.disks.is_empty() { 290 | args.disks.into_iter() 291 | } else { 292 | match Disk::list() { 293 | Ok(disks) => disks.into_iter(), 294 | 295 | Err(e) => { 296 | eprintln!("{}: cannot list disks: {e}", args.prog); 297 | exit(1); 298 | } 299 | } 300 | }; 301 | 302 | for path in iter { 303 | match Disk::read(path.clone()) { 304 | Ok(Some(disk)) => print!("{}", disk), 305 | 306 | Ok(None) => { 307 | eprintln!( 308 | "{}: cannot open {}: Invalid argument", 309 | args.prog, 310 | path.display() 311 | ); 312 | } 313 | 314 | Err(e) => { 315 | eprintln!("{}: cannot open {}: {e}", args.prog, path.display()); 316 | } 317 | } 318 | } 319 | 320 | return; 321 | } 322 | 323 | let disk_path = &args.disks[0]; 324 | 325 | if !args.script { 326 | let mut disk = Disk::read(disk_path.clone()) 327 | .unwrap() // TODO handle error 328 | .unwrap(); // TODO handle error 329 | 330 | while let Some(cmd) = prompt(Some("Command (m for help): "), false) { 331 | handle_cmd(&cmd, disk_path, &mut disk); 332 | } 333 | } else { 334 | // TODO Read and parse script 335 | // TODO Write partition table accordingly 336 | todo!(); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /fdisk/src/partition.rs: -------------------------------------------------------------------------------- 1 | //! TODO 2 | 3 | use crate::crc32; 4 | use crate::guid::GUID; 5 | use std::cmp::max; 6 | use std::cmp::min; 7 | use std::fmt; 8 | use std::fmt::Display; 9 | use std::fs::File; 10 | use std::io; 11 | use std::io::Read; 12 | use std::io::Seek; 13 | use std::io::SeekFrom; 14 | use std::io::Write; 15 | use std::mem::size_of; 16 | use std::path::Path; 17 | use std::slice; 18 | use std::str::FromStr; 19 | use utils::prompt::prompt; 20 | 21 | // TODO adapt to disks whose sector size is different than 512 22 | 23 | /// The signature of the MBR partition table. 24 | const MBR_SIGNATURE: u16 = 0xaa55; 25 | 26 | /// The signature in the GPT header. 27 | const GPT_SIGNATURE: &[u8] = b"EFI PART"; 28 | /// The polynom used in the computation of the CRC32 checksum. 29 | const GPT_CHECKSUM_POLYNOM: u32 = 0xedb88320; 30 | 31 | /// Translates the given LBA value `lba` into a positive LBA value. 32 | /// 33 | /// `storage_size` is the number of blocks on the storage device. 34 | /// 35 | /// If the LBA is out of bounds of the storage device, the function returns `None`. 36 | fn translate_lba(lba: i64, storage_size: u64) -> Option { 37 | if lba < 0 { 38 | if (-lba as u64) <= storage_size { 39 | Some(storage_size - (-lba as u64)) 40 | } else { 41 | None 42 | } 43 | } else { 44 | if (lba as u64) < storage_size { 45 | Some(lba as _) 46 | } else { 47 | None 48 | } 49 | } 50 | } 51 | 52 | /// Structure representing a MBR partition. 53 | #[repr(C, packed)] 54 | #[derive(Clone, Copy, Default)] 55 | struct MBRPartition { 56 | /// Partition attributes. 57 | attrs: u8, 58 | /// CHS address of partition start. 59 | chs_start: [u8; 3], 60 | /// The type of the partition. 61 | partition_type: u8, 62 | /// CHS address of partition end. 63 | chs_end: [u8; 3], 64 | /// LBA address of partition start. 65 | lba_start: u32, 66 | /// The number of sectors in the partition. 67 | sectors_count: u32, 68 | } 69 | 70 | impl MBRPartition { 71 | /// Tells whether the partition is active. 72 | pub fn is_active(&self) -> bool { 73 | self.attrs & (1 << 7) != 0 74 | } 75 | } 76 | 77 | /// Structure representing a MBR partition table. 78 | #[repr(C, packed)] 79 | pub struct MBRTable { 80 | /// The boot code. 81 | boot: [u8; 440], 82 | /// The disk signature (optional). 83 | disk_signature: u32, 84 | /// Zero. 85 | zero: u16, 86 | /// The list of partitions. 87 | partitions: [MBRPartition; 4], 88 | /// The partition table signature. 89 | signature: u16, 90 | } 91 | 92 | /// Structure representing a GPT entry. 93 | #[repr(C, packed)] 94 | struct GPTEntry { 95 | /// The partition type's GUID. 96 | partition_type: GUID, 97 | /// The partition's GUID. 98 | guid: GUID, 99 | /// The starting LBA. 100 | start: i64, 101 | /// The ending LBA. 102 | end: i64, 103 | /// Entry's attributes. 104 | attributes: u64, 105 | /// The partition's name. 106 | name: [u16; 36], 107 | } 108 | 109 | /// Structure representing the GPT header. 110 | #[derive(Clone, Copy)] 111 | #[repr(C, packed)] 112 | pub struct GPT { 113 | /// The header's signature. 114 | signature: [u8; 8], 115 | /// The header's revision. 116 | revision: u32, 117 | /// The size of the header in bytes. 118 | hdr_size: u32, 119 | /// The header's checksum. 120 | checksum: u32, 121 | /// Reserved field. 122 | reserved: u32, 123 | /// The LBA of the sector containing this header. 124 | hdr_lba: i64, 125 | /// The LBA of the sector containing the alternate header. 126 | alternate_hdr_lba: i64, 127 | /// The first usable sector. 128 | first_usable: i64, 129 | /// The last usable sector. 130 | last_usable: i64, 131 | /// The disk's GUID. 132 | disk_guid: GUID, 133 | /// The LBA of the beginning of the GUID partition entries array. 134 | entries_start: i64, 135 | /// The number of entries in the table. 136 | entries_number: u32, 137 | /// The size in bytes of each entry in the array. 138 | entry_size: u32, 139 | /// Checksum of the entries array. 140 | entries_checksum: u32, 141 | } 142 | 143 | /// Enumeration of partition table types. 144 | #[derive(Debug, Eq, PartialEq)] 145 | pub enum PartitionTableType { 146 | /// Master Boot Record. 147 | MBR, 148 | /// Globally Unique Identifier Partition Table. 149 | GPT, 150 | } 151 | 152 | impl PartitionTableType { 153 | /// Prints known partition types. 154 | pub fn print_partition_types(&self) { 155 | match self { 156 | Self::MBR => { 157 | let types = &[ 158 | (0x00, "Empty"), 159 | (0x01, "FAT12"), 160 | (0x02, "XENIX root"), 161 | (0x03, "XENIX usr"), 162 | (0x04, "FAT16 <32M"), 163 | (0x05, "Extended"), 164 | (0x06, "FAT16"), 165 | (0x07, "HPFS/NTFS/exFAT"), 166 | (0x08, "AIX"), 167 | (0x09, "AIX bootable"), 168 | (0x0a, "OS/2 Boot Manager"), 169 | (0x0b, "W95 FAT32"), 170 | (0x0c, "W95 FAT32 (LBA)"), 171 | (0x0e, "W95 FAT16 (LBA)"), 172 | (0x0f, "W95 Ext'd (LBA)"), 173 | (0x10, "OPUS"), 174 | (0x11, "Hidden FAT12"), 175 | (0x12, "Compaq diagnostics"), 176 | (0x14, "Hidden FAT16 <3"), 177 | (0x16, "Hidden FAT16"), 178 | (0x17, "Hidden HPFS/NTFS"), 179 | (0x18, "AST SmartSleep"), 180 | (0x1b, "Hidden W95 FAT3"), 181 | (0x1c, "Hidden W95 FAT3"), 182 | (0x1e, "Hidden W95 FAT1"), 183 | (0x24, "NEC DOS"), 184 | (0x27, "Hidden NTFS Win"), 185 | (0x39, "Plan 9"), 186 | (0x3c, "PartitionMagic"), 187 | (0x40, "Venix 80286"), 188 | (0x41, "PPC PReP Boot"), 189 | (0x42, "SFS"), 190 | (0x4d, "QNX4.x"), 191 | (0x4e, "QNX4.x 2nd part"), 192 | (0x4f, "QNX4.x 3rd part"), 193 | (0x50, "OnTrack DM"), 194 | (0x51, "OnTrack DM6 Aux"), 195 | (0x52, "CP/M"), 196 | (0x53, "OnTrack DM6 Aux"), 197 | (0x54, "OnTrackDM6"), 198 | (0x55, "EZ-Drive"), 199 | (0x56, "Golden Bow"), 200 | (0x5c, "Priam Edisk"), 201 | (0x61, "SpeedStor"), 202 | (0x63, "GNU HURD or Sys"), 203 | (0x64, "Novell Netware"), 204 | (0x65, "Novell Netware"), 205 | (0x70, "DiskSecure Mult"), 206 | (0x75, "PC/IX"), 207 | (0x80, "Old Minix"), 208 | (0x81, "Minix / old Linux"), 209 | (0x82, "Linux swap / Solaris"), 210 | (0x83, "Linux"), 211 | (0x84, "OS/2 hidden"), 212 | (0x85, "Linux extended"), 213 | (0x86, "NTFS volume set"), 214 | (0x87, "NTFS volume set"), 215 | (0x88, "Linux plaintext"), 216 | (0x8e, "Linux LVM"), 217 | (0x93, "Amoeba"), 218 | (0x94, "Amoeba BBT"), 219 | (0x9f, "BSD/OS"), 220 | (0xa0, "IBM Thinkpad"), 221 | (0xa5, "FreeBSD"), 222 | (0xa6, "OpenBSD"), 223 | (0xa7, "NeXTSTEP"), 224 | (0xa8, "Darwin UFS"), 225 | (0xa9, "NetBSD"), 226 | (0xab, "Darwin boot"), 227 | (0xaf, "HFS / HFS+"), 228 | (0xb7, "BSDI fs"), 229 | (0xb8, "BSDI swap"), 230 | (0xbb, "Boot Wizard hidden"), 231 | (0xbc, "Acronis FAT32"), 232 | (0xbe, "Solaris boot"), 233 | (0xbf, "Solaris"), 234 | (0xc1, "DRDOS/sec"), 235 | (0xc4, "DRDOS/sec"), 236 | (0xc6, "DRDOS/sec"), 237 | (0xc7, "Syrinx"), 238 | (0xda, "Non-FS data"), 239 | (0xdb, "CP/M / CTOS / ."), 240 | (0xde, "Dell Utility"), 241 | (0xdf, "BootIt"), 242 | (0xe0, "ST AVFS"), 243 | (0xe1, "DOS access"), 244 | (0xe3, "DOS R/O"), 245 | (0xe4, "SpeedStor"), 246 | (0xea, "Linux extended"), 247 | (0xeb, "BeOS fs"), 248 | (0xee, "GPT"), 249 | (0xef, "EFI (FAT-12/16/32)"), 250 | (0xf0, "Linux/PA-RISC bootloader"), 251 | (0xf1, "SpeedStor"), 252 | (0xf2, "DOS secondary"), 253 | (0xf4, "SpeedStor"), 254 | (0xf8, "EBBR protective"), 255 | (0xfb, "VMware VMFS"), 256 | (0xfc, "VMware VMKCORE"), 257 | (0xfd, "Linux raid auto"), 258 | (0xfe, "LANstep"), 259 | (0xff, "BBT"), 260 | ]; 261 | let max_len = types.iter().map(|(_, name)| name.len()).max().unwrap_or(0); 262 | let term_width = 80; // TODO get from ioctl 263 | let entries_per_line = max(term_width / (max_len + 5), 1); 264 | 265 | for (i, (id, name)) in types.iter().enumerate() { 266 | print!(" {id:02x} {name:max_len$}"); 267 | if i % entries_per_line == entries_per_line - 1 { 268 | println!(); 269 | } 270 | } 271 | } 272 | 273 | Self::GPT => { 274 | let types = &[ 275 | ("EFI System", "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"), 276 | ( 277 | "MBR partition scheme", 278 | "024dee41-33e7-11d3-9d69-0008c781f39f", 279 | ), 280 | ("Intel Fast Flash", "d3bfe2de-3daf-11df-ba40-e3a556d89593"), 281 | ("BIOS boot", "21686148-6449-6e6f-744e-656564454649"), 282 | ( 283 | "Sony boot partition", 284 | "f4019732-066e-4e12-8273-346c5641494f", 285 | ), 286 | ( 287 | "Lenovo boot partition", 288 | "bfbfafe7-a34f-448a-9a5b-6213eb736c22", 289 | ), 290 | ("PowerPC PReP boot", "9e1a2d38-c612-4316-aa26-8b49521e5a8b"), 291 | ("ONIE boot", "7412f7d5-a156-4b13-81dc-867174929325"), 292 | ("ONIE config", "d4e6e2cd-4469-46f3-b5cb-1bff57afc149"), 293 | ("Microsoft reserved", "e3c9e316-0b5c-4db8-817d-f92df00215ae"), 294 | ( 295 | "Microsoft basic data", 296 | "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", 297 | ), 298 | ( 299 | "Microsoft LDM metadata", 300 | "5808c8aa-7e8f-42e0-85d2-e1e90434cfb3", 301 | ), 302 | ("Microsoft LDM data", "af9b60a0-1431-4f62-bc68-3311714a69ad"), 303 | ( 304 | "Windows recovery environment", 305 | "de94bba4-06d1-4d40-a16a-bfd50179d6ac", 306 | ), 307 | ( 308 | "IBM General Parallel Fs", 309 | "37affc90-ef7d-4e96-91c3-2d7ae055b174", 310 | ), 311 | ( 312 | "Microsoft Storage Spaces", 313 | "e75caf8f-f680-4cee-afa3-b001e56efc2d", 314 | ), 315 | ("HP-UX data", "75894c1e-3aeb-11d3-b7c1-7b03a0000000"), 316 | ("HP-UX service", "e2a1e728-32e3-11d6-a682-7b03a0000000"), 317 | ("Linux swap", "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f"), 318 | ("Linux filesystem", "0fc63daf-8483-4772-8e79-3d69d8477de4"), 319 | ("Linux server data", "3b8f8425-20e0-4f3b-907f-1a25a76f98e8"), 320 | ("Linux root (x86)", "44479540-f297-41b2-9af7-d131d5f0458a"), 321 | ( 322 | "Linux root (x86-64)", 323 | "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", 324 | ), 325 | ("Linux root (Alpha)", "6523f8ae-3eb1-4e2a-a05a-18b695ae656f"), 326 | ("Linux root (ARC)", "d27f46ed-2919-4cb8-bd25-9531f3c16534"), 327 | ("Linux root (ARM)", "69dad710-2ce4-4e3c-b16c-21a1d49abed3"), 328 | ( 329 | "Linux root (ARM-64)", 330 | "b921b045-1df0-41c3-af44-4c6f280d3fae", 331 | ), 332 | ("Linux root (IA-64)", "993d8d3d-f80e-4225-855a-9daf8ed7ea97"), 333 | ( 334 | "Linux root (LoongArch-64)", 335 | "77055800-792c-4f94-b39a-98c91b762bb6", 336 | ), 337 | ( 338 | "Linux root (MIPS-32 LE)", 339 | "37c58c8a-d913-4156-a25f-48b1b64e07f0", 340 | ), 341 | ( 342 | "Linux root (MIPS-64 LE)", 343 | "700bda43-7a34-4507-b179-eeb93d7a7ca3", 344 | ), 345 | ("Linux root (PPC)", "1de3f1ef-fa98-47b5-8dcd-4a860a654d78"), 346 | ("Linux root (PPC64)", "912ade1d-a839-4913-8964-a10eee08fbd2"), 347 | ( 348 | "Linux root (PPC64LE)", 349 | "c31c45e6-3f39-412e-80fb-4809c4980599", 350 | ), 351 | ( 352 | "Linux root (RISC-V-32)", 353 | "60d5a7fe-8e7d-435c-b714-3dd8162144e1", 354 | ), 355 | ( 356 | "Linux root (RISC-V-64)", 357 | "72ec70a6-cf74-40e6-bd49-4bda08e8f224", 358 | ), 359 | ("Linux root (S390)", "08a7acea-624c-4a20-91e8-6e0fa67d23f9"), 360 | ("Linux root (S390X)", "5eead9a9-fe09-4a1e-a1d7-520d00531306"), 361 | ( 362 | "Linux root (TILE-Gx)", 363 | "c50cdd70-3862-4cc3-90e1-809a8c93ee2c", 364 | ), 365 | ("Linux reserved", "8da63339-0007-60c0-c436-083ac8230908"), 366 | ("Linux home", "933ac7e1-2eb4-4f13-b844-0e14e2aef915"), 367 | ("Linux RAID", "a19d880f-05fc-4d3b-a006-743f0f84911e"), 368 | ("Linux LVM", "e6d6d379-f507-44c2-a23c-238f2a3df928"), 369 | ( 370 | "Linux variable data", 371 | "4d21b016-b534-45c2-a9fb-5c16e091fd2d", 372 | ), 373 | ( 374 | "Linux temporary data", 375 | "7ec6f557-3bc5-4aca-b293-16ef5df639d1", 376 | ), 377 | ("Linux /usr (x86)", "75250d76-8cc6-458e-bd66-bd47cc81a812"), 378 | ( 379 | "Linux /usr (x86-64)", 380 | "8484680c-9521-48c6-9c11-b0720656f69e", 381 | ), 382 | ("Linux /usr (Alpha)", "e18cf08c-33ec-4c0d-8246-c6c6fb3da024"), 383 | ("Linux /usr (ARC)", "7978a683-6316-4922-bbee-38bff5a2fecc"), 384 | ("Linux /usr (ARM)", "7d0359a3-02b3-4f0a-865c-654403e70625"), 385 | ( 386 | "Linux /usr (ARM-64)", 387 | "b0e01050-ee5f-4390-949a-9101b17104e9", 388 | ), 389 | ("Linux /usr (IA-64)", "4301d2a6-4e3b-4b2a-bb94-9e0b2c4225ea"), 390 | ( 391 | "Linux /usr (LoongArch-64)", 392 | "e611c702-575c-4cbe-9a46-434fa0bf7e3f", 393 | ), 394 | ( 395 | "Linux /usr (MIPS-32 LE)", 396 | "0f4868e9-9952-4706-979f-3ed3a473e947", 397 | ), 398 | ( 399 | "Linux /usr (MIPS-64 LE)", 400 | "c97c1f32-ba06-40b4-9f22-236061b08aa8", 401 | ), 402 | ("Linux /usr (PPC)", "7d14fec5-cc71-415d-9d6c-06bf0b3c3eaf"), 403 | ("Linux /usr (PPC64)", "2c9739e2-f068-46b3-9fd0-01c5a9afbcca"), 404 | ( 405 | "Linux /usr (PPC64LE)", 406 | "15bb03af-77e7-4d4a-b12b-c0d084f7491c", 407 | ), 408 | ( 409 | "Linux /usr (RISC-V-32)", 410 | "b933fb22-5c3f-4f91-af90-e2bb0fa50702", 411 | ), 412 | ( 413 | "Linux /usr (RISC-V-64)", 414 | "beaec34b-8442-439b-a40b-984381ed097d", 415 | ), 416 | ("Linux /usr (S390)", "cd0f869b-d0fb-4ca0-b141-9ea87cc78d66"), 417 | ("Linux /usr (S390X)", "8a4f5770-50aa-4ed3-874a-99b710db6fea"), 418 | ( 419 | "Linux /usr (TILE-Gx)", 420 | "55497029-c7c1-44cc-aa39-815ed1558630", 421 | ), 422 | ( 423 | "Linux root verity (x86)", 424 | "d13c5d3b-b5d1-422a-b29f-9454fdc89d76", 425 | ), 426 | ( 427 | "Linux root verity (x86-64)", 428 | "2c7357ed-ebd2-46d9-aec1-23d437ec2bf5", 429 | ), 430 | ( 431 | "Linux root verity (Alpha)", 432 | "fc56d9e9-e6e5-4c06-be32-e74407ce09a5", 433 | ), 434 | ( 435 | "Linux root verity (ARC)", 436 | "24b2d975-0f97-4521-afa1-cd531e421b8d", 437 | ), 438 | ( 439 | "Linux root verity (ARM)", 440 | "7386cdf2-203c-47a9-a498-f2ecce45a2d6", 441 | ), 442 | ( 443 | "Linux root verity (ARM-64)", 444 | "df3300ce-d69f-4c92-978c-9bfb0f38d820", 445 | ), 446 | ( 447 | "Linux root verity (IA-64)", 448 | "86ed10d5-b607-45bb-8957-d350f23d0571", 449 | ), 450 | ( 451 | "Linux root verity (LoongArch-64)", 452 | "f3393b22-e9af-4613-a948-9d3bfbd0c535", 453 | ), 454 | ( 455 | "Linux root verity (MIPS-32 LE)", 456 | "d7d150d2-2a04-4a33-8f12-16651205ff7b", 457 | ), 458 | ( 459 | "Linux root verity (MIPS-64 LE)", 460 | "16b417f8-3e06-4f57-8dd2-9b5232f41aa6", 461 | ), 462 | ( 463 | "Linux root verity (PPC)", 464 | "98cfe649-1588-46dc-b2f0-add147424925", 465 | ), 466 | ( 467 | "Linux root verity (PPC64)", 468 | "9225a9a3-3c19-4d89-b4f6-eeff88f17631", 469 | ), 470 | ( 471 | "Linux root verity (PPC64LE)", 472 | "906bd944-4589-4aae-a4e4-dd983917446a", 473 | ), 474 | ( 475 | "Linux root verity (RISC-V-32)", 476 | "ae0253be-1167-4007-ac68-43926c14c5de", 477 | ), 478 | ( 479 | "Linux root verity (RISC-V-64)", 480 | "b6ed5582-440b-4209-b8da-5ff7c419ea3d", 481 | ), 482 | ( 483 | "Linux root verity (S390)", 484 | "7ac63b47-b25c-463b-8df8-b4a94e6c90e1", 485 | ), 486 | ( 487 | "Linux root verity (S390X)", 488 | "b325bfbe-c7be-4ab8-8357-139e652d2f6b", 489 | ), 490 | ( 491 | "Linux root verity (TILE-Gx)", 492 | "966061ec-28e4-4b2e-b4a5-1f0a825a1d84", 493 | ), 494 | ( 495 | "Linux /usr verity (x86)", 496 | "8f461b0d-14ee-4e81-9aa9-049b6fb97abd", 497 | ), 498 | ( 499 | "Linux /usr verity (x86-64)", 500 | "77ff5f63-e7b6-4633-acf4-1565b864c0e6", 501 | ), 502 | ( 503 | "Linux /usr verity (Alpha)", 504 | "8cce0d25-c0d0-4a44-bd87-46331bf1df67", 505 | ), 506 | ( 507 | "Linux /usr verity (ARC)", 508 | "fca0598c-d880-4591-8c16-4eda05c7347c", 509 | ), 510 | ( 511 | "Linux /usr verity (ARM)", 512 | "c215d751-7bcd-4649-be90-6627490a4c05", 513 | ), 514 | ( 515 | "Linux /usr verity (ARM-64)", 516 | "6e11a4e7-fbca-4ded-b9e9-e1a512bb664e", 517 | ), 518 | ( 519 | "Linux /usr verity (IA-64)", 520 | "6a491e03-3be7-4545-8e38-83320e0ea880", 521 | ), 522 | ( 523 | "Linux /usr verity (LoongArch-64)", 524 | "f46b2c26-59ae-48f0-9106-c50ed47f673d", 525 | ), 526 | ( 527 | "Linux /usr verity (MIPS-32 LE)", 528 | "46b98d8d-b55c-4e8f-aab3-37fca7f80752", 529 | ), 530 | ( 531 | "Linux /usr verity (MIPS-64 LE)", 532 | "3c3d61fe-b5f3-414d-bb71-8739a694a4ef", 533 | ), 534 | ( 535 | "Linux /usr verity (PPC)", 536 | "df765d00-270e-49e5-bc75-f47bb2118b09", 537 | ), 538 | ( 539 | "Linux /usr verity (PPC64)", 540 | "bdb528a5-a259-475f-a87d-da53fa736a07", 541 | ), 542 | ( 543 | "Linux /usr verity (PPC64LE)", 544 | "ee2b9983-21e8-4153-86d9-b6901a54d1ce", 545 | ), 546 | ( 547 | "Linux /usr verity (RISC-V-32)", 548 | "cb1ee4e3-8cd0-4136-a0a4-aa61a32e8730", 549 | ), 550 | ( 551 | "Linux /usr verity (RISC-V-64)", 552 | "8f1056be-9b05-47c4-81d6-be53128e5b54", 553 | ), 554 | ( 555 | "Linux /usr verity (S390)", 556 | "b663c618-e7bc-4d6d-90aa-11b756bb1797", 557 | ), 558 | ( 559 | "Linux /usr verity (S390X)", 560 | "31741cc4-1a2a-4111-a581-e00b447d2d06", 561 | ), 562 | ( 563 | "Linux /usr verity (TILE-Gx)", 564 | "2fb4bf56-07fa-42da-8132-6b139f2026ae", 565 | ), 566 | ( 567 | "Linux root verity sign. (x86)", 568 | "5996fc05-109c-48de-808b-23fa0830b676", 569 | ), 570 | ( 571 | "Linux root verity sign. (x86-64)", 572 | "41092b05-9fc8-4523-994f-2def0408b176", 573 | ), 574 | ( 575 | "Linux root verity sign. (Alpha)", 576 | "d46495b7-a053-414f-80f7-700c99921ef8", 577 | ), 578 | ( 579 | "Linux root verity sign. (ARC)", 580 | "143a70ba-cbd3-4f06-919f-6c05683a78bc", 581 | ), 582 | ( 583 | "Linux root verity sign. (ARM)", 584 | "42b0455f-eb11-491d-98d3-56145ba9d037", 585 | ), 586 | ( 587 | "Linux root verity sign. (ARM-64)", 588 | "6db69de6-29f4-4758-a7a5-962190f00ce3", 589 | ), 590 | ( 591 | "Linux root verity sign. (IA-64)", 592 | "e98b36ee-32ba-4882-9b12-0ce14655f46a", 593 | ), 594 | ( 595 | "Linux root verity sign. (LoongArch-64)", 596 | "5afb67eb-ecc8-4f85-ae8e-ac1e7c50e7d0", 597 | ), 598 | ( 599 | "Linux root verity sign. (MIPS-32 LE)", 600 | "c919cc1f-4456-4eff-918c-f75e94525ca5", 601 | ), 602 | ( 603 | "Linux root verity sign. (MIPS-64 LE)", 604 | "904e58ef-5c65-4a31-9c57-6af5fc7c5de7", 605 | ), 606 | ( 607 | "Linux root verity sign. (PPC)", 608 | "1b31b5aa-add9-463a-b2ed-bd467fc857e7", 609 | ), 610 | ( 611 | "Linux root verity sign. (PPC64)", 612 | "f5e2c20c-45b2-4ffa-bce9-2a60737e1aaf", 613 | ), 614 | ( 615 | "Linux root verity sign. (PPC64LE)", 616 | "d4a236e7-e873-4c07-bf1d-bf6cf7f1c3c6", 617 | ), 618 | ( 619 | "Linux root verity sign. (RISC-V-32)", 620 | "3a112a75-8729-4380-b4cf-764d79934448", 621 | ), 622 | ( 623 | "Linux root verity sign. (RISC-V-64)", 624 | "efe0f087-ea8d-4469-821a-4c2a96a8386a", 625 | ), 626 | ( 627 | "Linux root verity sign. (S390)", 628 | "3482388e-4254-435a-a241-766a065f9960", 629 | ), 630 | ( 631 | "Linux root verity sign. (S390X)", 632 | "c80187a5-73a3-491a-901a-017c3fa953e9", 633 | ), 634 | ( 635 | "Linux root verity sign. (TILE-Gx)", 636 | "b3671439-97b0-4a53-90f7-2d5a8f3ad47b", 637 | ), 638 | ( 639 | "Linux /usr verity sign. (x86)", 640 | "974a71c0-de41-43c3-be5d-5c5ccd1ad2c0", 641 | ), 642 | ( 643 | "Linux /usr verity sign. (x86-64)", 644 | "e7bb33fb-06cf-4e81-8273-e543b413e2e2", 645 | ), 646 | ( 647 | "Linux /usr verity sign. (Alpha)", 648 | "5c6e1c76-076a-457a-a0fe-f3b4cd21ce6e", 649 | ), 650 | ( 651 | "Linux /usr verity sign. (ARC)", 652 | "94f9a9a1-9971-427a-a400-50cb297f0f35", 653 | ), 654 | ( 655 | "Linux /usr verity sign. (ARM)", 656 | "d7ff812f-37d1-4902-a810-d76ba57b975a", 657 | ), 658 | ( 659 | "Linux /usr verity sign. (ARM-64)", 660 | "c23ce4ff-44bd-4b00-b2d4-b41b3419e02a", 661 | ), 662 | ( 663 | "Linux /usr verity sign. (IA-64)", 664 | "8de58bc2-2a43-460d-b14e-a76e4a17b47f", 665 | ), 666 | ( 667 | "Linux /usr verity sign. (LoongArch-64)", 668 | "b024f315-d330-444c-8461-44bbde524e99", 669 | ), 670 | ( 671 | "Linux /usr verity sign. (MIPS-32 LE)", 672 | "3e23ca0b-a4bc-4b4e-8087-5ab6a26aa8a9", 673 | ), 674 | ( 675 | "Linux /usr verity sign. (MIPS-64 LE)", 676 | "f2c2c7ee-adcc-4351-b5c6-ee9816b66e16", 677 | ), 678 | ( 679 | "Linux /usr verity sign. (PPC)", 680 | "7007891d-d371-4a80-86a4-5cb875b9302e", 681 | ), 682 | ( 683 | "Linux /usr verity sign. (PPC64)", 684 | "0b888863-d7f8-4d9e-9766-239fce4d58af", 685 | ), 686 | ( 687 | "Linux /usr verity sign. (PPC64LE)", 688 | "c8bfbd1e-268e-4521-8bba-bf314c399557", 689 | ), 690 | ( 691 | "Linux /usr verity sign. (RISC-V-32)", 692 | "c3836a13-3137-45ba-b583-b16c50fe5eb4", 693 | ), 694 | ( 695 | "Linux /usr verity sign. (RISC-V-64)", 696 | "d2f9000a-7a18-453f-b5cd-4d32f77a7b32", 697 | ), 698 | ( 699 | "Linux /usr verity sign. (S390)", 700 | "17440e4f-a8d0-467f-a46e-3912ae6ef2c5", 701 | ), 702 | ( 703 | "Linux /usr verity sign. (S390X)", 704 | "3f324816-667b-46ae-86ee-9b0c0c6c11b4", 705 | ), 706 | ( 707 | "Linux /usr verity sign. (TILE-Gx)", 708 | "4ede75e2-6ccc-4cc8-b9c7-70334b087510", 709 | ), 710 | ( 711 | "Linux extended boot", 712 | "bc13c2ff-59e6-4262-a352-b275fd6f7172", 713 | ), 714 | ("Linux user's home", "773f91ef-66d4-49b5-bd83-d683bf40ad16"), 715 | ("FreeBSD data", "516e7cb4-6ecf-11d6-8ff8-00022d09712b"), 716 | ("FreeBSD boot", "83bd6b9d-7f41-11dc-be0b-001560b84f0f"), 717 | ("FreeBSD swap", "516e7cb5-6ecf-11d6-8ff8-00022d09712b"), 718 | ("FreeBSD UFS", "516e7cb6-6ecf-11d6-8ff8-00022d09712b"), 719 | ("FreeBSD ZFS", "516e7cba-6ecf-11d6-8ff8-00022d09712b"), 720 | ("FreeBSD Vinum", "516e7cb8-6ecf-11d6-8ff8-00022d09712b"), 721 | ("Apple HFS/HFS+", "48465300-0000-11aa-aa11-00306543ecac"), 722 | ("Apple APFS", "7c3457ef-0000-11aa-aa11-00306543ecac"), 723 | ("Apple UFS", "55465300-0000-11aa-aa11-00306543ecac"), 724 | ("Apple RAID", "52414944-0000-11aa-aa11-00306543ecac"), 725 | ("Apple RAID offline", "52414944-5f4f-11aa-aa11-00306543ecac"), 726 | ("Apple boot", "426f6f74-0000-11aa-aa11-00306543ecac"), 727 | ("Apple label", "4c616265-6c00-11aa-aa11-00306543ecac"), 728 | ("Apple TV recovery", "5265636f-7665-11aa-aa11-00306543ecac"), 729 | ("Apple Core storage", "53746f72-6167-11aa-aa11-00306543ecac"), 730 | ("Apple Silicon boot", "69646961-6700-11aa-aa11-00306543ecac"), 731 | ( 732 | "Apple Silicon recovery", 733 | "52637672-7900-11aa-aa11-00306543ecac", 734 | ), 735 | ("Solaris boot", "6a82cb45-1dd2-11b2-99a6-080020736631"), 736 | ("Solaris root", "6a85cf4d-1dd2-11b2-99a6-080020736631"), 737 | ( 738 | "Solaris /usr & Apple ZFS", 739 | "6a898cc3-1dd2-11b2-99a6-080020736631", 740 | ), 741 | ("Solaris swap", "6a87c46f-1dd2-11b2-99a6-080020736631"), 742 | ("Solaris backup", "6a8b642b-1dd2-11b2-99a6-080020736631"), 743 | ("Solaris /var", "6a8ef2e9-1dd2-11b2-99a6-080020736631"), 744 | ("Solaris /home", "6a90ba39-1dd2-11b2-99a6-080020736631"), 745 | ( 746 | "Solaris alternate sector", 747 | "6a9283a5-1dd2-11b2-99a6-080020736631", 748 | ), 749 | ("Solaris reserved 1", "6a945a3b-1dd2-11b2-99a6-080020736631"), 750 | ("Solaris reserved 2", "6a9630d1-1dd2-11b2-99a6-080020736631"), 751 | ("Solaris reserved 3", "6a980767-1dd2-11b2-99a6-080020736631"), 752 | ("Solaris reserved 4", "6a96237f-1dd2-11b2-99a6-080020736631"), 753 | ("Solaris reserved 5", "6a8d2ac7-1dd2-11b2-99a6-080020736631"), 754 | ("NetBSD swap", "49f48d32-b10e-11dc-b99b-0019d1879648"), 755 | ("NetBSD FFS", "49f48d5a-b10e-11dc-b99b-0019d1879648"), 756 | ("NetBSD LFS", "49f48d82-b10e-11dc-b99b-0019d1879648"), 757 | ( 758 | "NetBSD concatenated", 759 | "2db519c4-b10f-11dc-b99b-0019d1879648", 760 | ), 761 | ("NetBSD encrypted", "2db519ec-b10f-11dc-b99b-0019d1879648"), 762 | ("NetBSD RAID", "49f48daa-b10e-11dc-b99b-0019d1879648"), 763 | ("ChromeOS kernel", "fe3a2a5d-4f32-41a7-b725-accc3285a309"), 764 | ("ChromeOS root fs", "3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec"), 765 | ("ChromeOS reserved", "2e0a753d-9e48-43b0-8337-b15192cb1b5e"), 766 | ("MidnightBSD data", "85d5e45a-237c-11e1-b4b3-e89a8f7fc3a7"), 767 | ("MidnightBSD boot", "85d5e45e-237c-11e1-b4b3-e89a8f7fc3a7"), 768 | ("MidnightBSD swap", "85d5e45b-237c-11e1-b4b3-e89a8f7fc3a7"), 769 | ("MidnightBSD UFS", "0394ef8b-237e-11e1-b4b3-e89a8f7fc3a7"), 770 | ("MidnightBSD ZFS", "85d5e45d-237c-11e1-b4b3-e89a8f7fc3a7"), 771 | ("MidnightBSD Vinum", "85d5e45c-237c-11e1-b4b3-e89a8f7fc3a7"), 772 | ("Ceph Journal", "45b0969e-9b03-4f30-b4c6-b4b80ceff106"), 773 | ( 774 | "Ceph Encrypted Journal", 775 | "45b0969e-9b03-4f30-b4c6-5ec00ceff106", 776 | ), 777 | ("Ceph OSD", "4fbd7e29-9d25-41b8-afd0-062c0ceff05d"), 778 | ("Ceph crypt OSD", "4fbd7e29-9d25-41b8-afd0-5ec00ceff05d"), 779 | ( 780 | "Ceph disk in creation", 781 | "89c57f98-2fe5-4dc0-89c1-f3ad0ceff2be", 782 | ), 783 | ( 784 | "Ceph crypt disk in creation", 785 | "89c57f98-2fe5-4dc0-89c1-5ec00ceff2be", 786 | ), 787 | ("VMware VMFS", "aa31e02a-400f-11db-9590-000c2911d1b8"), 788 | ("VMware Diagnostic", "9d275380-40ad-11db-bf97-000c2911d1b8"), 789 | ("VMware Virtual SAN", "381cfccc-7288-11e0-92ee-000c2911d0b2"), 790 | ("VMware Virsto", "77719a0c-a4a0-11e3-a47e-000c29745a24"), 791 | ("VMware Reserved", "9198effc-31c0-11db-8f78-000c2911d1b8"), 792 | ("OpenBSD data", "824cc7a0-36a8-11e3-890a-952519ad3f61"), 793 | ("QNX6 file system", "cef5a9ad-73bc-4601-89f3-cdeeeee321a1"), 794 | ("Plan 9 partition", "c91818f9-8025-47af-89d2-f030d7000c2c"), 795 | ("HiFive FSBL", "5b193300-fc78-40cd-8002-e86c45580b47"), 796 | ("HiFive BBL", "2e54b353-1271-4842-806f-e436d6af6985"), 797 | ("Haiku BFS", "42465331-3ba3-10f1-802a-4861696b7521"), 798 | ( 799 | "Marvell Armada 3700 Boot partition", 800 | "6828311a-ba55-42a4-bcde-a89bb5edecae", 801 | ), 802 | ]; 803 | let max_len = types.iter().map(|(name, _)| name.len()).max().unwrap_or(0); 804 | for (i, (name, uuid)) in types.iter().enumerate() { 805 | print!("{i:3} {name:max_len$} {uuid}"); 806 | } 807 | } 808 | } 809 | } 810 | 811 | // TODO Return result instead 812 | /// Prompts for informations related to a new partition to be created. 813 | pub fn prompt_new_partition(&self) -> Partition { 814 | let (_extended, max_partition_count) = match self { 815 | Self::MBR => { 816 | // TODO get info from disk, to be passed as argument 817 | println!("Partition type"); 818 | println!(" p primary (TODO primary, TODO extended, TODO free)"); // TODO 819 | println!(" e extended (container for logical partitions)"); // TODO 820 | 821 | let extended = prompt(Some("Select (default p): "), false) 822 | .map(|s| s == "e") // TODO handle invalid prompt (other than `p` and `e`) 823 | .unwrap_or(false); 824 | (extended, 4) 825 | } 826 | 827 | Self::GPT => (false, 128), 828 | }; 829 | 830 | // Ask partition number 831 | let first = 1; // TODO get from disk 832 | let prompt_str = 833 | format!("Partition number ({first}-{max_partition_count}, default {first}): "); 834 | let partition_number = prompt(Some(&prompt_str), false) 835 | .filter(|s| !s.is_empty()) 836 | .map(|s| s.parse::()) 837 | .transpose() 838 | .unwrap() // TODO handle error 839 | .unwrap_or(first); 840 | 841 | // Ask first sector 842 | let first_available = 2048; // TODO 843 | let last_available = 0; // TODO 844 | let prompt_str = format!( 845 | "First sector ({first_available}-{last_available}, default {first_available}): ", 846 | ); 847 | let start = prompt(Some(&prompt_str), false) 848 | .filter(|s| !s.is_empty()) 849 | .map(|s| s.parse::()) 850 | .transpose() 851 | .unwrap() // TODO handle error 852 | .unwrap_or(first_available); 853 | 854 | // Ask last sector 855 | let prompt_str = format!( 856 | "Last sector, +/-sectors or +/-size{{K,M,G,T,P}} ({start}-{last_available}, default {last_available}): ", 857 | ); 858 | let end = prompt(Some(&prompt_str), false) 859 | .map(|s| { 860 | // TODO parse suffix 861 | s.parse::() 862 | }) 863 | .transpose() 864 | .unwrap() // TODO handle error 865 | .unwrap_or(last_available); 866 | 867 | let sector_size = 512; // TODO get from disk? 868 | let size = (end - start) / sector_size as u64; 869 | 870 | // TODO use other values? 871 | let part_type = match self { 872 | Self::MBR => PartitionType::MBR(0), 873 | Self::GPT => PartitionType::GPT(GUID([0; 16])), 874 | }; 875 | 876 | Partition { 877 | start, 878 | size, 879 | 880 | part_type, 881 | 882 | uuid: None, // TODO 883 | 884 | bootable: false, 885 | } 886 | } 887 | 888 | /// Reads partitions from the storage device represented by `dev` and returns the list. 889 | pub fn read(&self, dev: &mut File, sectors_count: u64) -> io::Result>> { 890 | match self { 891 | Self::MBR => { 892 | let mut buff: [u8; size_of::()] = [0; size_of::()]; 893 | dev.seek(SeekFrom::Start(0))?; 894 | dev.read_exact(&mut buff)?; 895 | 896 | let mbr = unsafe { &*(buff.as_ptr() as *const MBRTable) }; 897 | if mbr.signature != MBR_SIGNATURE { 898 | return Ok(None); 899 | } 900 | 901 | let parts = mbr 902 | .partitions 903 | .iter() 904 | .filter(|p| p.sectors_count > 0) 905 | .map(|p| Partition { 906 | start: p.lba_start as _, 907 | size: p.sectors_count as _, 908 | 909 | part_type: PartitionType::MBR(p.partition_type), 910 | 911 | uuid: None, 912 | 913 | bootable: p.is_active(), 914 | }) 915 | .collect(); 916 | Ok(Some(parts)) 917 | } 918 | 919 | Self::GPT => { 920 | let mut buff: [u8; size_of::()] = [0; size_of::()]; 921 | dev.seek(SeekFrom::Start(512))?; 922 | dev.read_exact(&mut buff)?; 923 | 924 | let hdr = unsafe { &mut *(buff.as_mut_ptr() as *mut GPT) }; 925 | // Check signature 926 | if hdr.signature != GPT_SIGNATURE { 927 | return Ok(None); 928 | } 929 | 930 | let mut crc32_table: [u32; 256] = [0; 256]; 931 | crc32::compute_lookuptable(&mut crc32_table, GPT_CHECKSUM_POLYNOM); 932 | 933 | // Check header checksum 934 | let checksum = hdr.checksum; 935 | hdr.checksum = 0; 936 | // TODO computation must be done with the size of the header (dynamic) 937 | if crc32::compute(&buff, &crc32_table) != checksum { 938 | // TODO invalid table 939 | todo!(); 940 | } 941 | 942 | // TODO check entries checksum 943 | // TODO if entries checksum is invalid, use alternate table 944 | 945 | let mut parts = Vec::new(); 946 | 947 | let sector_size = 512; // TODO 948 | let entries_off = 949 | translate_lba(hdr.entries_start, sector_size).unwrap() * sector_size; 950 | 951 | for i in 0..hdr.entries_number { 952 | let off = entries_off + i as u64 * hdr.entry_size as u64; 953 | 954 | let mut buff = vec![0; hdr.entry_size as usize]; 955 | dev.seek(SeekFrom::Start(off as _))?; 956 | dev.read_exact(&mut buff)?; 957 | 958 | let entry = unsafe { &*(buff.as_ptr() as *const GPTEntry) }; 959 | 960 | // If entry is unused, skip 961 | if entry.guid.0.iter().all(|i| *i == 0) { 962 | continue; 963 | } 964 | 965 | // TODO handle negative lba 966 | parts.push(Partition { 967 | start: entry.start as _, 968 | size: (entry.end - entry.start) as _, 969 | 970 | part_type: PartitionType::GPT(entry.partition_type), 971 | 972 | uuid: Some(entry.guid), 973 | 974 | bootable: false, 975 | }); 976 | } 977 | 978 | Ok(Some(parts)) 979 | } 980 | } 981 | } 982 | 983 | /// Writes a GPT header and partitions. 984 | fn write_gpt( 985 | dev: &mut File, 986 | storage_size: u64, 987 | hdr_off: i64, 988 | hdr: &GPT, 989 | parts: &[GPTEntry], 990 | ) -> io::Result<()> { 991 | let sector_size = 512; // TODO 992 | 993 | let hdr_off = translate_lba(hdr_off, storage_size).unwrap() * sector_size; 994 | let entries_off = translate_lba(hdr.entries_start, storage_size).unwrap() * sector_size; 995 | 996 | for (i, entry) in parts.iter().enumerate() { 997 | let off = entries_off + i as u64 * size_of::() as u64; 998 | 999 | let entry_slice = unsafe { 1000 | slice::from_raw_parts(entry as *const _ as *const _, size_of::()) 1001 | }; 1002 | dev.seek(SeekFrom::Start(off))?; 1003 | dev.write_all(entry_slice)?; 1004 | } 1005 | 1006 | let hdr_slice = 1007 | unsafe { slice::from_raw_parts(hdr as *const _ as *const _, size_of::()) }; 1008 | dev.seek(SeekFrom::Start(hdr_off))?; 1009 | dev.write_all(hdr_slice)?; 1010 | 1011 | Ok(()) 1012 | } 1013 | 1014 | /// Writes the partitions table to the storage device represented by `dev`. 1015 | /// 1016 | /// Arguments: 1017 | /// - `dev` is the file representing the device. 1018 | /// - `partitions` is the list of partitions to be written. 1019 | /// - `sectors_count` is the number of sectors on the disk. 1020 | pub fn write( 1021 | &self, 1022 | dev: &mut File, 1023 | partitions: &[Partition], 1024 | sectors_count: u64, 1025 | ) -> io::Result<()> { 1026 | match self { 1027 | Self::MBR => { 1028 | let mut mbr = MBRTable { 1029 | boot: [0; 440], 1030 | disk_signature: 0, 1031 | zero: 0, 1032 | partitions: [MBRPartition::default(); 4], 1033 | signature: MBR_SIGNATURE, 1034 | }; 1035 | 1036 | if partitions.len() > mbr.partitions.len() { 1037 | // TODO error 1038 | todo!(); 1039 | } 1040 | 1041 | for (i, p) in partitions.iter().enumerate() { 1042 | let PartitionType::MBR(partition_type) = p.part_type else { 1043 | panic!("invalid partition type of MBR table"); 1044 | }; 1045 | mbr.partitions[i] = MBRPartition { 1046 | attrs: 0, 1047 | chs_start: [0; 3], 1048 | partition_type, 1049 | chs_end: [0; 3], 1050 | lba_start: p.start as _, 1051 | sectors_count: p.size as _, 1052 | }; 1053 | } 1054 | 1055 | let slice = unsafe { 1056 | slice::from_raw_parts( 1057 | (&mbr as *const _ as *const u8).add(mbr.boot.len()), 1058 | size_of::() - mbr.boot.len(), 1059 | ) 1060 | }; 1061 | dev.seek(SeekFrom::Start(mbr.boot.len() as _))?; 1062 | dev.write_all(slice) 1063 | } 1064 | 1065 | Self::GPT => { 1066 | if partitions.len() > 128 { 1067 | // TODO error 1068 | todo!(); 1069 | } 1070 | 1071 | // Write protective MBR 1072 | Self::MBR.write( 1073 | dev, 1074 | &[Partition { 1075 | start: 1, 1076 | size: min(u32::MAX as u64, sectors_count - 1), 1077 | 1078 | part_type: PartitionType::MBR(0xee), 1079 | 1080 | uuid: None, 1081 | 1082 | bootable: true, 1083 | }], 1084 | sectors_count, 1085 | )?; 1086 | 1087 | // Primary table 1088 | let mut gpt = GPT { 1089 | signature: [0; 8], 1090 | revision: 0x010000, 1091 | hdr_size: size_of::() as _, 1092 | checksum: 0, 1093 | reserved: 0, 1094 | hdr_lba: 1, 1095 | alternate_hdr_lba: -1, 1096 | first_usable: 34, 1097 | last_usable: -34, 1098 | disk_guid: GUID::random(), 1099 | entries_start: 2, 1100 | entries_number: partitions.len() as _, 1101 | entry_size: 128, 1102 | entries_checksum: 0, 1103 | }; 1104 | gpt.signature.copy_from_slice(GPT_SIGNATURE); 1105 | 1106 | let parts: Vec = partitions 1107 | .iter() 1108 | .map(|p| { 1109 | let PartitionType::GPT(partition_type) = p.part_type else { 1110 | panic!("invalid partition type of GPT table"); 1111 | }; 1112 | GPTEntry { 1113 | partition_type, 1114 | guid: p.uuid.unwrap(), 1115 | start: p.start as _, 1116 | end: (p.start + p.size) as _, 1117 | attributes: 0, // TODO 1118 | name: [0; 36], // TODO 1119 | } 1120 | }) 1121 | .collect(); 1122 | 1123 | let mut crc32_table: [u32; 256] = [0; 256]; 1124 | crc32::compute_lookuptable(&mut crc32_table, GPT_CHECKSUM_POLYNOM); 1125 | 1126 | let parts_slice = unsafe { 1127 | slice::from_raw_parts( 1128 | parts.as_ptr() as *const u8, 1129 | parts.len() * size_of::(), 1130 | ) 1131 | }; 1132 | gpt.entries_checksum = crc32::compute(parts_slice, &crc32_table); 1133 | 1134 | let hdr_slice = unsafe { 1135 | slice::from_raw_parts(&gpt as *const _ as *const u8, size_of::()) 1136 | }; 1137 | gpt.checksum = crc32::compute(hdr_slice, &crc32_table); 1138 | 1139 | Self::write_gpt(dev, sectors_count, 1, &gpt, &parts)?; 1140 | 1141 | // Alternate table 1142 | gpt.checksum = 0; 1143 | gpt.alternate_hdr_lba = 1; 1144 | gpt.entries_start = -33; 1145 | let hdr_slice = unsafe { 1146 | slice::from_raw_parts(&gpt as *const _ as *const u8, size_of::()) 1147 | }; 1148 | gpt.checksum = crc32::compute(hdr_slice, &crc32_table); 1149 | Self::write_gpt(dev, sectors_count, -1, &gpt, &parts)?; 1150 | 1151 | Ok(()) 1152 | } 1153 | } 1154 | } 1155 | } 1156 | 1157 | impl fmt::Display for PartitionTableType { 1158 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1159 | match self { 1160 | Self::MBR => write!(fmt, "dos"), 1161 | Self::GPT => write!(fmt, "gpt"), 1162 | } 1163 | } 1164 | } 1165 | 1166 | /// Enumeration of partition type formats. 1167 | #[derive(Clone, Debug, Eq, PartialEq)] 1168 | pub enum PartitionType { 1169 | /// MBR partition type. 1170 | MBR(u8), 1171 | /// GPT partition type. 1172 | GPT(GUID), 1173 | } 1174 | 1175 | impl Default for PartitionType { 1176 | fn default() -> Self { 1177 | Self::MBR(0) 1178 | } 1179 | } 1180 | 1181 | impl FromStr for PartitionType { 1182 | type Err = (); 1183 | 1184 | fn from_str(s: &str) -> Result { 1185 | GUID::from_str(s) 1186 | .map(Self::GPT) 1187 | .or_else(|_| u8::from_str_radix(s, 16).map(Self::MBR)) 1188 | .map_err(|_| ()) 1189 | } 1190 | } 1191 | 1192 | impl Display for PartitionType { 1193 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1194 | match self { 1195 | Self::MBR(n) => write!(fmt, "{n:x}"), 1196 | Self::GPT(n) => write!(fmt, "{n}"), 1197 | } 1198 | } 1199 | } 1200 | 1201 | /// Structure storing informations about a partition. 1202 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 1203 | pub struct Partition { 1204 | /// The start offset in sectors. 1205 | pub start: u64, 1206 | /// The size of the partition in sectors. 1207 | pub size: u64, 1208 | 1209 | /// The partition type. 1210 | pub part_type: PartitionType, 1211 | 1212 | /// The partition's UUID. 1213 | pub uuid: Option, 1214 | 1215 | /// Tells whether the partition is bootable. 1216 | pub bootable: bool, 1217 | } 1218 | 1219 | impl fmt::Display for Partition { 1220 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1221 | write!( 1222 | fmt, 1223 | "start={}, size={}, type={}", 1224 | self.start, self.size, self.part_type 1225 | )?; 1226 | if self.bootable { 1227 | write!(fmt, ", bootable")?; 1228 | } 1229 | if let Some(ref uuid) = self.uuid { 1230 | write!(fmt, ", uuid={uuid}")?; 1231 | } 1232 | Ok(()) 1233 | } 1234 | } 1235 | 1236 | /// Structure representing a partition table. 1237 | #[derive(Debug, Eq, PartialEq)] 1238 | pub struct PartitionTable { 1239 | /// The type of the partition table. 1240 | pub table_type: PartitionTableType, 1241 | /// The list of partitions in the table. 1242 | pub partitions: Vec, 1243 | } 1244 | 1245 | impl PartitionTable { 1246 | /// Reads the partition table from the given device file. 1247 | /// 1248 | /// Arguments: 1249 | /// - `dev` is the device to read from. 1250 | /// - `sectors_count` is the number of sectors on the device. 1251 | /// 1252 | /// The cursor of the device might be changed by the function. 1253 | /// 1254 | /// If the table is invalid, the function returns an empty MBR table. 1255 | pub fn read(dev: &mut File, sectors_count: u64) -> io::Result { 1256 | for t in [PartitionTableType::GPT, PartitionTableType::MBR] { 1257 | if let Some(partitions) = t.read(dev, sectors_count)? { 1258 | return Ok(PartitionTable { 1259 | table_type: t, 1260 | partitions, 1261 | }); 1262 | } 1263 | } 1264 | Ok(PartitionTable { 1265 | table_type: PartitionTableType::MBR, 1266 | partitions: vec![], 1267 | }) 1268 | } 1269 | 1270 | /// Writes the partition table to the disk device. 1271 | /// 1272 | /// Arguments: 1273 | /// - `dev` is the device to write on. 1274 | /// - `sectors_count` is the number of sectors on the device. 1275 | pub fn write(&self, dev: &mut File, sectors_count: u64) -> io::Result<()> { 1276 | self.table_type.write(dev, &self.partitions, sectors_count) 1277 | } 1278 | 1279 | /// Serializes a partitions list into a sfdisk script. 1280 | /// 1281 | /// `dev` is the path to the device file of the disk. 1282 | /// 1283 | /// The function returns the resulting script. 1284 | pub fn serialize(&self, dev: &Path) -> String { 1285 | let mut script = String::new(); 1286 | 1287 | // Write header 1288 | // TODO label 1289 | // TODO label-id 1290 | script += format!("device: {}\n", dev.display()).as_str(); 1291 | script += "unit: sectors\n"; 1292 | script += "\n"; 1293 | 1294 | // Write partitions 1295 | for (i, p) in self.partitions.iter().enumerate() { 1296 | script += &format!("{}{i} : {p}\n", dev.display()); 1297 | } 1298 | 1299 | script 1300 | } 1301 | } 1302 | 1303 | impl FromStr for PartitionTable { 1304 | type Err = String; 1305 | 1306 | /// Deserializes a partitions list from a given sfdisk script. 1307 | fn from_str(script: &str) -> Result { 1308 | // Skip header 1309 | let mut iter = script.split('\n'); 1310 | for line in iter.by_ref() { 1311 | if line.trim().is_empty() { 1312 | break; 1313 | } 1314 | } 1315 | 1316 | // Parse partitions 1317 | let partitions = iter 1318 | .filter(|line| !line.trim().is_empty()) 1319 | .map(|line| { 1320 | let mut split = line.split(':').skip(1); 1321 | let Some(values) = split.next() else { 1322 | return Err("Invalid syntax".to_owned()); 1323 | }; 1324 | 1325 | // Fill partition structure 1326 | let mut part = Partition::default(); 1327 | for v in values.split(',') { 1328 | let mut split = v.split('='); 1329 | let Some(name) = split.next() else { 1330 | return Err("Invalid syntax".to_owned()); 1331 | }; 1332 | 1333 | let name = name.trim(); 1334 | let value = split.next().map(str::trim); 1335 | 1336 | match name { 1337 | "start" => { 1338 | let Some(val) = value else { 1339 | return Err("`start` requires a value".into()); 1340 | }; 1341 | let Ok(v) = val.parse() else { 1342 | return Err(format!("Invalid value for `start`: {val}")); 1343 | }; 1344 | part.start = v; 1345 | } 1346 | 1347 | "size" => { 1348 | let Some(val) = value else { 1349 | return Err("`size` requires a value".into()); 1350 | }; 1351 | let Ok(v) = val.parse() else { 1352 | return Err(format!("Invalid value for `size`: {val}")); 1353 | }; 1354 | part.size = v; 1355 | } 1356 | 1357 | "type" => { 1358 | let Some(val) = value else { 1359 | return Err("`type` requires a value".into()); 1360 | }; 1361 | let Ok(v) = PartitionType::from_str(val) else { 1362 | return Err(format!("Invalid value for `type`: {val}")); 1363 | }; 1364 | part.part_type = v; 1365 | } 1366 | 1367 | "uuid" => { 1368 | let Some(val) = value else { 1369 | return Err("`uuid` requires a value".into()); 1370 | }; 1371 | let Ok(val) = GUID::from_str(val) else { 1372 | return Err(format!("Invalid value for `uuid`: {val}")); 1373 | }; 1374 | part.uuid = Some(val); 1375 | } 1376 | 1377 | "bootable" => part.bootable = true, 1378 | 1379 | _ => return Err(format!("Unknown attribute: `{name}`")), 1380 | } 1381 | } 1382 | Ok(part) 1383 | }) 1384 | .collect::, _>>()?; 1385 | Ok(Self { 1386 | table_type: PartitionTableType::MBR, // TODO 1387 | partitions, 1388 | }) 1389 | } 1390 | } 1391 | 1392 | #[cfg(test)] 1393 | mod test { 1394 | use super::*; 1395 | 1396 | #[test] 1397 | fn partitions_serialize0() { 1398 | let table0 = PartitionTable { 1399 | table_type: PartitionTableType::MBR, 1400 | partitions: vec![], 1401 | }; 1402 | 1403 | let script = table0.serialize(Path::new("/dev/sda")); 1404 | let table1 = PartitionTable::from_str(&script).unwrap(); 1405 | 1406 | assert_eq!(table0, table1); 1407 | } 1408 | 1409 | #[test] 1410 | fn partitions_serialize1() { 1411 | let table0 = PartitionTable { 1412 | table_type: PartitionTableType::MBR, 1413 | partitions: vec![Partition { 1414 | start: 0, 1415 | size: 1, 1416 | 1417 | part_type: PartitionType::MBR(0xab), 1418 | 1419 | uuid: Some(GUID([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])), 1420 | 1421 | bootable: false, 1422 | }], 1423 | }; 1424 | 1425 | let script = table0.serialize(Path::new("/dev/sda")); 1426 | let table1 = PartitionTable::from_str(&script).unwrap(); 1427 | 1428 | assert_eq!(table0, table1); 1429 | } 1430 | 1431 | // TODO More tests (especially invalid scripts) 1432 | } 1433 | -------------------------------------------------------------------------------- /insmod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "insmod" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | utils = { path = "../utils" } 8 | libc = "*" 9 | -------------------------------------------------------------------------------- /insmod/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `insmod` command loads a module from a file. 2 | 3 | use std::env; 4 | use std::ffi::c_long; 5 | use std::fs::File; 6 | use std::io::Error; 7 | use std::os::fd::AsRawFd; 8 | use std::path::PathBuf; 9 | use std::process::exit; 10 | use std::ptr::null; 11 | 12 | /// The ID of the `finit_module` system call. 13 | const FINIT_MODULE_ID: c_long = 0x15e; 14 | 15 | /// Prints usage. 16 | fn print_usage() { 17 | println!("Usage:"); 18 | println!(" insmod [params]"); 19 | println!(); 20 | println!("Loads a kernel module from the given file"); 21 | } 22 | 23 | fn main() { 24 | let args: Vec = env::args().collect(); 25 | 26 | if args.len() < 2 { 27 | print_usage(); 28 | exit(1); 29 | } 30 | 31 | let filepath = PathBuf::from(&args[1]); 32 | let file = File::open(&filepath).unwrap_or_else(|e| { 33 | eprintln!("insmod: cannot open file `{}`: {}", filepath.display(), e); 34 | exit(1); 35 | }); 36 | 37 | // TODO handle parameters 38 | let ret = unsafe { libc::syscall(FINIT_MODULE_ID, file.as_raw_fd(), null::(), 0) }; 39 | if ret < 0 { 40 | eprintln!( 41 | "insmod: cannot load module `{}`: {}", 42 | filepath.display(), 43 | Error::last_os_error() 44 | ); 45 | exit(1); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /login/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "login" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | utils = { path = "../utils" } 10 | libc = "*" 11 | -------------------------------------------------------------------------------- /login/src/main.rs: -------------------------------------------------------------------------------- 1 | //! `login` prompts a username/password to authenticate on a new session. 2 | 3 | #![feature(never_type)] 4 | 5 | use std::ffi::CString; 6 | use std::os::unix::ffi::OsStrExt; 7 | use std::path::Path; 8 | use std::process::exit; 9 | use std::ptr::null; 10 | use std::time::Duration; 11 | use std::{env, io, iter}; 12 | use utils::prompt::prompt; 13 | use utils::user; 14 | use utils::user::User; 15 | use utils::util; 16 | 17 | /// Builds an environment variable in the form: name=value 18 | fn build_env_var(name: &str, value: impl IntoIterator) -> CString { 19 | let data: Vec = name 20 | .as_bytes() 21 | .into_iter() 22 | .cloned() 23 | .chain(iter::once(b'=')) 24 | .chain(value) 25 | .collect(); 26 | // TODO handle when the value contains a nul-byte? 27 | CString::new(data).unwrap() 28 | } 29 | 30 | /// Switches to the given user after login is successful. 31 | /// 32 | /// Arguments: 33 | /// - `logname` is the name of the user used to login. 34 | /// - `user` is the user to switch to. 35 | fn switch_user(logname: &str, user: &User) -> io::Result { 36 | let User { 37 | login_name, 38 | uid, 39 | gid, 40 | home, 41 | interpreter, 42 | .. 43 | } = user; 44 | 45 | // Prepare environment 46 | let term = env::var_os("TERM").unwrap_or_else(|| { 47 | // TODO fetch from the terminal 48 | "linux".into() 49 | }); 50 | let shell = if !interpreter.is_empty() { 51 | interpreter 52 | } else { 53 | "/bin/sh" 54 | }; 55 | let path = match uid { 56 | 0 => "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin", 57 | _ => "/usr/local/bin:/bin:/usr/bin", 58 | }; 59 | let mail = "/var/spool/mail/".bytes().chain(login_name.bytes()); 60 | 61 | // Build variables 62 | let env_home = build_env_var("HOME", home.as_os_str().as_bytes().iter().cloned()); 63 | let env_user = build_env_var("USER", login_name.bytes()); 64 | let env_logname = build_env_var("LOGNAME", logname.bytes()); 65 | let env_term = build_env_var("TERM", term.as_bytes().iter().cloned()); 66 | let env_shell = build_env_var("SHELL", shell.bytes()); 67 | let env_path = build_env_var("PATH", path.bytes()); 68 | let env_mail = build_env_var("MAIL", mail); 69 | let envp = [ 70 | env_home.as_ptr(), 71 | env_user.as_ptr(), 72 | env_logname.as_ptr(), 73 | env_term.as_ptr(), 74 | env_shell.as_ptr(), 75 | env_path.as_ptr(), 76 | env_mail.as_ptr(), 77 | null(), 78 | ]; 79 | 80 | let bin = CString::new(shell).unwrap(); // TODO handle error? 81 | let argv = [bin.as_ptr(), null()]; 82 | 83 | // Set current user 84 | user::set(*uid, *gid)?; 85 | // Set current working directory 86 | env::set_current_dir(home)?; 87 | 88 | // Execute interpreter 89 | let res = unsafe { libc::execve(bin.as_ptr(), argv.as_ptr(), envp.as_ptr()) }; 90 | if res >= 0 { 91 | // In theory, `execve` will never return when successful 92 | unreachable!(); 93 | } else { 94 | Err(io::Error::last_os_error()) 95 | } 96 | } 97 | 98 | fn main() { 99 | let _args = env::args(); // TODO Parse and use 100 | 101 | loop { 102 | println!(); 103 | 104 | // Get user prompt 105 | let user_prompt = format!("{} login: ", util::get_hostname()); 106 | 107 | // Prompt login and password 108 | let login = prompt(Some(&user_prompt), false).unwrap_or_else(|| exit(1)); 109 | let pass = prompt(None, true).unwrap_or_else(|| exit(1)); 110 | 111 | // Read users lists 112 | let passwd = user::read_passwd(Path::new(user::PASSWD_PATH)).unwrap_or_else(|e| { 113 | eprintln!("Cannot read passwd file: {e}"); 114 | exit(1); 115 | }); 116 | let shadow = user::read_shadow(&Path::new(user::SHADOW_PATH)).ok(); 117 | 118 | // Get user from prompted login 119 | let user_entry = passwd.into_iter().find(|e| e.login_name == login); 120 | 121 | let interval = Duration::from_millis(1000); 122 | util::exec_wait(interval, || { 123 | if let Some(user_entry) = user_entry { 124 | // Checking password against user entry 125 | let correct = user_entry.check_password(&pass).unwrap_or_else(|| { 126 | if let Some(shadow) = shadow { 127 | shadow 128 | .into_iter() 129 | .filter(|e| e.login_name == login) 130 | .map(|e| e.check_password(&pass)) 131 | .next() 132 | .unwrap_or(false) 133 | } else { 134 | false 135 | } 136 | }); 137 | 138 | if correct { 139 | switch_user(&login, &user_entry).unwrap_or_else(|e| { 140 | eprintln!("login: {e}"); 141 | exit(1); 142 | }); 143 | } 144 | } 145 | }); 146 | 147 | eprintln!("Login incorrect"); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lsmod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lsmod" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /lsmod/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `lsmod` command allows to list loaded kernel modules. 2 | 3 | use std::fs::File; 4 | use std::io::BufRead; 5 | use std::io::BufReader; 6 | use std::process::exit; 7 | 8 | /// The path to the modules file. 9 | const MODULES_PATH: &str = "/proc/modules"; 10 | 11 | fn main() { 12 | let file = match File::open(MODULES_PATH) { 13 | Ok(f) => f, 14 | Err(e) => { 15 | eprintln!("lsmod: cannot open `{MODULES_PATH}`: {e}"); 16 | exit(1); 17 | } 18 | }; 19 | 20 | let reader = BufReader::new(file); 21 | 22 | println!("Name\tSize\tUsed by"); 23 | 24 | for line in reader.lines() { 25 | // TODO handle error 26 | let mut split = line.as_ref().unwrap().split(' '); 27 | 28 | let name = split.next().unwrap(); 29 | let size = split.next().unwrap(); 30 | let use_count = split.next().unwrap(); 31 | let used_by_list = split.next().unwrap(); 32 | 33 | // TODO padding 34 | println!("{name}\t{size}\t{use_count}\t{used_by_list}"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mkfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mkfs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | utils = { path = "../utils" } 8 | -------------------------------------------------------------------------------- /mkfs/src/ext2.rs: -------------------------------------------------------------------------------- 1 | //! Module handling the `ext2` filesystem. 2 | 3 | use crate::FSFactory; 4 | use std::cmp::min; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::Read; 8 | use std::io::Seek; 9 | use std::io::SeekFrom; 10 | use std::io::Write; 11 | use std::mem; 12 | use std::mem::size_of; 13 | use std::num::NonZeroU32; 14 | use std::slice; 15 | use utils::util; 16 | use utils::util::get_timestamp; 17 | use utils::util::log2; 18 | use utils::util::reinterpret; 19 | 20 | /// The offset of the superblock from the beginning of the device. 21 | const SUPERBLOCK_OFFSET: u64 = 1024; 22 | /// The filesystem's signature. 23 | const EXT2_SIGNATURE: u16 = 0xef53; 24 | 25 | /// The default block size in bytes. 26 | const DEFAULT_BLOCK_SIZE: u64 = 4096; 27 | /// The default number of inodes per group. 28 | const DEFAULT_INODES_PER_GROUP: u32 = 1024; 29 | /// The default number of blocks per group. 30 | const DEFAULT_BLOCKS_PER_GROUP: u32 = 1024; 31 | 32 | /// The default number of mounts before a fsck pass is required. 33 | const DEFAULT_FSCK_MOUNT_COUNT: u16 = 1024; 34 | /// The default interval in seconds before a fsck pass is required. 35 | const DEFAULT_FSCK_INTERVAL: u32 = 2678400; 36 | 37 | /// Filesystem state: the filesystem is clean 38 | const FS_STATE_CLEAN: u16 = 1; 39 | /// Filesystem state: the filesystem has errors 40 | const FS_STATE_ERROR: u16 = 2; 41 | 42 | /// Error handle action: ignore 43 | const ERR_ACTION_IGNORE: u16 = 1; 44 | /// Error handle action: mount as read-only 45 | const ERR_ACTION_READ_ONLY: u16 = 2; 46 | /// Error handle action: trigger a kernel panic 47 | const ERR_ACTION_KERNEL_PANIC: u16 = 3; 48 | 49 | /// Optional feature: Preallocation of a specified number of blocks for each new 50 | /// directories 51 | const OPTIONAL_FEATURE_DIRECTORY_PREALLOCATION: u32 = 0x1; 52 | /// Optional feature: AFS server 53 | const OPTIONAL_FEATURE_AFS: u32 = 0x2; 54 | /// Optional feature: Journal 55 | const OPTIONAL_FEATURE_JOURNAL: u32 = 0x4; 56 | /// Optional feature: Inodes have extended attributes 57 | const OPTIONAL_FEATURE_INODE_EXTENDED: u32 = 0x8; 58 | /// Optional feature: Filesystem can resize itself for larger partitions 59 | const OPTIONAL_FEATURE_RESIZE: u32 = 0x10; 60 | /// Optional feature: Directories use hash index 61 | const OPTIONAL_FEATURE_HASH_INDEX: u32 = 0x20; 62 | 63 | /// Required feature: Compression 64 | const REQUIRED_FEATURE_COMPRESSION: u32 = 0x1; 65 | /// Required feature: Directory entries have a type field 66 | const REQUIRED_FEATURE_DIRECTORY_TYPE: u32 = 0x2; 67 | /// Required feature: Filesystem needs to replay its journal 68 | const REQUIRED_FEATURE_JOURNAL_REPLAY: u32 = 0x4; 69 | /// Required feature: Filesystem uses a journal device 70 | const REQUIRED_FEATURE_JOURNAL_DEVIXE: u32 = 0x8; 71 | 72 | /// Write-required feature: Sparse superblocks and group descriptor tables 73 | const WRITE_REQUIRED_SPARSE_SUPERBLOCKS: u32 = 0x1; 74 | /// Write-required feature: Filesystem uses a 64-bit file size 75 | const WRITE_REQUIRED_64_BITS: u32 = 0x2; 76 | /// Directory contents are stored in the form of a Binary Tree 77 | const WRITE_REQUIRED_DIRECTORY_BINARY_TREE: u32 = 0x4; 78 | 79 | /// The root inode. 80 | const ROOT_INODE: u32 = 2; 81 | 82 | /// The ext2 superblock structure. 83 | #[repr(C, packed)] 84 | struct Superblock { 85 | /// Total number of inodes in the filesystem. 86 | s_inodes_count: u32, 87 | /// Total number of blocks in the filesystem. 88 | s_blocks_count: u32, 89 | /// Number of blocks reserved for the superuser. 90 | s_r_blocks_count: u32, 91 | /// Total number of unallocated blocks. 92 | s_free_blocks_count: u32, 93 | /// Total number of unallocated inodes. 94 | s_free_inodes_count: u32, 95 | /// Block number of the block containing the superblock. 96 | s_first_data_block: u32, 97 | /// log2(block_size) - 10 98 | s_log_block_size: u32, 99 | /// log2(fragment_size) - 10 100 | s_frag_log_size: u32, 101 | /// The number of blocks per block group. 102 | s_blocks_per_group: u32, 103 | /// The number of fragments per block group. 104 | s_frags_per_group: u32, 105 | /// The number of inodes per block group. 106 | s_inodes_per_group: u32, 107 | /// The timestamp of the last mount operation. 108 | s_mtime: u32, 109 | /// The timestamp of the last write operation. 110 | s_wtime: u32, 111 | /// The number of mounts since the last consistency check. 112 | s_mnt_count: u16, 113 | /// The number of mounts allowed before a consistency check must be done. 114 | s_max_mnt_count: u16, 115 | /// The ext2 signature. 116 | s_magic: u16, 117 | /// The filesystem's state. 118 | s_state: u16, 119 | /// The action to perform when an error is detected. 120 | s_errors: u16, 121 | /// The minor version. 122 | s_minor_rev_level: u16, 123 | /// The timestamp of the last consistency check. 124 | s_lastcheck: u32, 125 | /// The interval between mandatory consistency checks. 126 | s_checkinterval: u32, 127 | /// The id os the operating system from which the filesystem was created. 128 | s_creator_os: u32, 129 | /// The major version. 130 | s_rev_level: u32, 131 | /// The UID of the user that can use reserved blocks. 132 | s_def_resuid: u16, 133 | /// The GID of the group that can use reserved blocks. 134 | s_def_resgid: u16, 135 | 136 | // Extended superblock fields 137 | /// The first non reserved inode 138 | s_first_ino: u32, 139 | /// The size of the inode structure in bytes. 140 | s_inode_size: u16, 141 | /// The block group containing the superblock. 142 | s_block_group_nr: u16, 143 | /// Optional features for the implementation to support. 144 | s_feature_compat: u32, 145 | /// Required features for the implementation to support. 146 | s_feature_incompat: u32, 147 | /// Required features for the implementation to support for writing. 148 | s_feature_ro_compat: u32, 149 | /// The filesystem UUID. 150 | s_uuid: [u8; 16], 151 | /// The volume name. 152 | s_volume_name: [u8; 16], 153 | /// The path the volume was last mounted to. 154 | s_last_mounted: [u8; 64], 155 | /// Used compression algorithms. 156 | s_algo_bitmap: u32, 157 | /// The number of blocks to preallocate for files. 158 | s_prealloc_blocks: u8, 159 | /// The number of blocks to preallocate for directories. 160 | s_prealloc_dir_blocks: u8, 161 | /// Unused. 162 | _unused: u16, 163 | /// The journal UUID. 164 | s_journal_uuid: [u8; 16], 165 | /// The journal inode. 166 | s_journal_inum: u32, 167 | /// The journal device. 168 | s_journal_dev: u32, 169 | /// The head of orphan inodes list. 170 | s_last_orphan: u32, 171 | 172 | /// Structure padding. 173 | _padding: [u8; 788], 174 | } 175 | 176 | impl Superblock { 177 | /// Returns the size of a block. 178 | pub fn get_block_size(&self) -> u64 { 179 | util::pow2(self.s_log_block_size + 10) as _ 180 | } 181 | 182 | /// Returns the size of an inode. 183 | pub fn get_inode_size(&self) -> usize { 184 | if self.s_rev_level >= 1 { 185 | self.s_inode_size as _ 186 | } else { 187 | 128 188 | } 189 | } 190 | } 191 | 192 | /// Structure representing a block group descriptor to be stored into the Block Group Descriptor 193 | /// Table (BGDT). 194 | #[repr(C, packed)] 195 | struct BlockGroupDescriptor { 196 | /// The block address of the block usage bitmap. 197 | bg_block_bitmap: u32, 198 | /// The block address of the inode usage bitmap. 199 | bg_inode_bitmap: u32, 200 | /// Starting block address of inode table. 201 | bg_inode_table: u32, 202 | /// Number of unallocated blocks in group. 203 | bg_free_blocks_count: u16, 204 | /// Number of unallocated inodes in group. 205 | bg_free_inodes_count: u16, 206 | /// Number of directories in group. 207 | bg_used_dirs_count: u16, 208 | /// Structure padding. 209 | _padding: [u8; 14], 210 | } 211 | 212 | impl BlockGroupDescriptor { 213 | /// Returns the offset of the `i`th block group descriptor. 214 | /// 215 | /// `superblock` is the filesystem's superblock. 216 | pub fn get_disk_offset(i: u32, superblock: &Superblock) -> u64 { 217 | let bgdt_off = (SUPERBLOCK_OFFSET / superblock.get_block_size() as u64) + 1; 218 | (bgdt_off * superblock.get_block_size() as u64) + (i as u64 * size_of::() as u64) 219 | } 220 | 221 | /// Reads and returns the `i`th block group descriptor. 222 | /// 223 | /// Arguments: 224 | /// - `superblock` is the filesystem's superblock. 225 | /// - `dev` is the device. 226 | pub fn read(i: u32, superblock: &Superblock, dev: &mut File) -> io::Result { 227 | let bgd_off = Self::get_disk_offset(i, superblock); 228 | let mut bgd: BlockGroupDescriptor = unsafe { mem::zeroed() }; 229 | let slice = 230 | unsafe { slice::from_raw_parts_mut(&mut bgd as *mut _ as *mut u8, size_of::()) }; 231 | dev.seek(SeekFrom::Start(bgd_off))?; 232 | dev.read_exact(slice)?; 233 | Ok(bgd) 234 | } 235 | 236 | /// Writes the block group descriptor table. 237 | /// 238 | /// Arguments: 239 | /// - `i` is the offset of the group. 240 | /// - `superblock` is the filesystem's superblock. 241 | /// - `dev` is the device. 242 | pub fn write(&self, i: u32, superblock: &Superblock, dev: &mut File) -> io::Result<()> { 243 | let bgd_off = Self::get_disk_offset(i, superblock); 244 | let slice = reinterpret(self); 245 | dev.seek(SeekFrom::Start(bgd_off))?; 246 | dev.write_all(slice)?; 247 | Ok(()) 248 | } 249 | } 250 | 251 | /// An inode represents a file in the filesystem. 252 | /// 253 | /// The name of the file is not included in the inode but in the directory entry associated with it 254 | /// since several entries can refer to the same inode (hard links). 255 | #[repr(C, packed)] 256 | struct INode { 257 | /// Type and permissions. 258 | i_mode: u16, 259 | /// User ID. 260 | i_uid: u16, 261 | /// Lower 32 bits of size in bytes. 262 | i_size: u32, 263 | /// Timestamp of the last access. 264 | i_atime: u32, 265 | /// Timestamp of inode creation. 266 | i_ctime: u32, 267 | /// Timestamp of the last modification. 268 | i_mtime: u32, 269 | /// Timestamp of the deletion. 270 | i_dtime: u32, 271 | /// Group ID. 272 | i_gid: u16, 273 | /// The number of hard links to this inode. 274 | i_links_count: u16, 275 | /// The number of sectors used by this inode. 276 | i_blocks: u32, 277 | /// INode flags. 278 | i_flags: u32, 279 | /// OS-specific value. 280 | i_osd1: u32, 281 | /// Direct block pointers. 282 | i_block: [u32; 15], 283 | /// Generation number. 284 | i_generation: u32, 285 | /// The file's ACL. 286 | i_file_acl: u32, 287 | /// Higher 32 bits of size in bytes. 288 | /// 289 | /// The name of the variable is incoherent with its purpose. 290 | i_dir_acl: u32, 291 | /// Block address of fragment. 292 | i_faddr: u32, 293 | /// OS-specific value. 294 | _padding: [u8; 12], 295 | } 296 | 297 | impl INode { 298 | /// Returns the offset of the inode on the disk in bytes. 299 | /// 300 | /// Arguments: 301 | /// - `i` is the inode's index (starting at `1`). 302 | /// - `superblock` is the filesystem's superblock. 303 | /// - `dev` is the device. 304 | fn get_disk_offset(i: NonZeroU32, superblock: &Superblock, dev: &mut File) -> io::Result { 305 | let i = i.get(); 306 | 307 | let blk_size = superblock.get_block_size() as u64; 308 | let inode_size = superblock.get_inode_size() as u64; 309 | 310 | // The block group the inode is located in 311 | let blk_grp = (i - 1) / superblock.s_inodes_per_group; 312 | // The offset of the inode in the block group's bitfield 313 | let inode_grp_off = (i - 1) % superblock.s_inodes_per_group; 314 | // The offset of the inode's block 315 | let inode_table_blk_off = (inode_grp_off as u64 * inode_size) / blk_size; 316 | // The offset of the inode in the block 317 | let inode_blk_off = ((i - 1) as u64 * inode_size) % blk_size; 318 | 319 | let bgd = BlockGroupDescriptor::read(blk_grp, superblock, dev)?; 320 | 321 | // The block containing the inode 322 | let blk = bgd.bg_inode_table as u64 + inode_table_blk_off; 323 | 324 | // The offset of the inode on the disk 325 | Ok((blk * blk_size) + inode_blk_off) 326 | } 327 | } 328 | 329 | /// A directory entry is a structure stored in the content of an inode of type 330 | /// `Directory`. 331 | /// 332 | /// Each directory entry represent a file that is the stored in the 333 | /// directory and points to its inode. 334 | /// 335 | /// The name of the entry is not included to prevent the structure from being usized. 336 | #[repr(C, packed)] 337 | pub struct DirectoryEntry { 338 | /// The inode associated with the entry. 339 | inode: u32, 340 | /// The total size of the entry. 341 | rec_len: u16, 342 | /// Name length least-significant bits. 343 | name_len: u8, 344 | /// Name length most-significant bits or type indicator (if enabled). 345 | file_type: u8, 346 | } 347 | 348 | /// Fills the given bitmap. 349 | /// 350 | /// Arguments: 351 | /// - `off` is the offset to the beginning of the bitmap. 352 | /// - `size` is the size of the bitmap in bytes. 353 | /// - `end` is the end of the portion to be set with 1s. The rest is set with 0s. 354 | /// - `dev` is the device. 355 | pub fn fill_bitmap(off: u64, size: usize, end: usize, dev: &mut File) -> io::Result<()> { 356 | let mut slice: Vec = vec![0; size]; 357 | 358 | let set_bytes = end / 8; 359 | slice[0..set_bytes].fill(0xff); 360 | 361 | let remaining_bits = end % 8; 362 | let aligned = remaining_bits == 0; 363 | if !aligned { 364 | slice[set_bytes] = (1 << remaining_bits) - 1; 365 | } 366 | 367 | dev.seek(SeekFrom::Start(off))?; 368 | dev.write_all(&slice) 369 | } 370 | 371 | /// A factory to create an `ext2` filesystem. 372 | #[derive(Default)] 373 | pub struct Ext2Factory { 374 | /// The length of the filesystem in bytes. 375 | len: Option, 376 | 377 | /// The block size in bytes. 378 | block_size: Option, 379 | 380 | /// The number of inodes per group. 381 | inodes_per_group: Option, 382 | /// The number of blocks per group. 383 | blocks_per_group: Option, 384 | 385 | /// The ID of the filesystem. 386 | fs_id: Option<[u8; 16]>, 387 | /// The name of the filesystem. 388 | label: Option, 389 | } 390 | 391 | impl FSFactory for Ext2Factory { 392 | fn is_present(&self, dev: &mut File) -> io::Result { 393 | let mut superblock: Superblock = unsafe { mem::zeroed() }; 394 | let slice = unsafe { 395 | slice::from_raw_parts_mut( 396 | &mut superblock as *mut _ as *mut u8, 397 | size_of::(), 398 | ) 399 | }; 400 | dev.seek(SeekFrom::Start(SUPERBLOCK_OFFSET))?; 401 | dev.read_exact(slice)?; 402 | 403 | Ok(superblock.s_magic == EXT2_SIGNATURE) 404 | } 405 | 406 | fn create(&self, dev: &mut File) -> io::Result<()> { 407 | let create_timestamp = get_timestamp().as_secs() as u32; 408 | 409 | let sector_size = 512; // TODO get from device 410 | let len = match self.len { 411 | Some(len) => len, 412 | None => utils::disk::get_disk_size(dev)? * sector_size, 413 | }; 414 | 415 | let block_size = self.block_size.unwrap_or(DEFAULT_BLOCK_SIZE); 416 | // TODO if block size is not a power of two or if log2(block size) < 10, error 417 | let block_size_log = log2(block_size).unwrap() as u32; 418 | 419 | let blocks_per_group = self.blocks_per_group.unwrap_or(DEFAULT_BLOCKS_PER_GROUP); 420 | let inodes_per_group = self.inodes_per_group.unwrap_or(DEFAULT_INODES_PER_GROUP); 421 | 422 | let total_blocks = (len / block_size) as u32; 423 | let groups_count = total_blocks / blocks_per_group; 424 | let total_inodes = inodes_per_group * groups_count; 425 | 426 | let superblock_group = SUPERBLOCK_OFFSET as u32 / block_size as u32 / blocks_per_group; 427 | 428 | let volume_name = self 429 | .label 430 | .as_ref() 431 | .map(|label| { 432 | let label = label.as_bytes(); 433 | let mut b: [u8; 16] = [0; 16]; 434 | let len = min(label.len(), b.len()); 435 | b[..len].copy_from_slice(&label[0..len]); 436 | b 437 | }) 438 | .unwrap_or([0; 16]); 439 | let filesystem_id = self.fs_id.unwrap_or_else(|| { 440 | // Generate a random ID 441 | let mut id = [0; 16]; 442 | util::get_random(&mut id); 443 | id 444 | }); 445 | 446 | let mut superblock = Superblock { 447 | s_inodes_count: total_inodes, 448 | s_blocks_count: total_blocks, 449 | s_r_blocks_count: 0, 450 | s_free_blocks_count: 0, 451 | s_free_inodes_count: 0, 452 | s_first_data_block: (SUPERBLOCK_OFFSET / block_size) as _, 453 | s_log_block_size: block_size_log - 10, 454 | s_frag_log_size: block_size_log - 10, 455 | s_blocks_per_group: blocks_per_group, 456 | s_frags_per_group: blocks_per_group, 457 | s_inodes_per_group: inodes_per_group, 458 | s_mtime: 0, 459 | s_wtime: create_timestamp, 460 | s_mnt_count: 0, 461 | s_max_mnt_count: DEFAULT_FSCK_MOUNT_COUNT, // TODO take from param 462 | s_magic: EXT2_SIGNATURE, 463 | s_state: FS_STATE_CLEAN, 464 | s_errors: ERR_ACTION_READ_ONLY, 465 | s_minor_rev_level: 1, 466 | s_lastcheck: create_timestamp, 467 | s_checkinterval: DEFAULT_FSCK_INTERVAL, // TODO take from param 468 | s_creator_os: 0, 469 | s_rev_level: 1, 470 | s_def_resuid: 0, 471 | s_def_resgid: 0, 472 | 473 | s_first_ino: 11, 474 | s_inode_size: 128, 475 | s_block_group_nr: superblock_group as _, 476 | s_feature_compat: 0, 477 | s_feature_incompat: 0, 478 | s_feature_ro_compat: 0, 479 | s_uuid: filesystem_id, 480 | s_volume_name: volume_name, 481 | s_last_mounted: [0; 64], 482 | s_algo_bitmap: 0, 483 | s_prealloc_blocks: 0, 484 | s_prealloc_dir_blocks: 0, 485 | _unused: 0, 486 | s_journal_uuid: [0; 16], 487 | s_journal_inum: 0, 488 | s_journal_dev: 0, 489 | s_last_orphan: 0, 490 | 491 | _padding: [0; 788], 492 | }; 493 | 494 | let bgdt_off = (SUPERBLOCK_OFFSET / block_size) + 1; 495 | let bgdt_size = 496 | (groups_count as u64 * size_of::() as u64).div_ceil(block_size); 497 | let bgdt_end = bgdt_off + bgdt_size; 498 | 499 | let block_usage_bitmap_size = blocks_per_group.div_ceil((block_size * 8) as _); 500 | let inode_usage_bitmap_size = inodes_per_group.div_ceil((block_size * 8) as _); 501 | let inodes_table_size = 502 | (inodes_per_group * superblock.s_inode_size as u32).div_ceil(block_size as u32); 503 | let metadata_size = block_usage_bitmap_size + inode_usage_bitmap_size + inodes_table_size; 504 | 505 | // Add `1` to count a block for the `.` and `..` entries of root directory 506 | let used_blocks_end = bgdt_end as u32 + groups_count * metadata_size + 1; 507 | 508 | // Write block groups 509 | for i in 0..groups_count { 510 | let bg_block_bitmap = bgdt_end as u32 + i * metadata_size; 511 | let bg_inode_bitmap = bg_block_bitmap + block_usage_bitmap_size; 512 | let bg_inode_table = bg_inode_bitmap + inode_usage_bitmap_size; 513 | let mut bgd = BlockGroupDescriptor { 514 | bg_block_bitmap, 515 | bg_inode_bitmap, 516 | bg_inode_table, 517 | bg_free_blocks_count: blocks_per_group as _, 518 | bg_free_inodes_count: inodes_per_group as _, 519 | bg_used_dirs_count: 0, 520 | _padding: [0; 14], 521 | }; 522 | 523 | // Fill blocks bitmap 524 | let begin_block = i * blocks_per_group; 525 | let used_blocks_count = min( 526 | blocks_per_group, 527 | used_blocks_end.saturating_sub(begin_block), 528 | ); 529 | fill_bitmap( 530 | bg_block_bitmap as u64 * block_size, 531 | block_usage_bitmap_size as usize * block_size as usize, 532 | used_blocks_count as usize, 533 | dev, 534 | )?; 535 | bgd.bg_free_blocks_count -= used_blocks_count as u16; 536 | 537 | // Fill inodes bitmap 538 | let begin_inode = i * inodes_per_group; 539 | let used_inodes_count = min( 540 | inodes_per_group, 541 | superblock.s_first_ino.saturating_sub(begin_inode), 542 | ); 543 | fill_bitmap( 544 | bg_inode_bitmap as u64 * block_size, 545 | inode_usage_bitmap_size as usize * block_size as usize, 546 | used_inodes_count as usize, 547 | dev, 548 | )?; 549 | bgd.bg_free_inodes_count -= used_inodes_count as u16; 550 | 551 | // If containing the root inode 552 | if (begin_inode..(begin_inode + inodes_per_group)).contains(&ROOT_INODE) { 553 | bgd.bg_used_dirs_count += 1; 554 | } 555 | 556 | superblock.s_free_blocks_count += bgd.bg_free_blocks_count as u32; 557 | superblock.s_free_inodes_count += bgd.bg_free_inodes_count as u32; 558 | 559 | bgd.write(i, &superblock, dev)?; 560 | } 561 | 562 | // Ensure the block size is sufficient to fit the `.` and `..` entries of the root directory 563 | // This should be enforced by the size of the superblock, which is larger 564 | assert!(block_size >= ((size_of::() + 8) * 2) as u64); 565 | // Prepare root inode for `.` and `..` entries 566 | let root_size_low = (block_size & 0xffffffff) as u32; 567 | let root_size_high = ((block_size >> 32) & 0xffffffff) as u32; 568 | 569 | // Create root directory 570 | let root_inode_id = NonZeroU32::new(ROOT_INODE).unwrap(); 571 | let mut root_dir = INode { 572 | i_mode: 0x4000 | 0o755, 573 | i_uid: 0, 574 | i_size: root_size_low, 575 | i_atime: create_timestamp, 576 | i_ctime: create_timestamp, 577 | i_mtime: create_timestamp, 578 | i_dtime: 0, 579 | i_gid: 0, 580 | i_links_count: 2, // `.` and `..` entries 581 | i_blocks: (block_size / 512) as _, 582 | i_flags: 0, 583 | i_osd1: 0, 584 | i_block: [0; 15], 585 | i_generation: 0, 586 | i_file_acl: 0, 587 | i_dir_acl: root_size_high, 588 | i_faddr: 0, 589 | _padding: [0; 12], 590 | }; 591 | 592 | // Create `.` and `..` entries for the root directory 593 | let entries_block = used_blocks_end - 1; 594 | let entries_block_off = entries_block as u64 * block_size as u64; 595 | root_dir.i_block[0] = entries_block; 596 | dev.seek(SeekFrom::Start(entries_block_off))?; 597 | let self_entry = DirectoryEntry { 598 | inode: root_inode_id.into(), 599 | rec_len: (size_of::() + 8) as _, 600 | name_len: 1, 601 | file_type: 0, // TODO fill with type when driver is compatible 602 | }; 603 | dev.write_all(reinterpret(&self_entry))?; 604 | dev.write_all(b".")?; 605 | let parent_entry = DirectoryEntry { 606 | inode: root_inode_id.into(), 607 | rec_len: (block_size - (size_of::() + 8) as u64) as _, 608 | name_len: 2, 609 | file_type: 0, // TODO fill with type when driver is compatible 610 | }; 611 | dev.seek(SeekFrom::Start(entries_block_off + 16))?; 612 | dev.write_all(reinterpret(&parent_entry)).unwrap(); 613 | dev.write_all(b"..")?; 614 | 615 | // Write root inode 616 | let root_inode_off = INode::get_disk_offset(root_inode_id, &superblock, dev)?; 617 | dev.seek(SeekFrom::Start(root_inode_off))?; 618 | dev.write_all(reinterpret(&root_dir))?; 619 | 620 | // Write superblock 621 | dev.seek(SeekFrom::Start(SUPERBLOCK_OFFSET))?; 622 | dev.write_all(reinterpret(&superblock))?; 623 | 624 | Ok(()) 625 | } 626 | } 627 | 628 | #[cfg(test)] 629 | mod test { 630 | use super::*; 631 | use std::fs::{File, OpenOptions}; 632 | use std::io::Write; 633 | use std::path::PathBuf; 634 | 635 | fn prepare_device(size: usize) -> io::Result<(PathBuf, File)> { 636 | let path = "/tmp/maestro-utils-test-mkfs-ext2".into(); 637 | let mut dev = OpenOptions::new() 638 | .create(true) 639 | .read(true) 640 | .write(true) 641 | .truncate(true) 642 | .open(&path)?; 643 | let sector_size = 512; 644 | let buf = vec![0; sector_size]; 645 | for _ in 0..(size / sector_size) { 646 | dev.write_all(&buf)?; 647 | } 648 | dev.seek(SeekFrom::Start(0))?; 649 | Ok((path, dev)) 650 | } 651 | 652 | #[test] 653 | pub fn check_fs() { 654 | let disk_size = 64 * 1024 * 1024; 655 | let (dev_path, mut dev) = prepare_device(disk_size).unwrap(); 656 | 657 | let factory = Ext2Factory::default(); 658 | factory.create(&mut dev).unwrap(); 659 | 660 | assert!(factory.is_present(&mut dev).unwrap()); 661 | 662 | // TODO suspend this test, to be fixed later 663 | /*let status = Command::new("fsck.ext2") 664 | .arg("-fnv") 665 | .arg(dev_path) 666 | .status() 667 | .unwrap(); 668 | assert!(status.success());*/ 669 | } 670 | } 671 | -------------------------------------------------------------------------------- /mkfs/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `mkfs` tool allows to create a filesystem on a device. 2 | 3 | #![feature(int_roundings)] 4 | 5 | mod ext2; 6 | 7 | use std::collections::HashMap; 8 | use std::env; 9 | use std::fs::File; 10 | use std::fs::OpenOptions; 11 | use std::io; 12 | use std::path::PathBuf; 13 | use std::process::exit; 14 | use utils::prompt::prompt; 15 | 16 | /// Structure storing command line arguments. 17 | #[derive(Default)] 18 | struct Args { 19 | /// The name of the current program used in command line. 20 | prog: String, 21 | /// The select filesystem type. 22 | fs_type: String, 23 | 24 | /// If true, print command line help. 25 | help: bool, 26 | 27 | /// The path to the device file on which the filesystem will be created. 28 | device_path: Option, 29 | } 30 | 31 | fn parse_args() -> Args { 32 | let mut args: Args = Default::default(); 33 | let mut iter = env::args(); 34 | 35 | args.prog = iter.next().unwrap_or("mkfs".to_owned()); 36 | 37 | let fs_type = if args.prog.contains('.') { 38 | args.prog.split('.').last() 39 | } else { 40 | None 41 | }; 42 | args.fs_type = fs_type.unwrap_or("ext2").to_owned(); 43 | 44 | while let Some(arg) = iter.next() { 45 | match arg.as_str() { 46 | "-h" | "--help" => args.help = true, 47 | 48 | // TODO implement other options 49 | // TODO get device path 50 | _ => { 51 | // TODO handle case when several devices are given 52 | args.device_path = Some(PathBuf::from(arg)); 53 | } 54 | } 55 | } 56 | 57 | args 58 | } 59 | 60 | /// A trait representing an object used to create a filesystem on a device. 61 | pub trait FSFactory { 62 | /// Tells whether a filesystem corresponding to the factory is present on the given device 63 | /// `dev`. 64 | fn is_present(&self, dev: &mut File) -> io::Result; 65 | 66 | /// Creates the filesystem on the given device `dev`. 67 | fn create(&self, dev: &mut File) -> io::Result<()>; 68 | } 69 | 70 | fn main() { 71 | let args = parse_args(); 72 | 73 | // TODO build factory according to arguments 74 | let factories = HashMap::<&str, Box>::from([( 75 | "ext2", 76 | Box::::default() as Box, 77 | )]); 78 | let factory = factories.get(args.fs_type.as_str()).unwrap_or_else(|| { 79 | eprintln!("{}: invalid filesystem type `{}`", args.prog, args.fs_type); 80 | exit(1); 81 | }); 82 | 83 | let device_path = args.device_path.unwrap_or_else(|| { 84 | eprintln!("{}: specify path to a device", args.prog); 85 | exit(1); 86 | }); 87 | 88 | let mut file = OpenOptions::new() 89 | .read(true) 90 | .write(true) 91 | .open(&device_path) 92 | .unwrap_or_else(|e| { 93 | eprintln!("{}: {}: {}", args.prog, device_path.display(), e); 94 | exit(1); 95 | }); 96 | 97 | let prev_fs = factories.iter().find(|(_, factory)| { 98 | factory.is_present(&mut file).unwrap_or_else(|e| { 99 | eprintln!("{}: {}: {}", args.prog, device_path.display(), e); 100 | exit(1); 101 | }) 102 | }); 103 | if let Some((prev_fs_type, _prev_fs_factory)) = prev_fs { 104 | println!( 105 | "{} contains a file system of type: {}", 106 | device_path.display(), 107 | prev_fs_type 108 | ); 109 | // TODO print details on fs (use factory) 110 | 111 | let confirm = prompt(Some("Proceed anyway? (y/N) "), false) 112 | .map(|s| s.to_lowercase() == "y") 113 | .unwrap_or(false); 114 | if !confirm { 115 | eprintln!("Abort."); 116 | exit(1); 117 | } 118 | } 119 | 120 | factory.create(&mut file).unwrap_or_else(|e| { 121 | eprintln!("{}: failed to create filesystem: {}", args.prog, e); 122 | exit(1); 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /mount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mount" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libc = "*" 8 | -------------------------------------------------------------------------------- /mount/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `mount` command allows to mount a filesystem. 2 | 3 | use std::env; 4 | use std::ffi::c_ulong; 5 | use std::ffi::CString; 6 | use std::io; 7 | use std::io::Error; 8 | use std::process::exit; 9 | use std::ptr::null; 10 | 11 | /// Mount flag: TODO doc 12 | const MS_RDONLY: c_ulong = 1; 13 | /// Mount flag: TODO doc 14 | const MS_NOSUID: c_ulong = 2; 15 | /// Mount flag: TODO doc 16 | const MS_NODEV: c_ulong = 4; 17 | /// Mount flag: TODO doc 18 | const MS_NOEXEC: c_ulong = 8; 19 | /// Mount flag: TODO doc 20 | const MS_SYNCHRONOUS: c_ulong = 16; 21 | /// Mount flag: TODO doc 22 | const MS_REMOUNT: c_ulong = 32; 23 | /// Mount flag: TODO doc 24 | const MS_MANDLOCK: c_ulong = 64; 25 | /// Mount flag: TODO doc 26 | const MS_DIRSYNC: c_ulong = 128; 27 | /// Mount flag: TODO doc 28 | const MS_NOATIME: c_ulong = 1024; 29 | /// Mount flag: TODO doc 30 | const MS_NODIRATIME: c_ulong = 2048; 31 | /// Mount flag: TODO doc 32 | const MS_BIND: c_ulong = 4096; 33 | /// Mount flag: TODO doc 34 | const MS_MOVE: c_ulong = 8192; 35 | /// Mount flag: TODO doc 36 | const MS_REC: c_ulong = 16384; 37 | /// Mount flag: TODO doc 38 | const MS_SILENT: c_ulong = 32768; 39 | /// Mount flag: TODO doc 40 | const MS_POSIXACL: c_ulong = 1 << 16; 41 | /// Mount flag: TODO doc 42 | const MS_UNBINDABLE: c_ulong = 1 << 17; 43 | /// Mount flag: TODO doc 44 | const MS_PRIVATE: c_ulong = 1 << 18; 45 | /// Mount flag: TODO doc 46 | const MS_SLAVE: c_ulong = 1 << 19; 47 | /// Mount flag: TODO doc 48 | const MS_SHARED: c_ulong = 1 << 20; 49 | /// Mount flag: TODO doc 50 | const MS_RELATIME: c_ulong = 1 << 21; 51 | /// Mount flag: TODO doc 52 | const MS_KERNMOUNT: c_ulong = 1 << 22; 53 | /// Mount flag: TODO doc 54 | const MS_I_VERSION: c_ulong = 1 << 23; 55 | /// Mount flag: TODO doc 56 | const MS_STRICTATIME: c_ulong = 1 << 24; 57 | /// Mount flag: TODO doc 58 | const MS_LAZYTIME: c_ulong = 1 << 25; 59 | /// Mount flag: TODO doc 60 | const MS_NOREMOTELOCK: c_ulong = 1 << 27; 61 | /// Mount flag: TODO doc 62 | const MS_NOSEC: c_ulong = 1 << 28; 63 | /// Mount flag: TODO doc 64 | const MS_BORN: c_ulong = 1 << 29; 65 | /// Mount flag: TODO doc 66 | const MS_ACTIVE: c_ulong = 1 << 30; 67 | /// Mount flag: TODO doc 68 | const MS_NOUSER: c_ulong = 1 << 31; 69 | /// Mount flag: TODO doc 70 | const MS_MGC_VAL: c_ulong = 0xc0ed0000; 71 | /// Mount flag: TODO doc 72 | const MS_MGC_MSK: c_ulong = 0xffff0000; 73 | 74 | /// Prints the command's usage. 75 | /// 76 | /// `bin` is the name of the current binary. 77 | fn print_usage(bin: &str) { 78 | eprintln!("Usage:"); 79 | eprintln!(" {bin} [-h]"); 80 | eprintln!(" {bin} -l"); 81 | eprintln!(" {bin} -a"); 82 | eprintln!(" {bin} [device] dir"); 83 | eprintln!(); 84 | eprintln!("Options:"); 85 | eprintln!(" -h:\t\tprints usage"); 86 | eprintln!(" -l:\t\tlists mounted filesystems"); 87 | eprintln!(" -a:\t\tmounts every filesystems specified in the /etc/fstab file"); 88 | eprintln!(" device:\tthe device to mount. If not specified, the command attempts to find the device using the /dev/fstab file"); 89 | eprintln!(" dir:\t\tthe directory on which the filesystem is to be mounted"); 90 | } 91 | 92 | /// Mounts a filesystem. 93 | /// 94 | /// Arguments: 95 | /// TODO 96 | pub fn mount_fs( 97 | source: &str, 98 | target: &str, 99 | fs_type: Option<&str>, 100 | mountflags: c_ulong, 101 | data: Option<&[u8]>, 102 | ) -> io::Result<()> { 103 | let source_c = CString::new(source).unwrap(); 104 | let target_c = CString::new(target).unwrap(); 105 | 106 | let fs_type_c = fs_type.map(|fs_type| CString::new(fs_type).unwrap()); 107 | let fs_type_ptr = fs_type_c 108 | .as_ref() 109 | .map(|fs_type| fs_type.as_ptr()) 110 | .unwrap_or(null::<_>()); 111 | 112 | let data = data.map(|data| data.as_ptr()).unwrap_or(null::<_>()); 113 | 114 | let ret = unsafe { 115 | libc::mount( 116 | source_c.as_ptr(), 117 | target_c.as_ptr(), 118 | fs_type_ptr, 119 | mountflags, 120 | data as _, 121 | ) 122 | }; 123 | if ret < 0 { 124 | return Err(Error::last_os_error()); 125 | } 126 | Ok(()) 127 | } 128 | 129 | fn main() { 130 | let args: Vec = env::args().collect(); 131 | let bin = args.first().map(String::as_str).unwrap_or("mount"); 132 | 133 | if args.is_empty() { 134 | print_usage(bin); 135 | exit(1); 136 | } 137 | 138 | let a: Vec<&str> = args.iter().map(String::as_str).collect(); 139 | match a[1..] { 140 | [] => { 141 | print_usage(bin); 142 | exit(1); 143 | } 144 | 145 | ["-h"] => { 146 | print_usage(bin); 147 | exit(0); 148 | } 149 | 150 | ["-l"] => { 151 | // TODO print /etc/mtab to stdout 152 | todo!(); 153 | } 154 | 155 | ["-a"] => { 156 | // TODO iterate on entries of /etc/fstab and mount all 157 | todo!(); 158 | } 159 | 160 | [device, dir] => { 161 | // TODO detect filesystem type? 162 | mount_fs(device, dir, Some("ext2"), 0, None).unwrap(); // TODO handle error 163 | } 164 | 165 | [_dir] => { 166 | // TODO lookup in /etc/fstab to get device, then mount 167 | todo!(); 168 | } 169 | 170 | _ => { 171 | print_usage(bin); 172 | exit(1); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /nologin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nologin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /nologin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The command `nologin` simply refuses login. 2 | 3 | use std::io::Write; 4 | use std::process::exit; 5 | use std::{fs, io}; 6 | 7 | fn main() { 8 | let msg = fs::read("/etc/nologin.txt").ok(); 9 | let msg = msg 10 | .as_deref() 11 | .unwrap_or(b"This account is currently not available."); 12 | let _ = io::stdout().write_all(msg); 13 | exit(1); 14 | } 15 | -------------------------------------------------------------------------------- /powerctl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "powerctl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | utils = { path = "../utils" } 8 | libc = "*" 9 | -------------------------------------------------------------------------------- /powerctl/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `powerctl` command implements power control features such as shutdown, reboot, halt, etc... 2 | 3 | mod power; 4 | 5 | use power::halt; 6 | use power::poweroff; 7 | use power::reboot; 8 | use power::suspend; 9 | use std::env; 10 | use std::process::exit; 11 | 12 | /// Prints command usage. 13 | /// 14 | /// `name` is the name of the binary. 15 | fn print_usage(name: Option<&str>) { 16 | let name = name.unwrap_or("shutdown/poweroff/reboot/halt/suspend"); 17 | 18 | println!("Usage:"); 19 | println!(" {} [-f] [-n]", name); 20 | println!(); 21 | println!("Controls the system's power."); 22 | println!(); 23 | println!("Options:"); 24 | println!(" -f\tforce operation without stopping services"); 25 | println!(" -n\tdon't synchronize storage"); 26 | } 27 | 28 | /// Structure representing input arguments. 29 | struct Args { 30 | /// If true, the command forces the operation and doesn't stop services. 31 | force: bool, 32 | /// If true, the command doesn't sync storage. 33 | no_sync: bool, 34 | } 35 | 36 | /// Parses arguments from the given array. 37 | fn parse_args(args: Vec) -> Option { 38 | let mut err = false; 39 | let mut result = Args { 40 | force: false, 41 | no_sync: false, 42 | }; 43 | 44 | args.into_iter().skip(1).for_each(|a| match a.as_str() { 45 | "-f" | "--force" => result.force = true, 46 | "-n" | "--no-sync" => result.no_sync = true, 47 | 48 | _ => { 49 | eprintln!("Invalid argument `{}`", a); 50 | err = true; 51 | } 52 | }); 53 | 54 | if !err { 55 | Some(result) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | fn main() { 62 | let args: Vec = env::args().collect(); 63 | 64 | if args.is_empty() { 65 | print_usage(None); 66 | exit(1); 67 | } 68 | 69 | // Binary name 70 | let bin = args[0].clone(); 71 | // Parsing arguments 72 | let a = match parse_args(args) { 73 | Some(a) => a, 74 | None => exit(1), 75 | }; 76 | 77 | if !a.force { 78 | // TODO Stop services 79 | } 80 | if !a.no_sync { 81 | // TODO Sync storage 82 | } 83 | 84 | match bin.as_str() { 85 | "shutdown" | "poweroff" => poweroff(), 86 | "reboot" => reboot(), 87 | "halt" => halt(), 88 | "suspend" => suspend(), 89 | 90 | _ => { 91 | print_usage(Some(&bin)); 92 | exit(1); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /powerctl/src/power.rs: -------------------------------------------------------------------------------- 1 | //! This module handles power management system calls. 2 | 3 | use std::os::raw::c_long; 4 | 5 | /// The ID of the `reboot` system call. 6 | const REBOOT_ID: c_long = 0x58; 7 | 8 | /// First magic number. 9 | const MAGIC: u32 = 0xde145e83; 10 | /// Second magic number. 11 | const MAGIC2: u32 = 0x40367d6e; 12 | 13 | /// Command to power off the system. 14 | const CMD_POWEROFF: u32 = 0; 15 | /// Command to reboot the system. 16 | const CMD_REBOOT: u32 = 1; 17 | /// Command to halt the system. 18 | const CMD_HALT: u32 = 2; 19 | /// Command to suspend the system. 20 | const CMD_SUSPEND: u32 = 3; 21 | 22 | /// Power off the system. 23 | pub fn poweroff() { 24 | unsafe { 25 | libc::syscall(REBOOT_ID, MAGIC, MAGIC2, CMD_POWEROFF); 26 | } 27 | } 28 | 29 | /// Reboots the system. 30 | pub fn reboot() { 31 | unsafe { 32 | libc::syscall(REBOOT_ID, MAGIC, MAGIC2, CMD_REBOOT); 33 | } 34 | } 35 | 36 | /// Halts the system. 37 | pub fn halt() { 38 | unsafe { 39 | libc::syscall(REBOOT_ID, MAGIC, MAGIC2, CMD_HALT); 40 | } 41 | } 42 | 43 | /// Suspends the system. 44 | pub fn suspend() { 45 | unsafe { 46 | libc::syscall(REBOOT_ID, MAGIC, MAGIC2, CMD_SUSPEND); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ps" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | utils = { path = "../utils" } 10 | -------------------------------------------------------------------------------- /ps/src/format.rs: -------------------------------------------------------------------------------- 1 | //! This module implement display formats. 2 | 3 | use std::fmt; 4 | 5 | /// Enumeration of data names. 6 | pub enum Name { 7 | /// The real user ID. 8 | Ruser, 9 | /// The effective user ID. 10 | User, 11 | /// The real group ID. 12 | Rgroup, 13 | /// The effective group ID. 14 | Group, 15 | /// The process ID. 16 | Pid, 17 | /// The parent process ID. 18 | Ppid, 19 | ///// The process group ID. 20 | //Pgid, 21 | ///// TODO doc 22 | //Pcpu, 23 | ///// TODO doc 24 | //Vsz, 25 | ///// The nice value. 26 | //Nice, 27 | ///// TODO doc 28 | //Etime, 29 | ///// TODO doc 30 | //Time, 31 | /// The terminal. 32 | Tty, 33 | /// The name. 34 | Comm, 35 | /// The command line arguments. 36 | Args, 37 | } 38 | 39 | impl Name { 40 | /// Returns the variant associated with the given string. 41 | fn from_str(s: &str) -> Option { 42 | match s { 43 | "ruser" => Some(Self::Ruser), 44 | "user" => Some(Self::User), 45 | "rgroup" => Some(Self::Rgroup), 46 | "group" => Some(Self::Group), 47 | "pid" => Some(Self::Pid), 48 | "ppid" => Some(Self::Ppid), 49 | // TODO "pgid" => Some(Self::Pgid), 50 | // TODO "pcpu" => Some(Self::Pcpu), 51 | // TODO "vsz" => Some(Self::Vsz), 52 | // TODO "nice" => Some(Self::Nice), 53 | // TODO "etime" => Some(Self::Etime), 54 | // TODO "time" => Some(Self::Time), 55 | "tty" => Some(Self::Tty), 56 | "comm" => Some(Self::Comm), 57 | "args" => Some(Self::Args), 58 | 59 | _ => None, 60 | } 61 | } 62 | 63 | /// Returns the default display name. 64 | fn get_default_display(&self) -> &'static str { 65 | match self { 66 | Self::Ruser => "RUSER", 67 | Self::User => "USER", 68 | Self::Rgroup => "RGROUP", 69 | Self::Group => "GROUP", 70 | Self::Pid => "PID", 71 | Self::Ppid => "PPID", 72 | // TODO Self::Pgid => "PGID", 73 | // TODO Self::Pcpu => "%CPU", 74 | // TODO Self::Vsz => "VSZ", 75 | // TODO Self::Nice => "NI", 76 | // TODO Self::Etime => "ELAPSED", 77 | // TODO Self::Time => "TIME", 78 | Self::Tty => "TT", 79 | Self::Comm | Self::Args => "COMMAND", 80 | } 81 | } 82 | } 83 | 84 | /// Structure representing a display format. 85 | pub struct DisplayFormat { 86 | /// The list of names to be displayed along with their respective display name. 87 | pub names: Vec<(Name, String)>, 88 | } 89 | 90 | impl DisplayFormat { 91 | /// Creates a new empty instance. 92 | pub fn new() -> Self { 93 | Self { names: Vec::new() } 94 | } 95 | 96 | /// Tells whether the display format can be printed. 97 | pub fn can_print(&self) -> bool { 98 | self.names 99 | .iter() 100 | .any(|(_, display_name)| !display_name.is_empty()) 101 | } 102 | 103 | /// Concats the given format to the current. 104 | pub fn concat(&mut self, mut other: Self) { 105 | self.names.append(&mut other.names); 106 | } 107 | } 108 | 109 | impl Default for DisplayFormat { 110 | fn default() -> Self { 111 | Self { 112 | names: vec![ 113 | (Name::Pid, Name::Pid.get_default_display().to_owned()), 114 | (Name::Tty, Name::Tty.get_default_display().to_owned()), 115 | // TODO (Name::Time, Name::Time.get_default_display().to_owned()), 116 | (Name::Comm, Name::Comm.get_default_display().to_owned()), 117 | ], 118 | } 119 | } 120 | } 121 | 122 | impl fmt::Display for DisplayFormat { 123 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 124 | for (name, disp) in &self.names { 125 | if !disp.is_empty() { 126 | write!(fmt, " {}", disp)?; 127 | } else { 128 | // Add padding the same size as the default display name 129 | 130 | let len = name.get_default_display().len() + 1; 131 | for _ in 0..len { 132 | write!(fmt, " ")?; 133 | } 134 | } 135 | } 136 | 137 | Ok(()) 138 | } 139 | } 140 | 141 | /// A parser for display formats. 142 | pub struct FormatParser<'a> { 143 | /// The format to be parsed. 144 | format: &'a str, 145 | } 146 | 147 | impl<'a> FormatParser<'a> { 148 | /// Creates a new instance for the given format. 149 | pub fn new(format: &'a str) -> Self { 150 | Self { format } 151 | } 152 | 153 | /// Parses the format and returns the corresponding structure. 154 | pub fn yield_format(self) -> Result { 155 | let mut names = vec![]; 156 | 157 | let s = self 158 | .format 159 | .split(|ch: char| ch == ',' || ch.is_ascii_whitespace()); 160 | for n in s { 161 | // Splitting name and display name 162 | let split = n.find('=').map(|i| { 163 | let (name, display_name) = n.split_at(i); 164 | (name, display_name[1..].to_owned()) 165 | }); 166 | 167 | if let Some((name, display_name)) = split { 168 | let name = Name::from_str(name).ok_or(())?; 169 | 170 | names.push((name, display_name)); 171 | } else { 172 | let name = Name::from_str(n).ok_or(())?; 173 | let display_name = name.get_default_display(); 174 | 175 | names.push((name, display_name.to_owned())); 176 | } 177 | } 178 | 179 | Ok(DisplayFormat { names }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /ps/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `ps` command allows to print the list of processes running on the system. 2 | 3 | mod format; 4 | mod process; 5 | mod util; 6 | 7 | use format::DisplayFormat; 8 | use format::FormatParser; 9 | use process::Process; 10 | use process::ProcessIterator; 11 | use std::env; 12 | use std::path::PathBuf; 13 | use std::process::exit; 14 | 15 | // TODO Implement every arguments 16 | // TODO Implement environment variables 17 | // TODO i18n 18 | 19 | extern "C" { 20 | fn geteuid() -> u32; 21 | fn getegid() -> u32; 22 | } 23 | 24 | /// Enumeration of selectors used to accept or reject a process in the list. 25 | enum Selector { 26 | /// Selects processes attached to a terminal (-a). 27 | Terminal, 28 | /// Selects all processes (-A, -e). 29 | All, 30 | /// Selects all processes except session leaders (-d). 31 | NoLeaders, 32 | /// Selects all processes whose session leader effective group ID corresponds (-g). 33 | Gid(u32), 34 | /// Selects all processes whose real group ID corresponds (-G). 35 | Rgid(u32), 36 | /// Selects processes whose PID corresponds (-p). 37 | Pid(u32), 38 | /// Selects processes attached to the given TTY (-t). 39 | Term(String), 40 | /// Selects processes whose effective user ID corresponds (-u). 41 | Uid(u32), 42 | /// Selects processes whose real user ID corresponds (-U). 43 | Ruid(u32), 44 | } 45 | 46 | impl Selector { 47 | /// Tells whether the given process is accepted by the selector. 48 | pub fn is_accepted(&self, proc: &Process) -> bool { 49 | match self { 50 | Self::Terminal => proc.tty.is_some(), 51 | Self::All => true, 52 | 53 | Self::NoLeaders => { 54 | // TODO 55 | true 56 | } 57 | 58 | Self::Gid(gid) => proc.gid == *gid, 59 | Self::Rgid(rgid) => proc.rgid == *rgid, 60 | Self::Pid(pid) => proc.pid == *pid, 61 | 62 | Self::Term(tty) => { 63 | if let Some(t) = &proc.tty { 64 | t == tty || t == &format!("tty{}", tty) 65 | } else { 66 | false 67 | } 68 | } 69 | 70 | Self::Uid(uid) => proc.uid == *uid, 71 | Self::Ruid(ruid) => proc.ruid == *ruid, 72 | } 73 | } 74 | } 75 | 76 | /// Prints the command line usage on the standard error output. 77 | fn print_usage() { 78 | eprintln!(); 79 | eprintln!( 80 | "Usage: ps [-aA] [-defl] [-g grouplist] [-G grouplist] [ -n namelist] \ 81 | [-o format]... [-p proclist] [-t termlist] [ -u userlist] [-U userlist]" 82 | ); 83 | eprintln!(); 84 | eprintln!("For more details see ps(1)."); 85 | } 86 | 87 | /// Prints an error, then exits. 88 | fn error(msg: &str) -> ! { 89 | eprintln!("error: {}", msg); 90 | 91 | print_usage(); 92 | exit(1); 93 | } 94 | 95 | /// Parses arguments and returns the selectors list and format. 96 | fn parse_args() -> (Vec, DisplayFormat) { 97 | // Results 98 | let mut selectors = Vec::new(); 99 | let mut format = DisplayFormat::new(); 100 | let mut default_format = true; 101 | 102 | // Reading users and groups lists 103 | let users = 104 | utils::user::read_passwd(&PathBuf::from(utils::user::PASSWD_PATH)).unwrap_or(vec![]); 105 | let groups = utils::user::read_group(&PathBuf::from(utils::user::GROUP_PATH)).unwrap_or(vec![]); 106 | 107 | // TODO -l and -f 108 | let mut args = env::args().skip(1); 109 | while let Some(arg) = args.next() { 110 | match arg.as_str() { 111 | "-a" => selectors.push(Selector::Terminal), 112 | "-A" | "-e" => selectors.push(Selector::All), 113 | "-d" => selectors.push(Selector::NoLeaders), 114 | 115 | "-o" => { 116 | if let Some(format_str) = args.next() { 117 | let parser = FormatParser::new(&format_str); 118 | 119 | match parser.yield_format() { 120 | Ok(f) => { 121 | format.concat(f); 122 | default_format = false; 123 | } 124 | 125 | Err(_) => error("invalid format"), 126 | } 127 | } else { 128 | error("format specification must follow -o"); 129 | } 130 | } 131 | 132 | "-p" => { 133 | if let Some(pids_str) = args.next() { 134 | let iter = match util::parse_nbr_list(&pids_str) { 135 | Ok(list) => list.into_iter(), 136 | 137 | Err(_) => error("process ID list syntax error"), 138 | }; 139 | 140 | let mut pids = iter.map(Selector::Pid).collect(); 141 | selectors.append(&mut pids); 142 | } else { 143 | error("list of process IDs must follow -p"); 144 | } 145 | } 146 | 147 | "-t" => { 148 | if let Some(termlist) = args.next() { 149 | let mut terms = util::parse_str_list(&termlist) 150 | .into_iter() 151 | .map(Selector::Term) 152 | .collect(); 153 | 154 | selectors.append(&mut terms); 155 | } else { 156 | } 157 | } 158 | 159 | "-u" => { 160 | if let Some(users_list) = args.next() { 161 | util::parse_str_list(&users_list) 162 | .into_iter() 163 | .for_each(|user| match users.iter().find(|u| u.login_name == user) { 164 | Some(user) => selectors.push(Selector::Uid(user.uid)), 165 | 166 | None => match user.parse::() { 167 | Ok(uid) => selectors.push(Selector::Uid(uid)), 168 | Err(_) => {} 169 | }, 170 | }); 171 | } else { 172 | let uid = unsafe { geteuid() }; 173 | selectors.push(Selector::Uid(uid)); 174 | } 175 | } 176 | 177 | "-U" => { 178 | if let Some(users_list) = args.next() { 179 | util::parse_str_list(&users_list) 180 | .into_iter() 181 | .for_each(|user| match users.iter().find(|u| u.login_name == user) { 182 | Some(user) => selectors.push(Selector::Ruid(user.uid)), 183 | 184 | None => match user.parse::() { 185 | Ok(uid) => selectors.push(Selector::Ruid(uid)), 186 | Err(_) => {} 187 | }, 188 | }); 189 | } else { 190 | error("list of real users must follow -U"); 191 | } 192 | } 193 | 194 | "-g" => { 195 | if let Some(groups_list) = args.next() { 196 | util::parse_str_list(&groups_list) 197 | .into_iter() 198 | .for_each( 199 | |group| match groups.iter().find(|g| g.group_name == group) { 200 | Some(group) => selectors.push(Selector::Gid(group.gid)), 201 | 202 | None => match group.parse::() { 203 | Ok(gid) => selectors.push(Selector::Gid(gid)), 204 | Err(_) => {} 205 | }, 206 | }, 207 | ); 208 | } else { 209 | let gid = unsafe { getegid() }; 210 | selectors.push(Selector::Gid(gid)); 211 | } 212 | } 213 | 214 | "-G" => { 215 | if let Some(groups_list) = args.next() { 216 | util::parse_str_list(&groups_list) 217 | .into_iter() 218 | .for_each( 219 | |group| match groups.iter().find(|g| g.group_name == group) { 220 | Some(group) => selectors.push(Selector::Rgid(group.gid)), 221 | 222 | None => match group.parse::() { 223 | Ok(gid) => selectors.push(Selector::Rgid(gid)), 224 | Err(_) => {} 225 | }, 226 | }, 227 | ); 228 | } else { 229 | error("list of real groups must follow -G"); 230 | } 231 | } 232 | 233 | _ => error("error: garbage option"), 234 | } 235 | } 236 | 237 | // If no selector is specified, use defaults 238 | if selectors.is_empty() { 239 | let curr_euid = unsafe { geteuid() }; 240 | 241 | // TODO Select only processes that share the same controlling terminal 242 | selectors.push(Selector::Uid(curr_euid)); 243 | } 244 | 245 | // If no format is specified, use default 246 | if default_format { 247 | format = DisplayFormat::default(); 248 | } 249 | 250 | (selectors, format) 251 | } 252 | 253 | fn main() { 254 | let (selectors, format) = parse_args(); 255 | 256 | // Printing header 257 | if format.can_print() { 258 | println!("{}", format); 259 | } 260 | 261 | // Creating the process iterator 262 | let proc_iter = match ProcessIterator::new() { 263 | Ok(i) => i, 264 | Err(e) => { 265 | eprintln!("error: cannot read processes list: {}", e); 266 | exit(1); 267 | } 268 | }; 269 | 270 | // TODO When a PID, UID, GID... is specified, use files' metadata to avoid reading 271 | 272 | // Filtering processes according to arguments 273 | // A process is accepted if it matches at least one selector (union) 274 | let proc_iter = proc_iter.filter(|proc| { 275 | for s in &selectors { 276 | if s.is_accepted(proc) { 277 | return true; 278 | } 279 | } 280 | 281 | false 282 | }); 283 | 284 | // Printing processes 285 | for proc in proc_iter { 286 | println!("{}", proc.display(&format)); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /ps/src/process/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing process structures. 2 | 3 | mod status_parser; 4 | 5 | use crate::format::DisplayFormat; 6 | use crate::format::Name; 7 | use status_parser::StatusParser; 8 | use std::fmt; 9 | use std::fs; 10 | use std::fs::ReadDir; 11 | use std::io; 12 | 13 | /// Structure representing a process. 14 | #[derive(Debug, Default)] 15 | pub struct Process { 16 | /// The process's name. 17 | pub name: String, 18 | /// The full command. 19 | pub full_cmd: String, 20 | 21 | /// The process's PID. 22 | pub pid: u32, 23 | /// The PID of the process's parent. 24 | pub ppid: u32, 25 | 26 | /// The process's user ID. 27 | pub uid: u32, 28 | /// The process's real user ID. 29 | pub ruid: u32, 30 | /// The process's group ID. 31 | pub gid: u32, 32 | /// The process's real group ID. 33 | pub rgid: u32, 34 | 35 | /// The process's TTY. 36 | pub tty: Option, 37 | } 38 | 39 | impl Process { 40 | /// Returns an instance of ProcessDisplay, used to display a process with the given format. 41 | pub fn display<'p, 'f>(&'p self, format: &'f DisplayFormat) -> ProcessDisplay<'p, 'f> { 42 | ProcessDisplay { proc: self, format } 43 | } 44 | } 45 | 46 | /// Structure used to display a process's informations. 47 | pub struct ProcessDisplay<'p, 'f> { 48 | /// The process. 49 | proc: &'p Process, 50 | /// The display format. 51 | format: &'f DisplayFormat, 52 | } 53 | 54 | impl<'f, 'p> fmt::Display for ProcessDisplay<'f, 'p> { 55 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 56 | for (name, _) in &self.format.names { 57 | match name { 58 | Name::Ruser => write!(fmt, " {}", self.proc.ruid)?, 59 | Name::User => write!(fmt, " {}", self.proc.uid)?, 60 | Name::Rgroup => write!(fmt, " {}", self.proc.rgid)?, 61 | Name::Group => write!(fmt, " {}", self.proc.gid)?, 62 | Name::Pid => write!(fmt, " {}", self.proc.pid)?, 63 | Name::Ppid => write!(fmt, " {}", self.proc.ppid)?, 64 | // TODO Name::Pgid => write!(fmt, " {}", self.proc.pgid)?, 65 | // TODO Name::Pcpu => todo!(), 66 | // TODO Name::Vsz => todo!(), 67 | // TODO Name::Nice => todo!(), 68 | // TODO Name::Etime => todo!(), 69 | // TODO Name::Time => todo!(), 70 | Name::Tty => match &self.proc.tty { 71 | Some(tty) => write!(fmt, " {}", tty)?, 72 | None => write!(fmt, " ?")?, 73 | }, 74 | 75 | Name::Comm => write!(fmt, " {}", self.proc.name)?, 76 | Name::Args => write!(fmt, " {}", self.proc.full_cmd)?, 77 | } 78 | } 79 | 80 | Ok(()) 81 | } 82 | } 83 | 84 | /// An iterator on the system's processes. 85 | pub struct ProcessIterator { 86 | /// The iterator on procfs files. 87 | files: ReadDir, 88 | } 89 | 90 | impl ProcessIterator { 91 | /// Creates a new instance. 92 | pub fn new() -> Result { 93 | Ok(Self { 94 | files: fs::read_dir("/proc")?, 95 | }) 96 | } 97 | 98 | /// Returns the next PID in the iterator. 99 | /// If no PID is left, the function returns None. 100 | /// On error, the caller must retry. 101 | fn next_pid(&mut self) -> Option> { 102 | let entry = match self.files.next()? { 103 | Ok(e) => e, 104 | Err(_) => return Some(Err(())), 105 | }; 106 | 107 | let file_name = entry.file_name().into_string(); 108 | 109 | match file_name { 110 | Ok(file_name) => Some(file_name.parse::().map_err(|_| ())), 111 | Err(_) => Some(Err(())), 112 | } 113 | } 114 | 115 | /// Parses the status of process with PID `pid`. 116 | fn yield_proc(pid: u32) -> Result { 117 | let status_parser = StatusParser::new(pid).map_err(|_| ())?; 118 | status_parser.yield_process() 119 | } 120 | } 121 | 122 | impl Iterator for ProcessIterator { 123 | type Item = Process; 124 | 125 | fn next(&mut self) -> Option { 126 | // Looping until finding a valid process or reaching the end 127 | loop { 128 | // Getting the next PID 129 | let pid = match self.next_pid()? { 130 | Ok(pid) => pid, 131 | Err(_) => continue, 132 | }; 133 | 134 | // Parsing process status 135 | match Self::yield_proc(pid) { 136 | Ok(proc) => return Some(proc), 137 | 138 | // On fail, try next process 139 | Err(_) => continue, 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ps/src/process/status_parser.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a parser for the status of a process. 2 | 3 | use super::Process; 4 | use std::fs; 5 | use std::io; 6 | 7 | /// The status parser parses the content of the file `/proc/{pid}/status`, where `{pid}` is the pid 8 | /// of the process. 9 | pub struct StatusParser { 10 | /// The status file's content. 11 | status_content: String, 12 | /// The cmdline file's content. 13 | cmdline_content: String, 14 | } 15 | 16 | impl StatusParser { 17 | /// Creates a new instance for the given pid `pid`. 18 | pub fn new(pid: u32) -> Result { 19 | // Reading the process's status file 20 | let status_content = fs::read_to_string(format!("/proc/{}/status", pid))?; 21 | 22 | // Reading the process's status file 23 | let cmdline_content = fs::read_to_string(format!("/proc/{}/cmdline", pid))?; 24 | 25 | Ok(Self { 26 | status_content, 27 | cmdline_content, 28 | }) 29 | } 30 | 31 | /// Creates a process structure from files. 32 | pub fn yield_process(self) -> Result { 33 | let mut proc = Process::default(); 34 | 35 | for line in self.status_content.split('\n') { 36 | if line.is_empty() { 37 | continue; 38 | } 39 | 40 | // Splitting the line to get the name and value 41 | let (name, value) = line.find(':').map(|i| line.split_at(i)).ok_or(())?; 42 | let name = name.to_lowercase(); 43 | let value = value[1..].trim(); 44 | 45 | match name.as_str() { 46 | "name" => proc.name = value.to_string(), 47 | 48 | "pid" => proc.pid = value.parse::().map_err(|_| ())?, 49 | "ppid" => proc.ppid = value.parse::().map_err(|_| ())?, 50 | 51 | "uid" => { 52 | let mut s = value.split_whitespace(); 53 | 54 | proc.uid = s.nth(0).ok_or(())?.parse::().map_err(|_| ())?; 55 | proc.ruid = s.nth(2).ok_or(())?.parse::().map_err(|_| ())?; 56 | } 57 | "gid" => { 58 | let mut s = value.split_whitespace(); 59 | 60 | proc.gid = s.nth(0).ok_or(())?.parse::().map_err(|_| ())?; 61 | proc.rgid = s.nth(2).ok_or(())?.parse::().map_err(|_| ())?; 62 | } 63 | 64 | // TODO tty 65 | _ => {} 66 | } 67 | } 68 | 69 | // Getting full command line 70 | let mut cmdline = self 71 | .cmdline_content 72 | .chars() 73 | .map(|c| match c { 74 | '\0' => ' ', 75 | _ => c, 76 | }) 77 | .collect::(); 78 | cmdline.pop(); 79 | proc.full_cmd = cmdline; 80 | 81 | Ok(proc) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ps/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Module implementing utility functions. 2 | 3 | /// Parses a list of strings from the given string. 4 | /// On syntax error, the function returns an error. 5 | pub fn parse_str_list(s: &str) -> Vec { 6 | s.split(|c: char| c == ' ' || c == '\t' || c == ',') 7 | .map(|s| s.to_string()) 8 | .collect() 9 | } 10 | 11 | /// Parses a list of numbers from the given string. 12 | /// On syntax error, the function returns an error. 13 | pub fn parse_nbr_list(s: &str) -> Result, ()> { 14 | let iter = s.split(|c: char| c == ' ' || c == '\t' || c == ','); 15 | let mut list = Vec::new(); 16 | 17 | for e in iter { 18 | list.push(e.parse::().map_err(|_| ())?); 19 | } 20 | 21 | Ok(list) 22 | } 23 | -------------------------------------------------------------------------------- /rmmod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rmmod" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | utils = { path = "../utils" } 8 | libc = "*" 9 | -------------------------------------------------------------------------------- /rmmod/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `rmmod` command unloads a module. 2 | 3 | use std::env; 4 | use std::ffi::c_long; 5 | use std::ffi::CString; 6 | use std::io::Error; 7 | use std::process::exit; 8 | 9 | /// The ID of the `delete_module` system call. 10 | const DELETE_MODULE_ID: c_long = 0x81; 11 | 12 | /// Prints usage. 13 | fn print_usage() { 14 | println!("Usage:"); 15 | println!(" rmmod "); 16 | println!(); 17 | println!("Unloads a kernel module"); 18 | } 19 | 20 | fn main() { 21 | let args: Vec = env::args().collect(); 22 | 23 | if args.len() != 2 { 24 | print_usage(); 25 | exit(1); 26 | } 27 | 28 | let name = &args[1]; 29 | let c_name = CString::new(name.as_bytes()).unwrap(); // TODO handle error 30 | 31 | let ret = unsafe { libc::syscall(DELETE_MODULE_ID, c_name.as_ptr(), 0) }; 32 | if ret < 0 { 33 | eprintln!( 34 | "rmmod: cannot unload module `{}`: {}", 35 | name, 36 | Error::last_os_error() 37 | ); 38 | exit(1); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /su/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "su" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | utils = { path = "../utils" } 10 | -------------------------------------------------------------------------------- /su/src/main.rs: -------------------------------------------------------------------------------- 1 | //! `su` is a command allowing to run an other command with a substitute user and group ID. 2 | 3 | use std::env; 4 | use std::process::exit; 5 | use std::process::Command; 6 | 7 | use utils::prompt::prompt; 8 | 9 | /// Structure representing the command's arguments. 10 | #[derive(Default)] 11 | struct Args<'s> { 12 | /// The user which executes the command. If None, using root. 13 | user: Option<&'s str>, 14 | /// The group which executtes the command. If None, using root. 15 | group: Option<&'s str>, 16 | 17 | /// The shell to execute. If None, using the default. 18 | shell: Option<&'s str>, 19 | 20 | /// Arguments for the command to execute. 21 | args: Vec<&'s str>, 22 | } 23 | 24 | /// Parses the given CLI arguments `args` and returns their representation in the `Args` structure. 25 | fn parse_args(args: &Vec) -> Args<'_> { 26 | let mut result = Args::default(); 27 | // Iterating on arguments, skipping binary's name 28 | let mut iter = args.iter().skip(1).peekable(); 29 | 30 | // Tells whether arguments contain initial options 31 | let has_options = { 32 | iter.peek() 33 | .map(|first_arg| { 34 | first_arg 35 | .chars() 36 | .peekable() 37 | .peek() 38 | .map(|first_char| *first_char == '-') 39 | .unwrap_or(false) 40 | }) 41 | .unwrap_or(false) 42 | }; 43 | 44 | // Parsing options if present 45 | if has_options { 46 | while let Some(a) = iter.next() { 47 | if a == "-" { 48 | break; 49 | } 50 | 51 | // TODO 52 | } 53 | } 54 | 55 | result.user = iter.next().map(|s| s.as_str()); 56 | result.args = iter.map(|s| s.as_str()).collect(); 57 | 58 | result 59 | } 60 | 61 | fn main() { 62 | let args: Vec = env::args().collect(); 63 | let args = parse_args(&args); 64 | 65 | let _user = args.user.unwrap_or("root"); 66 | // TODO Read user's entry 67 | let shell = args.shell.unwrap_or("TODO"); 68 | 69 | let _pass = prompt(None, true); 70 | let correct = false; // TODO Check password against user's 71 | 72 | if correct { 73 | // TODO Change user 74 | 75 | // Running the shell 76 | let status = Command::new(&shell) 77 | .args(args.args) 78 | // TODO Set env 79 | .status() 80 | .unwrap_or_else(|_| { 81 | eprintln!("su: Failed to run shell `{}`", shell); 82 | exit(1); 83 | }); 84 | 85 | // Exiting with the shell's status 86 | exit(status.code().unwrap()); 87 | } else { 88 | eprintln!("su: Authentication failure"); 89 | exit(1); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /umount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "umount" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libc = "*" 8 | -------------------------------------------------------------------------------- /umount/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `mount` command allows to unmount a filesystem. 2 | 3 | use std::env; 4 | use std::ffi::{CStr, CString}; 5 | use std::fs; 6 | use std::io; 7 | use std::io::Error; 8 | use std::os::unix::ffi::OsStrExt; 9 | use std::path::PathBuf; 10 | use std::process::exit; 11 | 12 | /// Prints the command's usage. 13 | /// 14 | /// `bin` is the name of the current binary. 15 | fn print_usage(bin: &str) { 16 | eprintln!("Usage:"); 17 | eprintln!(" {bin} [-R] dir"); 18 | eprintln!(); 19 | eprintln!("Options:"); 20 | eprintln!(" -R:\tunmounts filesystems recursively"); 21 | eprintln!(" dir:\tthe directory on which the filesystem is mounted"); 22 | } 23 | 24 | /// Unmounts the filesystem at the given path `target`. 25 | pub fn unmount_fs(target: &CStr) -> io::Result<()> { 26 | let ret = unsafe { libc::umount(target.as_ptr() as _) }; 27 | if ret < 0 { 28 | return Err(Error::last_os_error()); 29 | } 30 | Ok(()) 31 | } 32 | 33 | /// Lists active mount points. 34 | pub fn list_mount_points() -> io::Result> { 35 | let content = fs::read_to_string("/etc/mtab")?; 36 | Ok(content 37 | .split('\n') 38 | .filter_map(|entry| Some(entry.split(' ').nth(1)?.into())) 39 | .collect()) 40 | } 41 | 42 | fn main() { 43 | let args: Vec = env::args().collect(); 44 | match args.len() { 45 | 0 => { 46 | print_usage("umount"); 47 | exit(1); 48 | } 49 | 50 | 2 if args[1] != "-R" => { 51 | let s = CString::new(args[1].as_bytes()).unwrap(); 52 | unmount_fs(&s).unwrap_or_else(|e| { 53 | eprintln!("{}: cannot unmount `{}`: {e}", args[0], args[1]); 54 | exit(1); 55 | }); 56 | } 57 | 58 | 3 if args[1] == "-R" => { 59 | let mut mount_points = list_mount_points().unwrap_or_else(|e| { 60 | eprintln!("{}: cannot list mount points: {e}", args[0]); 61 | exit(1); 62 | }); 63 | mount_points.sort_unstable(); 64 | 65 | let inner_mount_points_iter = mount_points.iter().filter(|mp| mp.starts_with(&args[1])); 66 | 67 | for mp in inner_mount_points_iter { 68 | let s = CString::new(mp.as_os_str().as_bytes()).unwrap(); 69 | unmount_fs(&s).unwrap_or_else(|e| { 70 | eprintln!("{}: cannot unmount `{}`: {e}", args[0], args[1]); 71 | exit(1); 72 | }); 73 | } 74 | } 75 | 76 | _ => { 77 | print_usage(&args[0]); 78 | exit(1); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /usrgrp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usrgrp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /usrgrp/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `usrgrp` command implements the following commands: 2 | //! - `useradd`: create a new user 3 | //! - `usermod`: modify an user 4 | //! - `userdel`: delete an user 5 | //! - `groupadd`: create a new group 6 | //! - `groupmod`: modify a group 7 | //! - `groupdel`: delete a group 8 | 9 | use std::env; 10 | use std::process::exit; 11 | 12 | /// Command line arguments. 13 | pub enum Args { 14 | UserAdd { 15 | /// If set, display usage. 16 | help: bool, 17 | 18 | /// The home directory of the new user. 19 | home_dir: Option, 20 | 21 | /// The expiry timestamp for the user. 22 | expire_ts: Option, 23 | /// The inactivity period of the new user. 24 | inactive_period: Option, 25 | 26 | /// If set, create the user's home directory. 27 | create_home: bool, 28 | 29 | /// If set, create a group with the same name as the user. 30 | user_group: bool, 31 | 32 | /// The UID for the new user. 33 | uid: Option, 34 | /// The ID or name of the group for the new user. 35 | gid: Option, 36 | 37 | /// The encrypted password for the new password. 38 | password: Option, 39 | /// The login shell of the new account. 40 | shell: Option, 41 | 42 | /// The username. 43 | name: String, 44 | }, 45 | 46 | UserMod { 47 | /// If set, display usage. 48 | help: bool, 49 | 50 | // TODO 51 | /// The username. 52 | name: String, 53 | }, 54 | 55 | UserDel { 56 | /// If set, display usage. 57 | help: bool, 58 | 59 | /// If set, delete the user even if still logged in. 60 | force: bool, 61 | 62 | /// If set, remove the home directory and mail spool. 63 | remove_home: bool, 64 | 65 | /// The username. 66 | name: String, 67 | }, 68 | 69 | GroupAdd { 70 | /// If set, display usage. 71 | help: bool, 72 | 73 | /// The ID to use for the group. 74 | gid: Option, 75 | 76 | /// The group name. 77 | name: String, 78 | }, 79 | 80 | GroupMod { 81 | /// If set, display usage. 82 | help: bool, 83 | 84 | // TODO 85 | /// The group name. 86 | name: String, 87 | }, 88 | 89 | GroupDel { 90 | /// If set, display usage. 91 | help: bool, 92 | 93 | /// If set, delete the group even if it is the primary group of a user. 94 | force: bool, 95 | 96 | /// The group name. 97 | name: String, 98 | }, 99 | } 100 | 101 | /// Parses command line arguments. 102 | fn parse_args() -> Args { 103 | let mut args_iter = env::args(); 104 | 105 | let bin = match args_iter.next() { 106 | Some(bin) => bin, 107 | 108 | None => { 109 | // TODO return usage 110 | todo!(); 111 | } 112 | }; 113 | 114 | match bin.as_str() { 115 | "useradd" => { 116 | // TODO 117 | todo!(); 118 | } 119 | 120 | "usermod" => { 121 | // TODO 122 | todo!(); 123 | } 124 | 125 | "userdel" => { 126 | // TODO 127 | todo!(); 128 | } 129 | 130 | "groupadd" => { 131 | // TODO 132 | todo!(); 133 | } 134 | 135 | "groupmod" => { 136 | // TODO 137 | todo!(); 138 | } 139 | 140 | "groupdel" => { 141 | // TODO 142 | todo!(); 143 | } 144 | 145 | _ => exit(1), 146 | } 147 | } 148 | 149 | fn main() { 150 | let args = parse_args(); 151 | 152 | match args { 153 | Args::UserAdd { .. } => { 154 | // TODO 155 | todo!(); 156 | } 157 | 158 | Args::UserMod { .. } => { 159 | // TODO 160 | todo!(); 161 | } 162 | 163 | Args::UserDel { .. } => { 164 | // TODO 165 | todo!(); 166 | } 167 | 168 | Args::GroupAdd { .. } => { 169 | // TODO 170 | todo!(); 171 | } 172 | 173 | Args::GroupMod { .. } => { 174 | // TODO 175 | todo!(); 176 | } 177 | 178 | Args::GroupDel { .. } => { 179 | // TODO 180 | todo!(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | argon2 = { version = "0.5.2", features = ["password-hash"] } 10 | libc = "0.2.119" 11 | rand_core = { version = "0.6.4", features = ["getrandom"] } 12 | -------------------------------------------------------------------------------- /utils/src/disk.rs: -------------------------------------------------------------------------------- 1 | //! Implements disk-related utility functions. 2 | 3 | use libc::ioctl; 4 | use std::ffi::c_long; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::Error; 8 | use std::os::fd::AsRawFd; 9 | use std::os::unix::fs::FileTypeExt; 10 | 11 | /// ioctl macro: Command. 12 | macro_rules! ioc { 13 | ($a:expr, $b:expr, $c:expr, $d:expr) => { 14 | (($a) << 30) | (($b) << 8) | ($c) | (($d) << 16) 15 | }; 16 | } 17 | 18 | /// ioctl macro: Read command. 19 | #[macro_export] 20 | macro_rules! ior { 21 | ($a:expr, $b:expr, $c:ty) => { 22 | ioc!(2, $a, $b, std::mem::size_of::<$c>() as c_long) 23 | }; 24 | } 25 | 26 | /// ioctl command: Get size of disk in number of sectors. 27 | const BLKGETSIZE64: c_long = ior!(0x12, 114, u64); 28 | 29 | /// Returns the number of sectors on the given device. 30 | pub fn get_disk_size(dev: &File) -> io::Result { 31 | let metadata = dev.metadata()?; 32 | let file_type = metadata.file_type(); 33 | if file_type.is_block_device() || file_type.is_char_device() { 34 | let mut size = 0; 35 | let ret = unsafe { ioctl(dev.as_raw_fd(), BLKGETSIZE64 as _, &mut size) }; 36 | if ret < 0 { 37 | return Err(Error::last_os_error()); 38 | } 39 | Ok(size / 512) 40 | } else if file_type.is_file() { 41 | Ok(metadata.len() / 512) 42 | } else { 43 | Ok(0) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module implements features common to several commands. 2 | 3 | pub mod disk; 4 | pub mod prompt; 5 | pub mod user; 6 | pub mod util; 7 | -------------------------------------------------------------------------------- /utils/src/prompt.rs: -------------------------------------------------------------------------------- 1 | //! This module implements prompting. 2 | 3 | use libc::tcgetattr; 4 | use libc::tcsetattr; 5 | use libc::termios; 6 | use libc::ECHO; 7 | use libc::ECHOE; 8 | use libc::ICANON; 9 | use libc::STDIN_FILENO; 10 | use libc::TCSANOW; 11 | use libc::VMIN; 12 | use std::io; 13 | use std::io::BufRead; 14 | use std::io::Write; 15 | use std::mem::MaybeUninit; 16 | 17 | // TODO Add line edition 18 | /// Show a prompt. This function returns when a newline is received. 19 | /// 20 | /// Arguments: 21 | /// - `prompt` is the prompt's text. If `None`, the function uses the default text. 22 | /// - `hidden` tells whether the input is hidden. 23 | pub fn prompt(prompt: Option<&str>, hidden: bool) -> Option { 24 | let prompt = prompt.unwrap_or("Password: "); 25 | 26 | // Save termios state 27 | let saved_termios = unsafe { 28 | let mut t: termios = MaybeUninit::zeroed().assume_init(); 29 | tcgetattr(STDIN_FILENO, &mut t); 30 | t 31 | }; 32 | 33 | if hidden { 34 | // Set temporary termios 35 | let mut termios = saved_termios; 36 | termios.c_lflag &= !(ICANON | ECHO | ECHOE); 37 | termios.c_cc[VMIN] = 1; 38 | 39 | unsafe { 40 | tcsetattr(STDIN_FILENO, TCSANOW, &termios); 41 | } 42 | } 43 | 44 | // Show prompt 45 | print!("{prompt}"); 46 | let _ = io::stdout().flush(); 47 | 48 | // Read input 49 | let input = io::stdin().lock().lines().next()?.unwrap_or(String::new()); 50 | 51 | if hidden { 52 | println!(); 53 | 54 | // Restore termios state 55 | unsafe { 56 | tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios); 57 | } 58 | } 59 | 60 | Some(input) 61 | } 62 | -------------------------------------------------------------------------------- /utils/src/user.rs: -------------------------------------------------------------------------------- 1 | //! The passwd, shadow and group files are mainly used to store respectively the users list, the 2 | //! passwords list and the groups list. 3 | 4 | use argon2::password_hash::SaltString; 5 | use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; 6 | use rand_core::OsRng; 7 | use std::error::Error; 8 | use std::ffi::OsString; 9 | use std::fs::File; 10 | use std::fs::OpenOptions; 11 | use std::io; 12 | use std::io::BufRead; 13 | use std::io::BufReader; 14 | use std::io::Write; 15 | use std::os::unix::ffi::OsStrExt; 16 | use std::path::Path; 17 | use std::path::PathBuf; 18 | 19 | /// The path to the passwd file. 20 | pub const PASSWD_PATH: &str = "/etc/passwd"; 21 | /// The path to the shadow file. 22 | pub const SHADOW_PATH: &str = "/etc/shadow"; 23 | /// The path to the group file. 24 | pub const GROUP_PATH: &str = "/etc/group"; 25 | 26 | // TODO For each files, use a backup file with the same path but with `-` appended at the end 27 | 28 | /// Hashes the given clear password and returns it with a generated salt, in the format 29 | /// required for the shadow file. 30 | pub fn hash_password(pass: &str) -> Result { 31 | let salt = SaltString::generate(&mut OsRng); 32 | let hash = Argon2::default().hash_password(pass.as_bytes(), &salt)?; 33 | Ok(hash.to_string()) 34 | } 35 | 36 | /// Tells whether the given password `pass` corresponds to the hashed password `hash`. 37 | pub fn check_password(hash: &str, pass: &str) -> bool { 38 | let Ok(parsed_hash) = PasswordHash::new(hash) else { 39 | return false; 40 | }; 41 | Argon2::default() 42 | .verify_password(pass.as_bytes(), &parsed_hash) 43 | .is_ok() 44 | } 45 | 46 | /// Structure representing a user. This entry is present in the passwd file. 47 | pub struct User { 48 | /// The user's login name. 49 | pub login_name: String, 50 | /// The user's encrypted password. If `x`, the password is located in the shadow file. 51 | pub password: String, 52 | /// The user ID. 53 | pub uid: u32, 54 | /// The user's group ID. 55 | pub gid: u32, 56 | /// User comment. 57 | pub comment: String, 58 | /// User's home path. 59 | pub home: PathBuf, 60 | /// User's command interpreter. 61 | pub interpreter: String, 62 | } 63 | 64 | impl User { 65 | /// Check the given (not hashed) password `pass` against the current entry. 66 | /// 67 | /// If the function returns None, the callee must use the shadow entry. 68 | pub fn check_password(&self, pass: &str) -> Option { 69 | if self.password.is_empty() || self.password == "x" { 70 | return None; 71 | } 72 | Some(check_password(&self.password, pass)) 73 | } 74 | } 75 | 76 | /// Structure representing a shadow entry. 77 | pub struct Shadow { 78 | /// The user's login name. 79 | pub login_name: String, 80 | /// The user's encrypted password. 81 | pub password: String, 82 | /// The date of the last password change in number of days since the Unix Epoch. 83 | pub last_change: u32, 84 | /// The minimum number of days to wait before the user becomes usable. 85 | pub minimum_age: Option, 86 | /// The maximum number of days to the password is valid. If this delay is exceeded, the user 87 | /// will be asked to change her password next time she logs. 88 | pub maximum_age: Option, 89 | /// The number of days before the password expires during which the user will be warned to 90 | /// change her password. 91 | pub warning_period: Option, 92 | /// The number of days after password expiration during which the user can still use her 93 | /// password. Passed this delay, she will have to contact her system administrator. 94 | pub inactivity_period: Option, 95 | /// The number of days after the Unix Epoch after which login to the user account will be 96 | /// denied. 97 | pub account_expiration: Option, 98 | /// Reserved field. 99 | pub reserved: String, 100 | } 101 | 102 | impl Shadow { 103 | /// Check the given (not hashed) password `pass` against the current entry. 104 | pub fn check_password(&self, pass: &str) -> bool { 105 | check_password(&self.password, pass) 106 | } 107 | } 108 | 109 | /// Structure representing a group. 110 | pub struct Group { 111 | /// The group's name. 112 | pub group_name: String, 113 | /// The encrypted group's password. 114 | pub password: String, 115 | /// The group's ID. 116 | pub gid: u32, 117 | /// The list of users member of this group, comma-separated. 118 | pub users_list: String, 119 | } 120 | 121 | /// Reads and parses the file at path `path`. 122 | fn read(path: &Path) -> io::Result>>> { 123 | let file = File::open(path)?; 124 | Ok(BufReader::new(file) 125 | .lines() 126 | .map(|l| Ok(l?.split(':').map(str::to_owned).collect::>()))) 127 | } 128 | 129 | /// Writes the file at path `path` with data `data`. 130 | fn write>( 131 | path: &Path, 132 | data: I, 133 | ) -> io::Result<()> { 134 | let mut file = OpenOptions::new().create(true).write(true).open(path)?; 135 | for line in data { 136 | for (i, elem) in line.iter().enumerate() { 137 | file.write_all(elem.as_bytes())?; 138 | if i + 1 < line.len() { 139 | file.write_all(b":")?; 140 | } 141 | } 142 | file.write_all(b"\n")?; 143 | } 144 | Ok(()) 145 | } 146 | 147 | /// Reads the passwd file. 148 | /// 149 | /// `path` is the path to the file. 150 | pub fn read_passwd(path: &Path) -> Result, Box> { 151 | read(path)? 152 | .into_iter() 153 | .enumerate() 154 | .map(|(i, data)| { 155 | let data = data?; 156 | if data.len() != 7 { 157 | return Err(format!("Invalid entry on line `{}`", i + 1).into()); 158 | } 159 | 160 | Ok(User { 161 | login_name: data[0].clone(), 162 | password: data[1].clone(), 163 | uid: data[2].parse::<_>()?, 164 | gid: data[3].parse::<_>()?, 165 | comment: data[4].clone(), 166 | home: data[5].clone().into(), 167 | interpreter: data[6].clone(), 168 | }) 169 | }) 170 | .collect() 171 | } 172 | 173 | /// Writes the passwd file. 174 | /// 175 | /// `path` is the path to the file. 176 | pub fn write_passwd(path: &Path, entries: &[User]) -> io::Result<()> { 177 | let iter = entries.iter().map(|e| { 178 | [ 179 | e.login_name.clone().into(), 180 | e.password.clone().into(), 181 | e.uid.to_string().into(), 182 | e.gid.to_string().into(), 183 | e.comment.clone().into(), 184 | e.home.clone().into_os_string(), 185 | e.interpreter.clone().into(), 186 | ] 187 | }); 188 | write(path, iter) 189 | } 190 | 191 | /// Reads the shadow file. 192 | /// 193 | /// `path` is the path to the file. 194 | pub fn read_shadow(path: &Path) -> Result, Box> { 195 | read(path)? 196 | .into_iter() 197 | .enumerate() 198 | .map(|(i, data)| { 199 | let data = data?; 200 | if data.len() != 9 { 201 | return Err(format!("Invalid entry on line `{}`", i + 1).into()); 202 | } 203 | 204 | Ok(Shadow { 205 | login_name: data[0].clone(), 206 | password: data[1].clone(), 207 | last_change: data[2].parse::<_>().unwrap_or(0), 208 | minimum_age: data[3].parse::<_>().ok(), 209 | maximum_age: data[4].parse::<_>().ok(), 210 | warning_period: data[5].parse::<_>().ok(), 211 | inactivity_period: data[6].parse::<_>().ok(), 212 | account_expiration: data[7].parse::<_>().ok(), 213 | reserved: data[8].clone(), 214 | }) 215 | }) 216 | .collect() 217 | } 218 | 219 | /// Writes the shadow file. 220 | /// 221 | /// `path` is the path to the file. 222 | pub fn write_shadow(path: &Path, entries: &[Shadow]) -> io::Result<()> { 223 | let iter = entries.iter().map(|e| { 224 | [ 225 | e.login_name.clone().into(), 226 | e.password.clone().into(), 227 | e.last_change.to_string().into(), 228 | e.minimum_age 229 | .as_ref() 230 | .map(u32::to_string) 231 | .unwrap_or_default() 232 | .into(), 233 | e.maximum_age 234 | .as_ref() 235 | .map(u32::to_string) 236 | .unwrap_or_default() 237 | .into(), 238 | e.warning_period 239 | .as_ref() 240 | .map(u32::to_string) 241 | .unwrap_or_default() 242 | .into(), 243 | e.inactivity_period 244 | .as_ref() 245 | .map(u32::to_string) 246 | .unwrap_or_default() 247 | .into(), 248 | e.account_expiration 249 | .as_ref() 250 | .map(u32::to_string) 251 | .unwrap_or_default() 252 | .into(), 253 | e.reserved.clone().into(), 254 | ] 255 | }); 256 | write(path, iter) 257 | } 258 | 259 | /// Reads the group file. 260 | /// 261 | /// `path` is the path to the file. 262 | pub fn read_group(path: &Path) -> Result, Box> { 263 | read(path)? 264 | .into_iter() 265 | .enumerate() 266 | .map(|(i, data)| { 267 | let data = data?; 268 | if data.len() != 4 { 269 | return Err(format!("Invalid entry on line `{}`", i + 1).into()); 270 | } 271 | 272 | Ok(Group { 273 | group_name: data[0].clone(), 274 | password: data[1].clone(), 275 | gid: data[2].parse::<_>()?, 276 | users_list: data[3].clone(), 277 | }) 278 | }) 279 | .collect() 280 | } 281 | 282 | /// Writes the group file. 283 | /// 284 | /// `path` is the path to the file. 285 | pub fn write_group(path: &Path, entries: &[Group]) -> io::Result<()> { 286 | let iter = entries.iter().map(|e| { 287 | [ 288 | e.group_name.clone().into(), 289 | e.password.clone().into(), 290 | e.gid.to_string().into(), 291 | e.users_list.clone().into(), 292 | ] 293 | }); 294 | write(path, iter) 295 | } 296 | 297 | /// Sets the current user. 298 | pub fn set(uid: u32, gid: u32) -> io::Result<()> { 299 | let result = unsafe { libc::setuid(uid) }; 300 | if result < 0 { 301 | return Err(io::Error::last_os_error()); 302 | } 303 | let result = unsafe { libc::setgid(gid) }; 304 | if result < 0 { 305 | return Err(io::Error::last_os_error()); 306 | } 307 | Ok(()) 308 | } 309 | -------------------------------------------------------------------------------- /utils/src/util.rs: -------------------------------------------------------------------------------- 1 | //! This module implements utility functions. 2 | 3 | use std::ffi::c_char; 4 | use std::fmt; 5 | use std::mem::size_of; 6 | use std::ops::Shl; 7 | use std::slice; 8 | use std::thread; 9 | use std::time::Duration; 10 | use std::time::SystemTime; 11 | use std::time::UNIX_EPOCH; 12 | 13 | /// Reinterprets the given reference as a slice. 14 | pub fn reinterpret(val: &T) -> &[u8] { 15 | unsafe { slice::from_raw_parts(val as *const _ as *const u8, size_of::()) } 16 | } 17 | 18 | /// Turns the given buffer into a [`String`]. 19 | pub fn array_to_string(buf: &[c_char]) -> String { 20 | buf.into_iter() 21 | .take_while(|b| **b != 0) 22 | .map(|b| (*b) as u8 as char) 23 | .collect() 24 | } 25 | 26 | /// Returns the hostname of the system. 27 | pub fn get_hostname() -> String { 28 | let mut hostname: [i8; 4096] = [0; 4096]; 29 | unsafe { 30 | libc::gethostname(hostname.as_mut_ptr() as _, hostname.len()); 31 | array_to_string(&hostname) 32 | } 33 | } 34 | 35 | /// Returns the current timestamp since the Unix epoch. 36 | pub fn get_timestamp() -> Duration { 37 | SystemTime::now() 38 | .duration_since(UNIX_EPOCH) 39 | .expect("System clock panic!") 40 | } 41 | 42 | /// Executes the closure `f`. 43 | /// 44 | /// If the closure returns Ok, the function returns directly. 45 | /// 46 | /// If it return an error, the function ensures the execution takes at least the given duration 47 | /// `d`. 48 | pub fn exec_wait T>(d: Duration, f: F) -> T { 49 | let start = get_timestamp(); 50 | let result = f(); 51 | // Wait until the given amount of time is spent 52 | loop { 53 | let ts = get_timestamp(); 54 | if ts >= start + d { 55 | break; 56 | } 57 | thread::sleep(ts - start); 58 | } 59 | result 60 | } 61 | 62 | /// Fills the given buffer with random bytes. 63 | pub fn get_random(buf: &mut [u8]) { 64 | unsafe { 65 | libc::getrandom(buf.as_mut_ptr() as _, buf.len(), 0); 66 | } 67 | } 68 | 69 | /// Computes 2^^n on unsigned integers (where `^^` is an exponent). 70 | /// 71 | /// If n < 0, the behaviour is undefined. 72 | pub fn pow2(n: T) -> T 73 | where 74 | T: From + Shl, 75 | { 76 | T::from(1) << n 77 | } 78 | 79 | /// Performs the log2 operation on the given integer. 80 | /// 81 | /// If the result is undefined, the function returns `None`. 82 | pub fn log2(n: u64) -> Option { 83 | let n = (u64::BITS as u64) - n.leading_zeros() as u64; 84 | if n > 0 { 85 | Some(n - 1) 86 | } else { 87 | None 88 | } 89 | } 90 | 91 | /// Structure representing a number of bytes. 92 | pub struct ByteSize(pub u64); 93 | 94 | impl ByteSize { 95 | /// Creates a size from a given number of sectors. 96 | pub fn from_sectors_count(cnt: u64) -> Self { 97 | Self(cnt * 512) 98 | } 99 | } 100 | 101 | impl fmt::Display for ByteSize { 102 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | let mut order = log2(self.0).unwrap_or(0) / log2(1024).unwrap(); 104 | let suffix = match order { 105 | 0 => "bytes", 106 | 1 => "KiB", 107 | 2 => "MiB", 108 | 3 => "GiB", 109 | 4 => "TiB", 110 | 5 => "PiB", 111 | 6 => "EiB", 112 | 7 => "ZiB", 113 | 8 => "YiB", 114 | _ => { 115 | order = 0; 116 | "bytes" 117 | } 118 | }; 119 | 120 | let unit = 1024u64.pow(order as u32); 121 | let nbr = self.0 / unit; 122 | write!(fmt, "{nbr} {suffix}") 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod test { 128 | use super::*; 129 | 130 | #[test] 131 | fn bytesize() { 132 | assert_eq!(ByteSize(0).to_string(), "0 bytes"); 133 | assert_eq!(ByteSize(1).to_string(), "1 bytes"); 134 | assert_eq!(ByteSize(1023).to_string(), "1023 bytes"); 135 | assert_eq!(ByteSize(1024).to_string(), "1 KiB"); 136 | assert_eq!(ByteSize(1025).to_string(), "1 KiB"); 137 | assert_eq!(ByteSize(2048).to_string(), "2 KiB"); 138 | assert_eq!(ByteSize(1024 * 1024).to_string(), "1 MiB"); 139 | assert_eq!(ByteSize(1024 * 1024 * 1024).to_string(), "1 GiB"); 140 | assert_eq!(ByteSize(1024 * 1024 * 1024 * 1024).to_string(), "1 TiB"); 141 | } 142 | } 143 | --------------------------------------------------------------------------------