gpt-3.1.0/.cargo_vcs_info.json0000644000000001360000000000100116270ustar { "git": { "sha1": "78666cff50a430dac31b59778f581012075718b3" }, "path_in_vcs": "" }gpt-3.1.0/.github/workflows/rust.yml000064400000000000000000000022001046102023000155260ustar 00000000000000# Workflow recipe from https://github.com/actions-rs/meta/blob/master/recipes/msrv.md on: push: branches: [master, 3.0.1] pull_request: branches: [master] env: RUSTFLAGS: -Dwarnings # change manually in matrix rust_min: 1.60.0 jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - stable - nightly - 1.60.0 steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - run: cargo check test: name: Test Suite runs-on: ubuntu-latest strategy: matrix: rust: - stable - nightly - 1.60.0 steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - run: cargo test clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.rust_min }} components: clippy - run: cargo clippy gpt-3.1.0/.gitignore000064400000000000000000000000371046102023000124070ustar 00000000000000target tests/header Cargo.lock gpt-3.1.0/Cargo.lock0000644000000330450000000000100076070ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "time", "wasm-bindgen", "winapi", ] [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "crc" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "gpt" version = "3.1.0" dependencies = [ "bitflags 2.3.3", "crc", "log", "simplelog", "tempfile", "uuid", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "iana-time-zone" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "proc-macro2" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "rustix" version = "0.37.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "simplelog" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1348164456f72ca0116e4538bdaabb0ddb622c7d9f16387c725af3e96d6001c" dependencies = [ "chrono", "log", "termcolor", ] [[package]] name = "syn" version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ "autocfg", "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys", ] [[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "time" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "unicode-ident" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "uuid" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ "getrandom", ] [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" gpt-3.1.0/Cargo.toml0000644000000021520000000000100076250ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "gpt" version = "3.1.0" authors = [ "Chris Ober ", "Chris Holcombe ", "Luca Bruno ", ] description = "A pure-Rust library to work with GPT partition tables." homepage = "https://github.com/Quyzi/gpt" documentation = "https://docs.rs/gpt" readme = "README.md" license = "MIT" [dependencies.bitflags] version = "2.0" [dependencies.crc] version = "3.0" [dependencies.log] version = "0.4" [dependencies.uuid] version = "1.0" features = ["v4"] [dev-dependencies.simplelog] version = "0.11" [dev-dependencies.tempfile] version = "3.0" gpt-3.1.0/Cargo.toml.orig000064400000000000000000000010341046102023000133040ustar 00000000000000[package] name = "gpt" version = "3.1.0" description = "A pure-Rust library to work with GPT partition tables." documentation = "https://docs.rs/gpt" authors = [ "Chris Ober ", "Chris Holcombe ", "Luca Bruno " ] license = "MIT" homepage = "https://github.com/Quyzi/gpt" edition = "2021" rust-version = "1.60" [dependencies] bitflags = "2.0" crc = "3.0" log = "0.4" uuid = { version = "1.0", features = ["v4"] } [dev-dependencies] simplelog = "0.11" tempfile = "3.0" gpt-3.1.0/LICENSE000064400000000000000000000020461046102023000114260ustar 00000000000000MIT License Copyright (c) 2017 Quyzi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gpt-3.1.0/README.md000064400000000000000000000025561046102023000117060ustar 00000000000000# gpt [![crates.io](https://img.shields.io/crates/v/gpt.svg)](https://crates.io/crates/gpt) ![minimum rust 1.60](https://img.shields.io/badge/rust-1.60%2B-orange.svg) [![Documentation](https://docs.rs/gpt/badge.svg)](https://docs.rs/gpt) A pure-Rust library to work with GPT partition tables. `gpt` provides support for manipulating (R/W) GPT headers and partition tables. It supports any that implements the `Read + Write + Seek + Debug` traits. ## Example ```rust use gpt; use simplelog::{Config, LevelFilter, SimpleLogger}; use std::io; fn main() { // Setup logging let _ = SimpleLogger::init(LevelFilter::Warn, Config::default()); // Inspect disk image, handling errors. if let Err(e) = run() { eprintln!("Failed to inspect image: {}", e); std::process::exit(1) } } fn run() -> io::Result<()> { // First parameter is target disk image (optional, default: fixtures sample) let sample = "tests/fixtures/gpt-linux-disk-01.img".to_string(); let input = std::env::args().nth(1).unwrap_or(sample); // Open disk image. let diskpath = std::path::Path::new(&input); let cfg = gpt::GptConfig::new().writable(false); let disk = cfg.open(diskpath)?; // Print GPT layout. println!("Disk (primary) header: {:#?}", disk.primary_header()); println!("Partition layout: {:#?}", disk.partitions()); Ok(()) } ``` gpt-3.1.0/examples/inspect.rs000064400000000000000000000015361046102023000142550ustar 00000000000000use gpt; use simplelog::{Config, LevelFilter, SimpleLogger}; use std::io; fn main() { // Setup logging let _ = SimpleLogger::init(LevelFilter::Warn, Config::default()); // Inspect disk image, handling errors. if let Err(e) = run() { eprintln!("Failed to inspect image: {}", e); std::process::exit(1) } } fn run() -> io::Result<()> { // First parameter is target disk image (optional, default: fixtures sample) let sample = "tests/fixtures/gpt-linux-disk-01.img".to_string(); let input = std::env::args().nth(1).unwrap_or(sample); // Open disk image. let cfg = gpt::GptConfig::new().writable(false); let disk = cfg.open(input)?; // Print GPT layout. println!("Disk (primary) header: {:#?}", disk.primary_header()); println!("Partition layout: {:#?}", disk.partitions()); Ok(()) } gpt-3.1.0/src/disk.rs000064400000000000000000000035321046102023000125110ustar 00000000000000//! Disk-related types and helper functions. use super::{GptConfig, GptDisk}; use std::{convert::TryFrom, fmt, io, path}; /// Default size of a logical sector (bytes). pub const DEFAULT_SECTOR_SIZE: LogicalBlockSize = LogicalBlockSize::Lb512; /// Logical block/sector size of a GPT disk. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum LogicalBlockSize { /// 512 bytes. Lb512, /// 4096 bytes. Lb4096, } impl From for u64 { fn from(lb: LogicalBlockSize) -> u64 { match lb { LogicalBlockSize::Lb512 => 512, LogicalBlockSize::Lb4096 => 4096, } } } impl From for usize { fn from(lb: LogicalBlockSize) -> usize { match lb { LogicalBlockSize::Lb512 => 512, LogicalBlockSize::Lb4096 => 4096, } } } impl TryFrom for LogicalBlockSize { type Error = io::Error; fn try_from(v: u64) -> Result { match v { 512 => Ok(LogicalBlockSize::Lb512), 4096 => Ok(LogicalBlockSize::Lb4096), _ => Err(io::Error::new( io::ErrorKind::Other, "unsupported logical block size (must be 512 or 4096)" )), } } } impl fmt::Display for LogicalBlockSize { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LogicalBlockSize::Lb512 => write!(f, "512"), LogicalBlockSize::Lb4096 => write!(f, "4096"), } } } /// Open and read a GPT disk, using default configuration options. /// /// ## Example /// /// ```rust,no_run /// let gpt_disk = gpt::disk::read_disk("/dev/sdz").unwrap(); /// println!("{:#?}", gpt_disk); /// ``` pub fn read_disk(diskpath: impl AsRef) -> io::Result> { let cfg = GptConfig::new(); cfg.open(diskpath) } gpt-3.1.0/src/header.rs000064400000000000000000000733641046102023000130210ustar 00000000000000//! GPT-header object and helper functions. use crc::Crc; use log::*; use std::collections::BTreeMap; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write}; use std::path::Path; use crate::disk; use crate::partition; /// Header describing a GPT disk. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Header { /// GPT header magic signature, hardcoded to "EFI PART". pub signature: String, // Offset 0. "EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h /// 00 00 01 00 pub revision: u32, // Offset 8 /// little endian pub header_size_le: u32, // Offset 12 /// CRC32 of the header with crc32 section zeroed pub crc32: u32, // Offset 16 /// must be 0 pub reserved: u32, // Offset 20 /// For main header, 1 pub current_lba: u64, // Offset 24 /// LBA for backup header pub backup_lba: u64, // Offset 32 /// First usable LBA for partitions (primary table last LBA + 1) pub first_usable: u64, // Offset 40 /// Last usable LBA (secondary partition table first LBA - 1) pub last_usable: u64, // Offset 48 /// UUID of the disk pub disk_guid: uuid::Uuid, // Offset 56 /// Starting LBA of partition entries pub part_start: u64, // Offset 72 /// Number of partition entries pub num_parts: u32, // Offset 80 /// Size of a partition entry, usually 128 pub part_size: u32, // Offset 84 /// CRC32 of the partition table pub crc32_parts: u32, // Offset 88 } impl Header { pub(crate) fn compute_new( primary: bool, pp: &BTreeMap, guid: uuid::Uuid, backup_offset: u64, original_header: &Option
, lb_size: disk::LogicalBlockSize, num_parts: Option, ) -> Result { let (cur, bak) = if primary { (1, backup_offset) } else { (backup_offset, 1) }; // really this number should actually usually be 128, as it is the // TOTAL number of entries in the partition table, NOT the number USED. // UEFI requires space for 128 minimum, but the number can be increased or reduced. // If we're creating the table from scratch, make sure the table contains enough // room to be UEFI compliant. let parts = match num_parts { Some(p) => {p} None => { match original_header { Some(header) => header.num_parts, None => (pp.iter().filter(|p| p.1.is_used()).count() as u32).max(128), } } }; //though usually 128, it might be a different number let part_size = match original_header { Some(header) => header.part_size, None => 128, }; let part_array_num_bytes = u64::from(parts * part_size); // If not an exact multiple of a sector, round up to the next # of whole sectors. let lb_size_u64 = Into::::into(lb_size); let part_array_num_lbs = (part_array_num_bytes + (lb_size_u64 - 1)) / lb_size_u64; // sometimes the first usable isn't sector 34, fdisk starts at 2048 // alternatively, if the sector size is 4096 it might not be 34 either. // to align partition boundaries (https://metebalci.com/blog/a-quick-tour-of-guid-partition-table-gpt/) let first = match num_parts { Some(_) => 1 + 1 + part_array_num_lbs, None => { match original_header { Some(header) => header.first_usable, None => 1 + 1 + part_array_num_lbs, //protective MBR + GPT header + partition array } } }; let last = match num_parts { Some(_) => { // last is inclusive: end of disk is (partition array) (backup header) backup_offset .checked_sub(part_array_num_lbs + 1) .ok_or_else(|| Error::new(ErrorKind::Other, "header underflow - last usable"))? }, None => { match original_header { Some(header) => header.last_usable, None => { // last is inclusive: end of disk is (partition array) (backup header) backup_offset .checked_sub(part_array_num_lbs + 1) .ok_or_else(|| Error::new(ErrorKind::Other, "header underflow - last usable"))? } } } }; // the partition entry LBA starts at 2 (usually) for primary headers and at the last_usable + 1 for backup headers let part_start = if primary { 2 } else { last + 1 }; let hdr = Header { signature: "EFI PART".to_string(), revision: 65536, header_size_le: 92, crc32: 0, reserved: 0, current_lba: cur, backup_lba: bak, first_usable: first, last_usable: last, disk_guid: guid, part_start, num_parts: parts, part_size, crc32_parts: 0, }; Ok(hdr) } /// Write the primary header. /// /// With a CRC32 set to zero this will set the crc32 after /// writing the header out. pub fn write_primary( &self, file: &mut D, lb_size: disk::LogicalBlockSize, ) -> Result { // This is the primary header. It must start before the backup one. if self.current_lba >= self.backup_lba { debug!( "current lba: {} backup_lba: {}", self.current_lba, self.backup_lba ); return Err(Error::new( ErrorKind::Other, "primary header does not start before backup one", )); } self.file_write_header(file, self.current_lba, lb_size) } /// Write the backup header. /// /// With a CRC32 set to zero this will set the crc32 after /// writing the header out. pub fn write_backup( &self, file: &mut D, lb_size: disk::LogicalBlockSize, ) -> Result { // This is the backup header. It must start after the primary one. if self.current_lba <= self.backup_lba { debug!( "current lba: {} backup_lba: {}", self.current_lba, self.backup_lba ); return Err(Error::new( ErrorKind::Other, "backup header does not start after primary one", )); } self.file_write_header(file, self.current_lba, lb_size) } /// Write an header to an arbitrary LBA. /// /// With a CRC32 set to zero this will set the crc32 after /// writing the header out. fn file_write_header( &self, file: &mut D, lba: u64, lb_size: disk::LogicalBlockSize, ) -> Result { // Build up byte array in memory let parts_checksum = partentry_checksum(file, self, lb_size)?; trace!("computed partitions CRC32: {:#x}", parts_checksum); let bytes = self.as_bytes(None, Some(parts_checksum))?; trace!("bytes before checksum: {:?}", bytes); // Calculate the CRC32 from the byte array let checksum = calculate_crc32(&bytes); trace!("computed header CRC32: {:#x}", checksum); // Write it to disk in 1 shot let start = lba .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "header overflow - offset"))?; trace!("Seeking to {}", start); let _ = file.seek(SeekFrom::Start(start))?; let mut header_bytes = self.as_bytes(Some(checksum), Some(parts_checksum))?; // Per the spec, the rest of the logical block must be zeros... header_bytes.resize(Into::::into(lb_size), 0x00); let len = file.write(&header_bytes)?; trace!("Wrote {} bytes", len); Ok(len) } fn as_bytes( &self, header_checksum: Option, partitions_checksum: Option, ) -> Result> { let mut buff: Vec = Vec::new(); let disk_guid_fields = self.disk_guid.as_fields(); buff.write_all(self.signature.as_bytes())?; buff.write_all(&self.revision.to_le_bytes())?; buff.write_all(&self.header_size_le.to_le_bytes())?; match header_checksum { Some(c) => buff.write_all(&c.to_le_bytes())?, None => buff.write_all(&[0_u8; 4])?, }; buff.write_all(&[0; 4])?; buff.write_all(&self.current_lba.to_le_bytes())?; buff.write_all(&self.backup_lba.to_le_bytes())?; buff.write_all(&self.first_usable.to_le_bytes())?; buff.write_all(&self.last_usable.to_le_bytes())?; buff.write_all(&disk_guid_fields.0.to_le_bytes())?; buff.write_all(&disk_guid_fields.1.to_le_bytes())?; buff.write_all(&disk_guid_fields.2.to_le_bytes())?; buff.write_all(disk_guid_fields.3)?; buff.write_all(&self.part_start.to_le_bytes())?; buff.write_all(&self.num_parts.to_le_bytes())?; buff.write_all(&self.part_size.to_le_bytes())?; match partitions_checksum { Some(c) => buff.write_all(&c.to_le_bytes())?, None => buff.write_all(&[0_u8; 4])?, }; Ok(buff) } } /// Parses a uuid with first 3 portions in little endian. pub fn parse_uuid(rdr: &mut Cursor<&[u8]>) -> Result { let d1 = u32::from_le_bytes(read_exact_buff!(d1b, rdr, 4)); let d2 = u16::from_le_bytes(read_exact_buff!(d2b, rdr, 2)); let d3 = u16::from_le_bytes(read_exact_buff!(d3b, rdr, 2)); let d4 = read_exact_buff!(d4b, rdr, 8); let uuid = uuid::Uuid::from_fields( d1, d2, d3, &d4, ); Ok(uuid) } impl fmt::Display for Header { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Disk:\t\t{}\nCRC32:\t\t{}\nTable CRC:\t{}", self.disk_guid, self.crc32, self.crc32_parts ) } } /// Read a GPT header from a given path. /// /// ## Example /// /// ```rust,no_run /// use gpt::header::read_header; /// /// let lb_size = gpt::disk::DEFAULT_SECTOR_SIZE; /// let diskpath = std::path::Path::new("/dev/sdz"); /// /// let h = read_header(diskpath, lb_size).unwrap(); /// ``` pub fn read_header( path: impl AsRef, sector_size: disk::LogicalBlockSize ) -> Result
{ let mut file = File::open(path)?; read_primary_header(&mut file, sector_size) } /// Read a GPT header from any device capable of reading and seeking. pub fn read_header_from_arbitrary_device( device: &mut D, sector_size: disk::LogicalBlockSize, ) -> Result
{ read_primary_header(device, sector_size) } pub(crate) fn read_primary_header( file: &mut D, sector_size: disk::LogicalBlockSize, ) -> Result
{ let cur = file.seek(SeekFrom::Current(0)).unwrap_or(0); let offset: u64 = sector_size.into(); let res = file_read_header(file, offset); let _ = file.seek(SeekFrom::Start(cur)); res } pub(crate) fn read_backup_header( file: &mut D, sector_size: disk::LogicalBlockSize, ) -> Result
{ let cur = file.seek(SeekFrom::Current(0)).unwrap_or(0); let h2sect = find_backup_lba(file, sector_size)?; let offset = h2sect .checked_mul(sector_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "backup header overflow - offset"))?; let res = file_read_header(file, offset); let _ = file.seek(SeekFrom::Start(cur)); res } pub(crate) fn file_read_header(file: &mut D, offset: u64) -> Result
{ let _ = file.seek(SeekFrom::Start(offset)); let mut hdr: [u8; 92] = [0; 92]; let _ = file.read_exact(&mut hdr); let mut reader = Cursor::new(&hdr[..]); let sigstr = String::from_utf8_lossy( &reader.get_ref()[reader.position() as usize..reader.position() as usize + 8], ); reader.seek(SeekFrom::Current(8))?; if sigstr != "EFI PART" { return Err(Error::new(ErrorKind::Other, "invalid GPT signature")); }; let h = Header { signature: sigstr.to_string(), revision: u32::from_le_bytes(read_exact_buff!(rev, reader, 4)), header_size_le: u32::from_le_bytes(read_exact_buff!(hsle, reader, 4)), crc32: u32::from_le_bytes(read_exact_buff!(crc32, reader, 4)), reserved: u32::from_le_bytes(read_exact_buff!(reserv, reader, 4)), current_lba: u64::from_le_bytes(read_exact_buff!(clba, reader, 8)), backup_lba: u64::from_le_bytes(read_exact_buff!(blba, reader, 8)), first_usable: u64::from_le_bytes(read_exact_buff!(fusable, reader, 8)), last_usable: u64::from_le_bytes(read_exact_buff!(lusable, reader, 8)), disk_guid: parse_uuid(&mut reader)?, part_start: u64::from_le_bytes(read_exact_buff!(pstart, reader, 8)), // Note: this will always return the total number of partition entries // in the array, not how many are actually used num_parts: u32::from_le_bytes(read_exact_buff!(nparts, reader, 4)), part_size: u32::from_le_bytes(read_exact_buff!(partsize, reader, 4)), crc32_parts: u32::from_le_bytes(read_exact_buff!(crc32parts, reader, 4)), }; trace!("header: {:?}", &hdr[..]); trace!("header gpt: {}", h.disk_guid.as_hyphenated().to_string()); let mut hdr_crc = hdr; for crc_byte in hdr_crc.iter_mut().skip(16).take(4) { *crc_byte = 0; } let c = calculate_crc32(&hdr_crc); trace!("header CRC32: {:#x} - computed CRC32: {:#x}", h.crc32, c); if c == h.crc32 { Ok(h) } else { Err(Error::new(ErrorKind::Other, "invalid CRC32 checksum")) } } pub(crate) fn find_backup_lba( f: &mut D, sector_size: disk::LogicalBlockSize, ) -> Result { trace!("querying file size to find backup header location"); let lb_size: u64 = sector_size.into(); let old_pos = f.seek(std::io::SeekFrom::Current(0))?; let len = f.seek(std::io::SeekFrom::End(0))?; f.seek(std::io::SeekFrom::Start(old_pos))?; if len <= lb_size { return Err(Error::new( ErrorKind::Other, "disk image too small for backup header", )); } let bak_offset = len.saturating_sub(lb_size); let bak_lba = bak_offset / lb_size; trace!( "backup header: LBA={}, bytes offset={}", bak_lba, bak_offset ); Ok(bak_lba) } const CRC_32: Crc = Crc::::new(&crc::CRC_32_ISO_HDLC); fn calculate_crc32(b: &[u8]) -> u32 { let mut digest = CRC_32.digest(); trace!("Writing buffer to digest calculator"); digest.update(b); digest.finalize() } pub(crate) fn partentry_checksum( file: &mut D, hdr: &Header, lb_size: disk::LogicalBlockSize, ) -> Result { // Seek to start of partition table. trace!("Computing partition checksum"); let start = hdr .part_start .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "header overflow - partition table start"))?; trace!("Seek to {}", start); let _ = file.seek(SeekFrom::Start(start))?; // Read partition table. let pt_len = u64::from(hdr.num_parts) .checked_mul(hdr.part_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition table - size"))?; trace!("Reading {} bytes", pt_len); let mut buf = vec![0; pt_len as usize]; file.read_exact(&mut buf)?; //trace!("Buffer before checksum: {:?}", buf); // Compute CRC32 over all table bits. Ok(calculate_crc32(&buf)) } /// A helper function to create a new header and write it to disk. /// If the uuid isn't given a random one will be generated. Use /// this in conjunction with Partition::write() // TODO: Move this to Header::new() and Header::write to write it // that will match the Partition::write() API pub fn write_header( p: impl AsRef, uuid: Option, sector_size: disk::LogicalBlockSize, ) -> Result { debug!("opening {} for writing", p.as_ref().display()); let mut file = OpenOptions::new().write(true).read(true).open(p)?; let bak = find_backup_lba(&mut file, sector_size)?; let guid = match uuid { Some(u) => u, None => { let u = uuid::Uuid::new_v4(); debug!("Generated random uuid: {}", u); u } }; let hdr = Header::compute_new(true, &BTreeMap::new(), guid, bak, &None, sector_size, None)?; debug!("new header: {:#?}", hdr); hdr.write_primary(&mut file, sector_size)?; Ok(guid) } #[test] // test compute new with fdisk'd image, without giving original header fn test_compute_new_fdisk_no_header() { use tempfile; let lb_size = disk::DEFAULT_SECTOR_SIZE; let diskpath = Path::new("tests/fixtures/test.img"); let h = read_header(diskpath, lb_size).unwrap(); let cfg = crate::GptConfig::new().writable(false).initialized(true); let disk = cfg.open(diskpath).unwrap(); println!("original Disk {:#?}", disk); let partitions: BTreeMap = BTreeMap::new(); let mut file = std::fs::OpenOptions::new() .write(false) .read(true) .open(diskpath) .unwrap(); let bak = find_backup_lba(&mut file, *disk.logical_block_size()).unwrap(); println!("Back offset {}", bak); let mut tempdisk = tempfile::tempfile().expect("failed to create tempfile disk"); { let data: [u8; 4096] = [0; 4096]; println!("Creating blank header file for testing"); // This should be large enough to contain the backup partition array, // or computing the checksum when writing the backup header will fail. let min_file_size = (bak * Into::::into(lb_size)) + Into::::into(lb_size); for _ in 0..((min_file_size + 4095) / 4096) { tempdisk.write_all(&data).unwrap(); } }; let new_primary = Header::compute_new(true, &partitions, uuid::Uuid::new_v4(), bak, &None, lb_size, None).unwrap(); println!("new primary header {:#?}", new_primary); let new_backup = Header::compute_new(false, &partitions, uuid::Uuid::new_v4(), bak, &None, lb_size, None).unwrap(); println!("new backup header {:#?}", new_backup); new_primary .write_primary(&mut tempdisk, lb_size) .unwrap(); new_backup .write_backup(&mut tempdisk, lb_size) .unwrap(); let mbr = crate::mbr::ProtectiveMBR::new(); mbr.overwrite_lba0(&mut tempdisk).unwrap(); assert_eq!(h.signature, new_primary.signature); assert_eq!(h.revision, new_primary.revision); assert_eq!(h.header_size_le, new_primary.header_size_le); assert_eq!(h.reserved, new_primary.reserved); assert_eq!(h.current_lba, new_primary.current_lba); assert_eq!(h.backup_lba, new_primary.backup_lba); assert_eq!(34, new_primary.first_usable); // since we did not include the original header, the first usable defaults to 34 assert_eq!(h.last_usable, new_primary.last_usable); assert_ne!(h.disk_guid, new_primary.disk_guid); //writing new disk => new guid assert_eq!(2, new_primary.part_start); // when creating a new table from scratch, we should always have a minimum of 128 entries to be UEFI compliant assert_eq!(128, new_primary.num_parts); assert_eq!(128, new_primary.part_size); //standard size (it is possibly different, but usually 128) let bh = read_backup_header(&mut file, *disk.logical_block_size()).unwrap(); //backup header tests //current_lba and backup_lba should be flipped assert_eq!(h.backup_lba, new_backup.current_lba); assert_eq!(h.current_lba, new_backup.backup_lba); // also, the backup header should match assert_eq!(bh.current_lba, new_backup.current_lba); assert_eq!(bh.backup_lba, new_backup.backup_lba); assert_eq!(bh.part_start, new_backup.part_start); } #[test] // test compute new with fdisk'd image, giving original header // Note: if you would like to save to a file to check the headers // manually, use OpenOptions with write/create/truncate/read. without the // read the checksum will not be able to read the tempdisk fn test_compute_new_fdisk_pass_header() { let diskpath = Path::new("tests/fixtures/test.img"); let h = read_header(diskpath, disk::DEFAULT_SECTOR_SIZE).unwrap(); let cfg = crate::GptConfig::new().writable(false).initialized(true); let disk = cfg.open(diskpath).unwrap(); println!("original Disk {:#?}", disk); let partitions: BTreeMap = BTreeMap::new(); let mut file = std::fs::OpenOptions::new() .write(false) .read(true) .open(diskpath) .unwrap(); let bak = find_backup_lba(&mut file, *disk.logical_block_size()).unwrap(); println!("Back offset {}", bak); let mut tempdisk = tempfile::tempfile().expect("failed to create tempfile disk"); { let data: [u8; 4096] = [0; 4096]; println!("Creating copy of test header file for testing"); for _ in 0..2560 { tempdisk.write_all(&data).unwrap(); } }; let bh = read_backup_header(&mut file, *disk.logical_block_size()).unwrap(); let mbr = crate::mbr::ProtectiveMBR::new(); mbr.overwrite_lba0(&mut tempdisk).unwrap(); let new_primary = Header::compute_new( true, &partitions, uuid::Uuid::new_v4(), bak, &Some(h.clone()), disk::DEFAULT_SECTOR_SIZE, None, ) .unwrap(); println!("new primary header {:#?}", new_primary); let new_backup = Header::compute_new( false, &partitions, uuid::Uuid::new_v4(), bak, &Some(h.clone()), disk::DEFAULT_SECTOR_SIZE, None, ) .unwrap(); println!("new backup header {:#?}", new_backup); new_primary .write_primary(&mut tempdisk, disk::DEFAULT_SECTOR_SIZE) .unwrap(); new_backup .write_backup(&mut tempdisk, disk::DEFAULT_SECTOR_SIZE) .unwrap(); assert_eq!(h.signature, new_primary.signature); assert_eq!(h.revision, new_primary.revision); assert_eq!(h.header_size_le, new_primary.header_size_le); assert_eq!(h.reserved, new_primary.reserved); assert_eq!(h.current_lba, new_primary.current_lba); assert_eq!(h.backup_lba, new_primary.backup_lba); assert_eq!(h.first_usable, new_primary.first_usable); // since we did not include the original header, the first usable defaults to 34 assert_eq!(h.last_usable, new_primary.last_usable); assert_ne!(h.disk_guid, new_primary.disk_guid); //writing new disk => new guid assert_eq!(2, new_primary.part_start); //if we do a write disk this wouldn't actually be able to write a new partition with fdisk unless you created a new partition table on it assert_eq!(h.num_parts, new_primary.num_parts); assert_eq!(h.part_size, new_primary.part_size); //standard size (it is possibly different, but usually 128) //backup header tests //current_lba and backup_lba should be flipped assert_eq!(h.backup_lba, new_backup.current_lba); assert_eq!(h.current_lba, new_backup.backup_lba); // also, the backup header should match assert_eq!(bh.current_lba, new_backup.current_lba); assert_eq!(bh.backup_lba, new_backup.backup_lba); assert_eq!(bh.part_start, new_backup.part_start); } #[test] // test compute new with fdisk'd image, without giving original header fn test_compute_new_gpt_no_header() { use tempfile; let lb_size = disk::DEFAULT_SECTOR_SIZE; let diskpath = Path::new("tests/fixtures/gpt-linux-disk-01.img"); let h = read_header(diskpath, lb_size).unwrap(); let cfg = crate::GptConfig::new().writable(false).initialized(true); let disk = cfg.open(diskpath).unwrap(); println!("original Disk {:#?}", disk); let partitions: BTreeMap = BTreeMap::new(); let mut file = std::fs::OpenOptions::new() .write(false) .read(true) .open(diskpath) .unwrap(); let bak = find_backup_lba(&mut file, *disk.logical_block_size()).unwrap(); println!("Back offset {}", bak); let mut tempdisk = tempfile::tempfile().expect("failed to create tempfile disk"); { let data: [u8; 4096] = [0; 4096]; println!("Creating blank header file for testing"); for _ in 0..100 { tempdisk.write_all(&data).unwrap(); } }; let new_primary = Header::compute_new(true, &partitions, uuid::Uuid::new_v4(), bak, &None, lb_size, None).unwrap(); println!("new primary header {:#?}", new_primary); let new_backup = Header::compute_new(false, &partitions, uuid::Uuid::new_v4(), bak, &None, lb_size, None).unwrap(); println!("new backup header {:#?}", new_backup); new_primary .write_primary(&mut tempdisk, lb_size) .unwrap(); new_backup .write_backup(&mut tempdisk, lb_size) .unwrap(); let mbr = crate::mbr::ProtectiveMBR::new(); mbr.overwrite_lba0(&mut tempdisk).unwrap(); assert_eq!(h.signature, new_primary.signature); assert_eq!(h.revision, new_primary.revision); assert_eq!(h.header_size_le, new_primary.header_size_le); assert_eq!(h.reserved, new_primary.reserved); assert_eq!(h.current_lba, new_primary.current_lba); assert_eq!(h.backup_lba, new_primary.backup_lba); assert_eq!(34, new_primary.first_usable); // since we did not include the original header, the first usable defaults to 34 assert_eq!(h.last_usable, new_primary.last_usable); assert_ne!(h.disk_guid, new_primary.disk_guid); //writing new disk => new guid assert_eq!(2, new_primary.part_start); // when creating a new table from scratch, we should always have a minimum of 128 entries to be UEFI compliant assert_eq!(128, new_primary.num_parts); assert_eq!(128, new_primary.part_size); //standard size (it is possibly different, but usually 128) let bh = read_backup_header(&mut file, *disk.logical_block_size()).unwrap(); //backup header tests //current_lba and backup_lba should be flipped assert_eq!(h.backup_lba, new_backup.current_lba); assert_eq!(h.current_lba, new_backup.backup_lba); // also, the backup header should match assert_eq!(bh.current_lba, new_backup.current_lba); assert_eq!(bh.backup_lba, new_backup.backup_lba); assert_eq!(bh.part_start, new_backup.part_start); } #[test] // test compute new with fdisk'd image, giving original header // Note: if you would like to save to a file to check the headers // manually, use OpenOptions with write/create/truncate/read. without the // read the checksum will not be able to read the tempdisk fn test_compute_new_fdisk_gpt_header() { let diskpath = Path::new("tests/fixtures/gpt-linux-disk-01.img"); let h = read_header(diskpath, disk::DEFAULT_SECTOR_SIZE).unwrap(); let cfg = crate::GptConfig::new().writable(false).initialized(true); let disk = cfg.open(diskpath).unwrap(); println!("original Disk {:#?}", disk); let partitions: BTreeMap = BTreeMap::new(); let mut file = std::fs::OpenOptions::new() .write(false) .read(true) .open(diskpath) .unwrap(); let bak = find_backup_lba(&mut file, *disk.logical_block_size()).unwrap(); println!("Back offset {}", bak); let mut tempdisk = tempfile::tempfile().expect("failed to create tempfile disk"); { let data: [u8; 4096] = [0; 4096]; println!("Creating copy of test header file for testing"); for _ in 0..2560 { tempdisk.write_all(&data).unwrap(); } }; let bh = read_backup_header(&mut file, *disk.logical_block_size()).unwrap(); let mbr = crate::mbr::ProtectiveMBR::new(); mbr.overwrite_lba0(&mut tempdisk).unwrap(); let new_primary = Header::compute_new( true, &partitions, uuid::Uuid::new_v4(), bak, &Some(h.clone()), disk::DEFAULT_SECTOR_SIZE, None, ) .unwrap(); println!("new primary header {:#?}", new_primary); let new_backup = Header::compute_new( false, &partitions, uuid::Uuid::new_v4(), bak, &Some(h.clone()), disk::DEFAULT_SECTOR_SIZE, None, ) .unwrap(); println!("new backup header {:#?}", new_backup); new_primary .write_primary(&mut tempdisk, disk::DEFAULT_SECTOR_SIZE) .unwrap(); new_backup .write_backup(&mut tempdisk, disk::DEFAULT_SECTOR_SIZE) .unwrap(); assert_eq!(h.signature, new_primary.signature); assert_eq!(h.revision, new_primary.revision); assert_eq!(h.header_size_le, new_primary.header_size_le); assert_eq!(h.reserved, new_primary.reserved); assert_eq!(h.current_lba, new_primary.current_lba); assert_eq!(h.backup_lba, new_primary.backup_lba); assert_eq!(h.first_usable, new_primary.first_usable); // since we did not include the original header, the first usable defaults to 34 assert_eq!(h.last_usable, new_primary.last_usable); assert_ne!(h.disk_guid, new_primary.disk_guid); //writing new disk => new guid assert_eq!(2, new_primary.part_start); //if we do a write disk this wouldn't actually be able to write a new partition with fdisk unless you created a new partition table on it assert_eq!(h.num_parts, new_primary.num_parts); assert_eq!(h.part_size, new_primary.part_size); //standard size (it is possibly different, but usually 128) //backup header tests //current_lba and backup_lba should be flipped assert_eq!(h.backup_lba, new_backup.current_lba); assert_eq!(h.current_lba, new_backup.backup_lba); // also, the backup header should match assert_eq!(bh.current_lba, new_backup.current_lba); assert_eq!(bh.backup_lba, new_backup.backup_lba); assert_eq!(bh.part_start, new_backup.part_start); } gpt-3.1.0/src/lib.rs000064400000000000000000000555721046102023000123400ustar 00000000000000//! A pure-Rust library to work with GPT partition tables. //! //! It provides support for manipulating (R/W) GPT headers and partition //! tables. Raw disk devices as well as disk images are supported. //! //! ``` //! extern crate gpt; //! use std::convert::TryFrom; //! //! fn inspect_disk() { //! let diskpath = std::path::Path::new("/dev/sdz"); //! let cfg = gpt::GptConfig::new().writable(false); //! //! let disk = cfg.open(diskpath).expect("failed to open disk"); //! //! println!("Disk header: {:#?}", disk.primary_header()); //! println!("Partition layout: {:#?}", disk.partitions()); //! } //! //! fn create_partition() { //! let diskpath = std::path::Path::new("/tmp/chris.img"); //! let cfg = gpt::GptConfig::new().writable(true).initialized(true); //! let mut disk = cfg.open(diskpath).expect("failed to open disk"); //! let result = disk.add_partition( //! "rust_partition", //! 100, //! gpt::partition_types::LINUX_FS, //! 0, //! None //! ); //! disk.write().unwrap(); //! } //! //! /// Demonstrates how to create a new partition table without anything pre-existing //! fn create_partition_in_ram() { //! const TOTAL_BYTES: usize = 1024 * 64; //! let mut mem_device = Box::new(std::io::Cursor::new(vec![0u8; TOTAL_BYTES])); //! //! // Create a protective MBR at LBA0 //! let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( //! u32::try_from((TOTAL_BYTES / 512) - 1).unwrap_or(0xFF_FF_FF_FF)); //! mbr.overwrite_lba0(&mut mem_device).expect("failed to write MBR"); //! //! let mut gdisk = gpt::GptConfig::default() //! .initialized(false) //! .writable(true) //! .logical_block_size(gpt::disk::LogicalBlockSize::Lb512) //! .create_from_device(mem_device, None) //! .expect("failed to crate GptDisk"); //! //! // Initialize the headers using a blank partition table //! gdisk.update_partitions( //! std::collections::BTreeMap::::new() //! ).expect("failed to initialize blank partition table"); //! //! // At this point, gdisk.primary_header() and gdisk.backup_header() are populated... //! // Add a few partitions to demonstrate how... //! gdisk.add_partition("test1", 1024 * 12, gpt::partition_types::BASIC, 0, None) //! .expect("failed to add test1 partition"); //! gdisk.add_partition("test2", 1024 * 18, gpt::partition_types::LINUX_FS, 0, None) //! .expect("failed to add test2 partition"); //! // Write the partition table and take ownership of //! // the underlying memory buffer-backed block device //! let mut mem_device = gdisk.write().expect("failed to write partition table"); //! // Read the written bytes out of the memory buffer device //! mem_device.seek(std::io::SeekFrom::Start(0)).expect("failed to seek"); //! let mut final_bytes = vec![0u8; TOTAL_BYTES]; //! mem_device.read_exact(&mut final_bytes) //! .expect("failed to read contents of memory device"); //! } //! //! // only manipulates memory buffers, so this can run on any system... //! create_partition_in_ram(); //! ``` #![deny(missing_docs)] use log::*; use std::collections::BTreeMap; use std::io::{Read, Seek, Write}; use std::{fs, io, path}; #[macro_use] mod macros; pub mod disk; pub mod header; pub mod mbr; pub mod partition; pub mod partition_types; /// A generic device that we can read/write partitions from/to. pub trait DiskDevice: Read + Write + Seek + std::fmt::Debug {} /// Implement the DiskDevice trait for anything that meets the /// requirements, e.g., `std::fs::File` impl DiskDevice for T where T: Read + Write + Seek + std::fmt::Debug {} /// A dynamic trait object that is used by GptDisk for reading/writing/seeking. pub type DiskDeviceObject<'a> = Box; /// Configuration options to open a GPT disk. #[derive(Debug, Eq, PartialEq)] pub struct GptConfig { /// Logical block size. lb_size: disk::LogicalBlockSize, /// Whether to open a GPT partition table in writable mode. writable: bool, /// Whether to expect and parse an initialized disk image. initialized: bool, } impl GptConfig { // TODO(lucab): complete support for skipping backup // header, etc, then expose all config knobs here. /// Create a new default configuration. pub fn new() -> Self { GptConfig::default() } /// Whether to open a GPT partition table in writable mode. pub fn writable(mut self, writable: bool) -> Self { self.writable = writable; self } /// Whether to assume an initialized GPT disk and read its /// partition table on open. pub fn initialized(mut self, initialized: bool) -> Self { self.initialized = initialized; self } /// Size of logical blocks (sectors) for this disk. pub fn logical_block_size(mut self, lb_size: disk::LogicalBlockSize) -> Self { self.lb_size = lb_size; self } /// Open the GPT disk at the given path and inspect it according /// to configuration options. pub fn open(self, diskpath: impl AsRef) -> io::Result> { let file = Box::new(fs::OpenOptions::new() .write(self.writable) .read(true) .open(diskpath)?); self.open_from_device(file as DiskDeviceObject) } /// Open the GPT disk from the given DiskDeviceObject and /// inspect it according to configuration options. pub fn open_from_device(self, mut device: DiskDeviceObject) -> io::Result { // Uninitialized disk, no headers/table to parse. if !self.initialized { return self.create_from_device(device, Some(uuid::Uuid::new_v4())); } // Proper GPT disk, fully inspect its layout. let h1 = header::read_primary_header(&mut device, self.lb_size)?; let h2 = header::read_backup_header(&mut device, self.lb_size)?; let table = partition::file_read_partitions(&mut device, &h1, self.lb_size)?; let disk = GptDisk { config: self, device, guid: h1.disk_guid, primary_header: Some(h1), backup_header: Some(h2), partitions: table, }; debug!("disk: {:?}", disk); Ok(disk) } /// Create a GPTDisk with default headers and an empty partition table. /// If guid is None then it will generate a new random guid. pub fn create_from_device( self, device: DiskDeviceObject, guid: Option ) -> io::Result { if self.initialized { Err(io::Error::new( io::ErrorKind::Other, "we were expecting to read an existing partition table, but \ instead we're attempting to create a new blank table", )) } else { let empty = GptDisk { config: self, device, guid: guid.unwrap_or_else(uuid::Uuid::new_v4), primary_header: None, backup_header: None, partitions: BTreeMap::new(), }; Ok(empty) } } } impl Default for GptConfig { fn default() -> Self { Self { lb_size: disk::DEFAULT_SECTOR_SIZE, initialized: true, writable: false, } } } /// A GPT disk backed by an arbitrary device. #[derive(Debug)] pub struct GptDisk<'a> { config: GptConfig, device: DiskDeviceObject<'a>, guid: uuid::Uuid, primary_header: Option, backup_header: Option, partitions: BTreeMap, } impl<'a> GptDisk<'a> { /// Add another partition to this disk. This tries to find /// the optimum partition location with the lowest block device. /// Returns the new partition id if there was sufficient room /// to add the partition. Size is specified in bytes. pub fn add_partition( &mut self, name: &str, size: u64, part_type: partition_types::Type, flags: u64, part_alignment: Option, ) -> io::Result { // Ceiling division which avoids overflow let size_lba = size.checked_sub(1) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "size must be greater than zero bytes"))? .checked_div(self.config.lb_size.into()) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid logical block size caused bad division when calculating size in blocks"))? .checked_add(1) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "size too large. must be within u64::MAX - 1 bounds"))?; // Find the lowest lba that is larger than size. let free_sections = self.find_free_sectors(); for (starting_lba, length) in free_sections { // Get the distance between the starting LBA of this section and the next aligned LBA // We don't need to do any checked math here because we guarantee that with `(A % B)`, // `A` will always be between 0 and `B-1`. let alignment_offset_lba = match part_alignment { Some(alignment) => (alignment - (starting_lba % alignment)) % alignment, None => 0_u64, }; debug!("starting_lba {}, length {}, alignment_offset_lba {}", starting_lba, length, alignment_offset_lba); if length >= (alignment_offset_lba + size_lba - 1) { let starting_lba= starting_lba + alignment_offset_lba; // Found our free slice. let partition_id = self.find_next_partition_id(); debug!( "Adding partition id: {} {:?}. first_lba: {} last_lba: {}", partition_id, part_type, starting_lba, starting_lba + size_lba - 1_u64 ); let part = partition::Partition { part_type_guid: part_type, part_guid: uuid::Uuid::new_v4(), first_lba: starting_lba, last_lba: starting_lba + size_lba - 1_u64, flags, name: name.to_string(), }; if let Some(p) = self.partitions.insert(partition_id, part.clone()) { debug!("Replacing\n{}\nwith\n{}", p, part); } return Ok(partition_id); } } Err(io::Error::new( io::ErrorKind::Other, "Unable to find enough space on drive", )) } /// remove partition from this disk. This tries to find the partition based on either a /// given partition number (id) or a partition guid. Returns the partition id if the /// partition is removed pub fn remove_partition( &mut self, id: Option, partguid: Option, ) -> io::Result { if let Some(part_id) = id { if let Some(partition_id) = self.partitions.remove(&part_id) { debug!("Removing partition number {}", partition_id); } return Ok(part_id); } if let Some(part_guid) = partguid { for (key, partition) in &self.partitions.clone() { if partition.part_guid == part_guid { if let Some(partition_id) = self.partitions.remove(key) { debug!("Removing partition number {}", partition_id); } return Ok(*key); } } } Err(io::Error::new( io::ErrorKind::Other, "Unable to find partition to remove", )) } /// Find free space on the disk. /// Returns a tuple of (starting_lba, length in lba's). pub fn find_free_sectors(&self) -> Vec<(u64, u64)> { if let Some(header) = self.primary_header().or_else(|| self.backup_header()) { trace!("first_usable: {}", header.first_usable); let mut disk_positions = vec![header.first_usable]; for part in self.partitions().iter().filter(|p| p.1.is_used()) { trace!("partition: ({}, {})", part.1.first_lba, part.1.last_lba); disk_positions.push(part.1.first_lba); disk_positions.push(part.1.last_lba); } disk_positions.push(header.last_usable); trace!("last_usable: {}", header.last_usable); disk_positions.sort_unstable(); return disk_positions // Walk through the LBA's in chunks of 2 (ending, starting). .chunks(2) // Add 1 to the ending and then subtract the starting if NOT the first usable sector .map(|p| { if p[0] == header.first_usable { (p[0], p[1].saturating_sub(p[0])) } else { (p[0] + 1, p[1].saturating_sub(p[0] + 1)) } }) .collect(); } // No primary header. Return nothing. vec![] } /// Find next highest partition id. pub fn find_next_partition_id(&self) -> u32 { let max = match self .partitions() .iter() // Skip unused partitions. .filter(|p| p.1.is_used()) // Find the maximum id. .max_by_key(|x| x.0) { Some(i) => *i.0, // Partitions start at 1. None => return 1, }; for i in 1..max { if self.partitions().get(&i).is_none() { return i; } } max + 1 } /// Retrieve primary header, if any. pub fn primary_header(&self) -> Option<&header::Header> { self.primary_header.as_ref() } /// Retrieve backup header, if any. pub fn backup_header(&self) -> Option<&header::Header> { self.backup_header.as_ref() } /// Retrieve partition entries. pub fn partitions(&self) -> &BTreeMap { &self.partitions } /// Retrieve disk UUID. pub fn guid(&self) -> &uuid::Uuid { &self.guid } /// Retrieve disk logical block size. pub fn logical_block_size(&self) -> &disk::LogicalBlockSize { &self.config.lb_size } /// Change the disk device that we are reading/writing from/to. /// Returns the previous disk device. pub fn update_disk_device( &mut self, device: DiskDeviceObject<'a>, writable: bool ) -> DiskDeviceObject { self.config.writable = writable; std::mem::replace(&mut self.device, device) } /// Update disk UUID. /// /// If no UUID is specified, a new random one is generated. /// No changes are recorded to disk until `write()` is called. pub fn update_guid(&mut self, uuid: Option) -> io::Result<&Self> { let guid = match uuid { Some(u) => u, None => { let u = uuid::Uuid::new_v4(); debug!("Generated random uuid: {}", u); u } }; self.guid = guid; Ok(self) } /// Update current partition table. /// /// No changes are recorded to disk until `write()` is called. pub fn update_partitions( &mut self, pp: BTreeMap, ) -> io::Result<&Self> { // TODO(lucab): validate partitions. let bak = header::find_backup_lba(&mut self.device, self.config.lb_size)?; let h1 = header::Header::compute_new( true, &pp, self.guid, bak, &self.primary_header, self.config.lb_size, None)?; let h2 = header::Header::compute_new( false, &pp, self.guid, bak, &self.backup_header, self.config.lb_size, None)?; self.primary_header = Some(h1); self.backup_header = Some(h2); self.partitions = pp; self.config.initialized = true; Ok(self) } /// Update current partition table without touching backups /// /// No changes are recorded to disk until `write()` is called. pub fn update_partitions_safe( &mut self, pp: BTreeMap, ) -> io::Result<&Self> { // TODO(lucab): validate partitions. let bak = header::find_backup_lba(&mut self.device, self.config.lb_size)?; let h1 = header::Header::compute_new( true, &pp, self.guid, bak, &self.primary_header, self.config.lb_size, None)?; self.primary_header = Some(h1); self.partitions = pp; self.config.initialized = true; Ok(self) } /// Update current partition table. /// Allows for changing the partition count, use with caution. /// No changes are recorded to disk until `write()` is called. pub fn update_partitions_embedded( &mut self, pp: BTreeMap, num_parts: u32, ) -> io::Result<&Self> { // TODO(lucab): validate partitions. let bak = header::find_backup_lba(&mut self.device, self.config.lb_size)?; let h1 = header::Header::compute_new( true, &pp, self.guid, bak, &self.primary_header, self.config.lb_size, Some(num_parts))?; let h2 = header::Header::compute_new( false, &pp, self.guid, bak, &self.backup_header, self.config.lb_size, Some(num_parts))?; self.primary_header = Some(h1); self.backup_header = Some(h2); self.partitions = pp; self.config.initialized = true; Ok(self) } /// Persist state to disk, consuming this disk object. /// /// This is a destructive action, as it overwrite headers and /// partitions entries on disk. All writes are flushed to disk /// before returning the underlying DiskDeviceObject. pub fn write(mut self) -> io::Result> { self.write_inplace()?; Ok(self.device) } /// Persist state to disk, leaving this disk object intact. /// /// This is a destructive action, as it overwrites headers /// and partitions entries on disk. All writes are flushed /// to disk before returning. pub fn write_inplace(&mut self) -> io::Result<()> { if !self.config.writable { return Err(io::Error::new( io::ErrorKind::Other, "disk not opened in writable mode", )); } if !self.config.initialized { return Err(io::Error::new(io::ErrorKind::Other, "disk not initialized")); } debug!("Computing new headers"); trace!("old primary header: {:?}", self.primary_header); trace!("old backup header: {:?}", self.backup_header); let bak = header::find_backup_lba(&mut self.device, self.config.lb_size)?; trace!("old backup lba: {}", bak); let primary_header = self.primary_header.clone().unwrap(); let backup_header = self.backup_header.clone(); // Write all of the used partitions at the start of the partition array. let mut next_partition_index = 0_u64; for partition in self.partitions().clone().iter().filter(|p| p.1.is_used()) { // don't allow us to overflow partition array... if next_partition_index >= u64::from(primary_header.num_parts) { return Err(io::Error::new( io::ErrorKind::Other, format!("attempting to write more than max of {} partitions in primary array", primary_header.num_parts), )); } // Write to primary partition array partition.1.write_to_device( &mut self.device, next_partition_index, primary_header.part_start, self.config.lb_size, primary_header.part_size, )?; // IMPORTANT: must also write it to the backup header if it uses a different // area to store the partition array; otherwise backup header will not point // to an up to date partition array on disk. if let Some(backup_header) = backup_header.as_ref() { if next_partition_index >= u64::from(backup_header.num_parts) { return Err(io::Error::new( io::ErrorKind::Other, format!("attempting to write more than max of {} partitions in backup array", backup_header.num_parts), )); } if primary_header.part_start != backup_header.part_start { partition.1.write_to_device( &mut self.device, next_partition_index, backup_header.part_start, self.config.lb_size, backup_header.part_size, )?; } } next_partition_index += 1; } // Next, write zeros to the rest of the primary/backup partition array // (ensures any newly deleted partitions are truly removed from disk, etc.) // NOTE: we should never underflow here because of boundary checking in loop above. partition::Partition::write_zero_entries_to_device( &mut self.device, next_partition_index, u64::from(primary_header.num_parts).checked_sub(next_partition_index).unwrap(), primary_header.part_start, self.config.lb_size, primary_header.part_size, )?; if let Some(backup_header) = backup_header.as_ref() { partition::Partition::write_zero_entries_to_device( &mut self.device, next_partition_index, u64::from(backup_header.num_parts).checked_sub(next_partition_index).unwrap(), backup_header.part_start, self.config.lb_size, backup_header.part_size, )?; } let new_backup_header = header::Header::compute_new( false, &self.partitions, self.guid, bak, &self.primary_header, self.config.lb_size, None, )?; let new_primary_header = header::Header::compute_new( true, &self.partitions, self.guid, bak, &self.backup_header, self.config.lb_size, None, )?; debug!("Writing backup header"); new_backup_header.write_backup(&mut self.device, self.config.lb_size)?; debug!("Writing primary header"); new_primary_header.write_primary(&mut self.device, self.config.lb_size)?; trace!("new primary header: {:?}", new_primary_header); trace!("new backup header: {:?}", new_backup_header); self.device.flush()?; self.primary_header = Some(new_primary_header); self.backup_header = Some(new_backup_header); Ok(()) } } gpt-3.1.0/src/macros.rs000064400000000000000000000022501046102023000130370ustar 00000000000000 #[macro_use] pub(crate) mod crate_macros { /// Macro to read an exact buffer macro_rules! read_exact_buff { ($bufid:ident, $rdr:expr, $buflen:expr) => { { let mut $bufid = [0_u8; $buflen]; let _ = $rdr.read_exact(&mut $bufid)?; $bufid } } } } #[macro_use] pub mod pub_macros { /// Macro to create const for partition types. macro_rules! partition_types { ( $( $(#[$docs:meta])* ($upcase:ident, $guid:expr, $os:expr)$(,)* )+ ) => { $( $(#[$docs])* pub const $upcase: Type = Type { guid: $guid, os: $os, }; )+ impl FromStr for Type { type Err = String; fn from_str(s: &str) -> Result { match s { $( $guid => Ok($upcase), stringify!($upcase) => Ok($upcase), )+ _ => Err("Invalid or unknown Partition Type GUID.".to_string()), } } } } } }gpt-3.1.0/src/mbr.rs000064400000000000000000000306771046102023000123510ustar 00000000000000//! MBR-related types and helper functions. //! //! This module provides access to low-level primitives //! to work with Master Boot Record (MBR), also known as LBA0. use crate::disk; use crate::DiskDevice; use std::io::{Read, Write}; use std::{fmt, io}; /// Protective MBR, as defined by GPT. pub struct ProtectiveMBR { bootcode: [u8; 440], disk_signature: [u8; 4], unknown: u16, partitions: [PartRecord; 4], signature: [u8; 2], } impl fmt::Debug for ProtectiveMBR { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Protective MBR, partitions: {:#?}", self.partitions) } } impl Default for ProtectiveMBR { fn default() -> Self { Self { bootcode: [0x00; 440], disk_signature: [0x00; 4], unknown: 0, partitions: [ PartRecord::new_protective(None), PartRecord::zero(), PartRecord::zero(), PartRecord::zero(), ], signature: [0x55, 0xAA], } } } impl ProtectiveMBR { /// Create a default protective-MBR object. pub fn new() -> Self { Self::default() } /// Create a protective-MBR object with a specific protective partition size (in LB). /// The protective partition size should be the size of the disk - 1 (because the protective /// partition always begins at LBA 1 (the second sector)). pub fn with_lb_size(lb_size: u32) -> Self { Self { bootcode: [0x00; 440], disk_signature: [0x00; 4], unknown: 0, partitions: [ PartRecord::new_protective(Some(lb_size)), PartRecord::zero(), PartRecord::zero(), PartRecord::zero(), ], signature: [0x55, 0xAA], } } /// Parse input bytes into a protective-MBR object. pub fn from_bytes(buf: &[u8], sector_size: disk::LogicalBlockSize) -> io::Result { let mut pmbr = Self::new(); let totlen: u64 = sector_size.into(); if buf.len() != (totlen as usize) { return Err(io::Error::new(io::ErrorKind::Other, "invalid MBR length")); } pmbr.bootcode.copy_from_slice(&buf[0..440]); pmbr.disk_signature.copy_from_slice(&buf[440..444]); pmbr.unknown = u16::from_le_bytes(read_exact_buff!(pmbru, &buf[444..446], 2)); for (i, p) in pmbr.partitions.iter_mut().enumerate() { let start = i .checked_mul(16) .ok_or_else(|| { io::Error::new( io::ErrorKind::Other, "partition record overflow - entry start", ) })? .checked_add(446) .ok_or_else(|| { io::Error::new(io::ErrorKind::Other, "partition overflow - start offset") })?; let end = start.checked_add(16).ok_or_else(|| { io::Error::new( io::ErrorKind::Other, "partition record overflow - end offset", ) })?; *p = PartRecord::from_bytes(&buf[start..end])?; } pmbr.signature.copy_from_slice(&buf[510..512]); if pmbr.signature != [0x55, 0xAA] { return Err(io::Error::new( io::ErrorKind::Other, "invalid MBR signature", )); }; Ok(pmbr) } /// Read the LBA0 of a disk device and parse it into a protective-MBR object. pub fn from_disk( device: &mut D, sector_size: disk::LogicalBlockSize ) -> io::Result { let totlen: u64 = sector_size.into(); let mut buf = vec![0_u8; totlen as usize]; let cur = device.seek(io::SeekFrom::Current(0))?; device.seek(io::SeekFrom::Start(0))?; device.read_exact(&mut buf)?; let pmbr = Self::from_bytes(&buf, sector_size); device.seek(io::SeekFrom::Start(cur))?; pmbr } /// Return the memory representation of this MBR as a byte vector. pub fn as_bytes(&self) -> io::Result> { let mut buf: Vec = Vec::with_capacity(512); buf.write_all(&self.bootcode)?; buf.write_all(&self.disk_signature)?; buf.write_all(&self.unknown.to_le_bytes())?; for p in &self.partitions { let pdata = p.as_bytes()?; buf.write_all(&pdata)?; } buf.write_all(&self.signature)?; Ok(buf) } /// Return the 440 bytes of BIOS bootcode. pub fn bootcode(&self) -> &[u8; 440] { &self.bootcode } /// Set the 440 bytes of BIOS bootcode. /// /// This only changes the in-memory state, without overwriting /// any on-disk data. pub fn set_bootcode(&mut self, bootcode: [u8; 440]) -> &Self { self.bootcode = bootcode; self } /// Return the 4 bytes of MBR disk signature. pub fn disk_signature(&self) -> &[u8; 4] { &self.disk_signature } /// Set the 4 bytes of MBR disk signature. /// /// This only changes the in-memory state, without overwriting /// any on-disk data. pub fn set_disk_signature(&mut self, sig: [u8; 4]) -> &Self { self.disk_signature = sig; self } /// Returns the given partition (0..=3) or None if the partition index is invalid. pub fn partition(&self, partition_index: usize) -> Option { if partition_index >= self.partitions.len() { None } else { Some(self.partitions[partition_index]) } } /// Set the data for the given partition. /// Returns the previous partition record or None if the partition index is invalid. /// /// This only changes the in-memory state, without overwriting /// any on-disk data. pub fn set_partition(&mut self, partition_index: usize, partition: PartRecord) -> Option { if partition_index >= self.partitions.len() { None } else { Some(std::mem::replace(&mut self.partitions[partition_index], partition)) } } /// Write a protective MBR to LBA0, overwriting any existing data. pub fn overwrite_lba0(&self, device: &mut D) -> io::Result { let cur = device.seek(io::SeekFrom::Current(0))?; let _ = device.seek(io::SeekFrom::Start(0))?; let data = self.as_bytes()?; device.write_all(&data)?; device.flush()?; device.seek(io::SeekFrom::Start(cur))?; Ok(data.len()) } /// Update LBA0, preserving most bytes of any existing MBR. /// /// This overwrites the four MBR partition records and the /// well-known signature, leaving all other MBR bits as-is. pub fn update_conservative(&self, device: &mut D) -> io::Result { let cur = device.seek(io::SeekFrom::Current(0))?; // Seek to first partition record. // (GPT spec 2.7 - sec. 5.2.3 - table 15) let _ = device.seek(io::SeekFrom::Start(446))?; for p in &self.partitions { let pdata = p.as_bytes()?; device.write_all(&pdata)?; } device.write_all(&self.signature)?; device.flush()?; device.seek(io::SeekFrom::Start(cur))?; let bytes_updated: usize = (16 * 4) + 2; Ok(bytes_updated) } } /// A partition record, MBR-style. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct PartRecord { /// Bit 7 set if partition is active (bootable) pub boot_indicator: u8, /// CHS address of partition start: 8-bit value of head in CHS address pub start_head: u8, /// CHS address of partition start: Upper 2 bits are 8th-9th bits of cylinder, lower 6 bits are sector pub start_sector: u8, /// CHS address of partition start: Lower 8 bits of cylinder pub start_track: u8, /// Partition type. See https://www.win.tue.nl/~aeb/partitions/partition_types-1.html pub os_type: u8, /// CHS address of partition end: 8-bit value of head in CHS address pub end_head: u8, /// CHS address of partition end: Upper 2 bits are 8th-9th bits of cylinder, lower 6 bits are sector pub end_sector: u8, /// CHS address of partition end: Lower 8 bits of cylinder pub end_track: u8, /// LBA of start of partition pub lb_start: u32, /// Number of sectors in partition pub lb_size: u32, } impl PartRecord { /// Create a protective Partition Record object with a specific disk size (in LB). pub fn new_protective(lb_size: Option) -> Self { let size = lb_size.unwrap_or(0xFF_FF_FF_FF); Self { boot_indicator: 0x00, start_head: 0x00, start_sector: 0x02, start_track: 0x00, os_type: 0xEE, end_head: 0xFF, end_sector: 0xFF, end_track: 0xFF, lb_start: 1, lb_size: size, } } /// Create an all-zero Partition Record. pub fn zero() -> Self { Self { boot_indicator: 0x00, start_head: 0x00, start_sector: 0x00, start_track: 0x00, os_type: 0x00, end_head: 0x00, end_sector: 0x00, end_track: 0x00, lb_start: 0, lb_size: 0, } } /// Parse input bytes into a Partition Record. pub fn from_bytes(buf: &[u8]) -> io::Result { if buf.len() != 16 { return Err(io::Error::new( io::ErrorKind::Other, "invalid length for a partition record", )); }; let pr = Self { boot_indicator: buf[0], start_head: buf[1], start_sector: buf[2], start_track: buf[3], os_type: buf[4], end_head: buf[5], end_sector: buf[6], end_track: buf[7], lb_start: u32::from_le_bytes(read_exact_buff!(lbs, &buf[8..12], 4)), lb_size: u32::from_le_bytes(read_exact_buff!(lbsize, &buf[12..16], 4) ), }; Ok(pr) } /// Return the memory representation of this Partition Record as a byte vector. pub fn as_bytes(&self) -> io::Result> { let mut buf: Vec = Vec::with_capacity(16); buf.write_all(&self.boot_indicator.to_le_bytes())?; buf.write_all(&self.start_head.to_le_bytes())?; buf.write_all(&self.start_sector.to_le_bytes())?; buf.write_all(&self.start_track.to_le_bytes())?; buf.write_all(&self.os_type.to_le_bytes())?; buf.write_all(&self.end_head.to_le_bytes())?; buf.write_all(&self.end_sector.to_le_bytes())?; buf.write_all(&self.end_track.to_le_bytes())?; buf.write_all(&self.lb_start.to_le_bytes())?; buf.write_all(&self.lb_size.to_le_bytes())?; Ok(buf) } } /// Return the 440 bytes of BIOS bootcode. pub fn read_bootcode(device: &mut D) -> io::Result<[u8; 440]> { let bootcode_offset = 0; let cur = device.seek(io::SeekFrom::Current(0))?; let _ = device.seek(io::SeekFrom::Start(bootcode_offset))?; let mut bootcode = [0x00; 440]; device.read_exact(&mut bootcode)?; device.seek(io::SeekFrom::Start(cur))?; Ok(bootcode) } /// Write the 440 bytes of BIOS bootcode. pub fn write_bootcode(device: &mut D, bootcode: &[u8; 440]) -> io::Result<()> { let bootcode_offset = 0; let cur = device.seek(io::SeekFrom::Current(0))?; let _ = device.seek(io::SeekFrom::Start(bootcode_offset))?; device.write_all(bootcode)?; device.flush()?; device.seek(io::SeekFrom::Start(cur))?; Ok(()) } /// Read the 4 bytes of MBR disk signature. pub fn read_disk_signature(device: &mut D) -> io::Result<[u8; 4]> { let dsig_offset = 440; let cur = device.seek(io::SeekFrom::Current(0))?; let _ = device.seek(io::SeekFrom::Start(dsig_offset))?; let mut dsig = [0x00; 4]; device.read_exact(&mut dsig)?; device.seek(io::SeekFrom::Start(cur))?; Ok(dsig) } /// Write the 4 bytes of MBR disk signature. #[cfg_attr(feature = "cargo-clippy", allow(clippy::trivially_copy_pass_by_ref))] pub fn write_disk_signature(device: &mut D, sig: &[u8; 4]) -> io::Result<()> { let dsig_offset = 440; let cur = device.seek(io::SeekFrom::Current(0))?; let _ = device.seek(io::SeekFrom::Start(dsig_offset))?; device.write_all(sig)?; device.flush()?; device.seek(io::SeekFrom::Start(cur))?; Ok(()) } gpt-3.1.0/src/partition.rs000064400000000000000000000351411046102023000135710ustar 00000000000000//! Partition-related types and helper functions. //! //! This module provides access to low-level primitives //! to work with GPT partitions. use bitflags::*; use crc::Crc; use log::*; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write}; use std::path::Path; use std::str::FromStr; use crate::disk; use crate::header::{parse_uuid, Header}; use crate::partition_types::Type; use crate::DiskDevice; bitflags! { /// Partition entry attributes, defined for UEFI. pub struct PartitionAttributes: u64 { /// Required platform partition. const PLATFORM = 1; /// No Block-IO protocol. const EFI = (1 << 1); /// Legacy-BIOS bootable partition. const BOOTABLE = (1 << 2); } } /// A partition entry in a GPT partition table. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Partition { /// GUID of the partition type. pub part_type_guid: Type, /// UUID of the partition. pub part_guid: uuid::Uuid, /// First LBA of the partition. pub first_lba: u64, /// Last LBA of the partition. pub last_lba: u64, /// Partition flags. pub flags: u64, /// Partition name. pub name: String, } impl Partition { /// Create a partition entry of type "unused", whose bytes are all 0s. pub fn zero() -> Self { Self { part_type_guid: crate::partition_types::UNUSED, part_guid: uuid::Uuid::nil(), first_lba: 0, last_lba: 0, flags: 0, name: "".to_string(), } } /// Serialize this partition entry to its bytes representation. fn as_bytes(&self, entry_size: u32) -> Result> { let mut buf: Vec = Vec::with_capacity(entry_size as usize); // Type GUID. let tyguid = uuid::Uuid::from_str(self.part_type_guid.guid).map_err(|e| { Error::new(ErrorKind::Other, format!("Invalid guid: {}", e)) })?; let tyguid = tyguid.as_fields(); buf.write_all(&tyguid.0.to_le_bytes())?; buf.write_all(&tyguid.1.to_le_bytes())?; buf.write_all(&tyguid.2.to_le_bytes())?; buf.write_all(tyguid.3)?; // Partition GUID. let pguid = self.part_guid.as_fields(); buf.write_all(&pguid.0.to_le_bytes())?; buf.write_all(&pguid.1.to_le_bytes())?; buf.write_all(&pguid.2.to_le_bytes())?; buf.write_all(pguid.3)?; // LBAs and flags. buf.write_all(&self.first_lba.to_le_bytes())?; buf.write_all(&self.last_lba.to_le_bytes())?; buf.write_all(&self.flags.to_le_bytes())?; // Partition name as UTF16-LE. for utf16_char in self.name.encode_utf16().take(36) { buf.write_all(&utf16_char.to_le_bytes())?; // TODO: Check this } // Resize buffer to exact entry size. buf.resize(usize::try_from(entry_size).unwrap(), 0x00); Ok(buf) } /// Write the partition entry to the partitions area in the given file. /// NOTE: does not update partitions array crc32 in the headers! pub fn write( &self, p: &Path, partition_index: u64, start_lba: u64, lb_size: disk::LogicalBlockSize, ) -> Result<()> { let mut file = OpenOptions::new().write(true).read(true).open(p)?; self.write_to_device(&mut file, partition_index, start_lba, lb_size, 128) } /// Write the partition entry to the partitions area in the given device. /// NOTE: does not update partitions array crc32 in the headers! pub fn write_to_device( &self, device: &mut D, partition_index: u64, start_lba: u64, lb_size: disk::LogicalBlockSize, bytes_per_partition: u32, ) -> Result<()> { debug!("writing partition to: {:?}", device); let pstart = start_lba .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?; // The offset is bytes_per_partition * partition_index let offset = partition_index .checked_mul(u64::from(bytes_per_partition)) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow"))?; trace!("seeking to partition start: {}", pstart + offset); device.seek(SeekFrom::Start(pstart + offset))?; trace!("writing {:?}", &self.as_bytes(bytes_per_partition)); device.write_all(&self.as_bytes(bytes_per_partition)?)?; Ok(()) } /// Write empty partition entries starting at the given index in the partition array /// for the given number of entries... pub fn write_zero_entries_to_device( device: &mut D, starting_partition_index: u64, number_entries: u64, start_lba: u64, lb_size: disk::LogicalBlockSize, bytes_per_partition: u32, ) -> Result<()> { trace!("writing {} unused partition entries starting at index {}, start_lba={}", number_entries, starting_partition_index, start_lba); let pstart = start_lba .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?; let offset = starting_partition_index .checked_mul(u64::from(bytes_per_partition)) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow"))?; trace!("seeking to starting partition start: {}", pstart + offset); device.seek(SeekFrom::Start(pstart + offset))?; let bytes_to_zero = u64::from(bytes_per_partition) .checked_mul(number_entries) .and_then(|x| usize::try_from(x).ok()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - bytes to zero"))?; device.write_all(&vec![0_u8; bytes_to_zero])?; Ok(()) } /// Return the length (in bytes) of this partition. /// Partition size is calculated as (last_lba + 1 - first_lba) * block_size /// Bounds are inclusive, meaning we add one to account for the full last logical block pub fn bytes_len(&self, lb_size: disk::LogicalBlockSize) -> Result { let len = self .last_lba .checked_sub(self.first_lba) .ok_or_else(|| Error::new(ErrorKind::Other, "partition length underflow - sectors"))? .checked_add(1) .ok_or_else(|| Error::new(ErrorKind::Other, "partition length overflow - sectors"))? .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition length overflow - bytes"))?; Ok(len) } /// Return the starting offset (in bytes) of this partition. pub fn bytes_start(&self, lb_size: disk::LogicalBlockSize) -> Result { let len = self .first_lba .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition start overflow - bytes"))?; Ok(len) } /// Check whether this partition is in use. pub fn is_used(&self) -> bool { self.part_type_guid.guid != crate::partition_types::UNUSED.guid } /// Return the number of sectors in the partition. pub fn size(&self) -> Result { match self.last_lba.checked_sub(self.first_lba) { Some(size) => Ok(size), None => Err(Error::new( ErrorKind::Other, "Invalid partition. last_lba < first_lba", )), } } } impl fmt::Display for Partition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Partition:\t\t{}\nPartition GUID:\t\t{}\nPartition Type:\t\t{}\n\ Span:\t\t\t{} - {}\nFlags:\t\t\t{}", self.name, self.part_guid, self.part_type_guid.guid, self.first_lba, self.last_lba, self.flags, ) } } fn read_part_name(rdr: &mut Cursor<&[u8]>) -> Result { trace!("Reading partition name"); let mut namebytes: Vec = Vec::new(); for _ in 0..36 { let b = u16::from_le_bytes(read_exact_buff!(bbuff, rdr, 2)); if b == 0 { break } namebytes.push(b); } Ok(String::from_utf16_lossy(&namebytes)) } /// Read a GPT partition table. /// /// ## Example /// /// ```rust,no_run /// use gpt::{header, disk, partition}; /// use std::path::Path; /// /// let lb_size = disk::DEFAULT_SECTOR_SIZE; /// let diskpath = Path::new("/dev/sdz"); /// let hdr = header::read_header(diskpath, lb_size).unwrap(); /// let partitions = partition::read_partitions(diskpath, &hdr, lb_size).unwrap(); /// println!("{:#?}", partitions); /// ``` pub fn read_partitions( path: impl AsRef, header: &Header, lb_size: disk::LogicalBlockSize, ) -> Result> { debug!("reading partitions from file: {}", path.as_ref().display()); let mut file = File::open(path)?; file_read_partitions(&mut file, header, lb_size) } const CRC_32: Crc = Crc::::new(&crc::CRC_32_ISO_HDLC); /// Read a GPT partition table from an open `Read` + `Seek` object. pub fn file_read_partitions( file: &mut D, header: &Header, lb_size: disk::LogicalBlockSize, ) -> Result> { let pstart = header .part_start .checked_mul(lb_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?; trace!("seeking to partitions start: {:#x}", pstart); let _ = file.seek(SeekFrom::Start(pstart))?; let mut parts: BTreeMap = BTreeMap::new(); trace!("scanning {} partitions", header.num_parts); let mut count = 0; for i in 0..header.num_parts { let mut bytes: [u8; 56] = [0; 56]; let mut nameraw: [u8; 72] = [0; 72]; file.read_exact(&mut bytes)?; file.read_exact(&mut nameraw)?; let test: [u8; 56] = [0; 56]; let test2: [u8; 72] = [0; 72]; // Note: unused partition entries are zeroed, so skip them if bytes.eq(&test[0..]) && nameraw.eq(&test2[0..]) { count += 1; } else { let mut reader = Cursor::new(&bytes[..]); let type_guid = parse_uuid(&mut reader)?; let part_guid = parse_uuid(&mut reader)?; let partname = read_part_name(&mut Cursor::new(&nameraw[..]))?; let p = Partition { part_type_guid: Type::from_uuid(&type_guid).unwrap_or_default(), part_guid, first_lba: u64::from_le_bytes(read_exact_buff!(flba, reader, 8)), last_lba: u64::from_le_bytes(read_exact_buff!(llba, reader, 8)), flags: u64::from_le_bytes(read_exact_buff!(flagbuff, reader, 8)), name: partname.to_string(), }; parts.insert(i + 1, p); } } debug!("Num Zeroed partitions {:?}\n\n", count); debug!("checking partition table CRC"); let _ = file.seek(SeekFrom::Start(pstart))?; let pt_len = u64::from(header.num_parts) .checked_mul(header.part_size.into()) .ok_or_else(|| Error::new(ErrorKind::Other, "partitions - size"))?; let mut table = vec![0; pt_len as usize]; file.read_exact(&mut table)?; let comp_crc = CRC_32.checksum(&table); if comp_crc != header.crc32_parts { return Err(Error::new(ErrorKind::Other, "partition table CRC mismatch")); } Ok(parts) } #[cfg(test)] mod tests { use crate::disk; use crate::partition; #[test] fn test_zero_part() { let p0 = partition::Partition::zero(); let b128 = p0.as_bytes(128).unwrap(); assert_eq!(b128.len(), 128); assert_eq!(b128, vec![0_u8; 128]); let b256 = p0.as_bytes(256).unwrap(); assert_eq!(b256.len(), 256); assert_eq!(b256, vec![0_u8; 256]); } #[test] fn test_part_bytes_len() { { // Zero. let p0 = partition::Partition::zero(); let b512len = p0.bytes_len(disk::LogicalBlockSize::Lb512).unwrap(); let b4096len = p0.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap(); // The lower bound of partition size is equal to the logical block size. // This is because the bounds for the partition size are inclusive and use // logical block addressing, meaning that even when the lba_start and lba_end // are set to the same block, you still have the space within that block. assert_eq!(b512len, 512); assert_eq!(b4096len, 4096); } { // Negative length. let mut p1 = partition::Partition::zero(); p1.first_lba = p1.last_lba + 1; p1.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err(); p1.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err(); } { // Overflowing u64 length. let mut p2 = partition::Partition::zero(); p2.last_lba = ::max_value(); p2.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err(); p2.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err(); } { // Positive value. let mut p3 = partition::Partition::zero(); p3.first_lba = 2; p3.last_lba = 3; let b512len = p3.bytes_len(disk::LogicalBlockSize::Lb512).unwrap(); let b4096len = p3.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap(); assert_eq!(b512len, 2 * 512); assert_eq!(b4096len, 2 * 4096); } } #[test] fn test_part_bytes_start() { { // Zero. let p0 = partition::Partition::zero(); let b512len = p0.bytes_start(disk::LogicalBlockSize::Lb512).unwrap(); let b4096len = p0.bytes_start(disk::LogicalBlockSize::Lb4096).unwrap(); assert_eq!(b512len, 0); assert_eq!(b4096len, 0); } { // Overflowing u64 start. let mut p1 = partition::Partition::zero(); p1.first_lba = ::max_value(); p1.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err(); p1.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err(); } { // Positive value. let mut p2 = partition::Partition::zero(); p2.first_lba = 2; let b512start = p2.bytes_start(disk::LogicalBlockSize::Lb512).unwrap(); let b4096start = p2.bytes_start(disk::LogicalBlockSize::Lb4096).unwrap(); assert_eq!(b512start, 2 * 512); assert_eq!(b4096start, 2 * 4096); } } } gpt-3.1.0/src/partition_types.rs000064400000000000000000000376601046102023000150250ustar 00000000000000//! Parition type constants use log::trace; use std::str::FromStr; /// The type #[derive(Clone, Debug, Eq, PartialEq)] pub struct Type { /// Type-GUID for a GPT partition. pub guid: &'static str, /// well-known OS label for this type-GUID. pub os: OperatingSystem, } /// Operating System #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum OperatingSystem { /// No OS None, /// Android OS Android, /// Atari Atari, /// Ceph Ceph, /// ChromeOS Chrome, /// CoreOs CoreOs, /// Custom Custom(String), /// FreeBsd FreeBsd, /// FreeDesktop FreeDesktop, /// Haiku Haiku, /// Hp Unix HpUnix, /// Linux Linux, /// MidnightBsd MidnightBsd, /// MacOs MacOs, /// NetBsd NetBsd, /// Onie Onie, /// OpenBSD OpenBsd, /// Plan9 Plan9, /// PowerPC PowerPc, /// Solaris Solaris, /// VmWare VmWare, /// Windows Windows, /// QNX QNX, } impl FromStr for OperatingSystem { type Err = String; fn from_str(s: &str) -> Result { match s { "unused" => Ok(OperatingSystem::None), "android" => Ok(OperatingSystem::Android), "atari" => Ok(OperatingSystem::Atari), "Ceph" => Ok(OperatingSystem::Ceph), "Chrome" => Ok(OperatingSystem::Chrome), "FreeBsd" => Ok(OperatingSystem::FreeBsd), "FreeDesktop" => Ok(OperatingSystem::FreeDesktop), "Haiku" => Ok(OperatingSystem::Haiku), "HP-UX" => Ok(OperatingSystem::HpUnix), "Linux" => Ok(OperatingSystem::Linux), "MacOS" => Ok(OperatingSystem::MacOs), "MidnightBsd" => Ok(OperatingSystem::MidnightBsd), "Onie" => Ok(OperatingSystem::Onie), "PowerPc" => Ok(OperatingSystem::PowerPc), "Solaris Illumos" => Ok(OperatingSystem::Solaris), _ => Err(format!("Unknown operating system: {}", s)), } } } #[test] fn test_partition_fromstr_guid() { let p = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"; let t = Type::from_str(p).unwrap(); println!("result: {:?}", t); assert_eq!(t, LINUX_FS); } #[test] fn test_partition_from_name() { // mix case as part of the test let p = "Linux_FS"; let t = Type::from_name(p).unwrap(); println!("result: {:?}", t); assert_eq!(t, LINUX_FS); } impl Type { /// Lookup a partition type by uuid pub fn from_uuid(u: &uuid::Uuid) -> Result { let uuid_str = u.as_hyphenated().to_string().to_uppercase(); trace!("looking up partition type guid {}", uuid_str); Type::from_str(&uuid_str) } /// Lookup a partition type by name pub fn from_name(name: &str) -> Result { let name_str = name.to_uppercase(); trace!("looking up partition type by name {}", name_str); Type::from_str(&name_str) } } impl Default for Type { fn default() -> Type { Type { guid: "00000000-0000-0000-0000-000000000000", os: OperatingSystem::None } } } partition_types! { /// unused (UNUSED, "00000000-0000-0000-0000-000000000000", OperatingSystem::None), /// MBR Partition Scheme (MBR, "024DEE41-33E7-11D3-9D69-0008C781F39F", OperatingSystem::None), /// EFI System Partition (EFI, "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", OperatingSystem::None), /// BIOS Boot Partition (BIOS, "21686148-6449-6E6F-744E-656564454649", OperatingSystem::None), /// Intel Fast Flash (iFFS) Partition (FLASH, "D3BFE2DE-3DAF-11DF-BA40-E3A556D89593",OperatingSystem::None), /// Sony Boot Partition (SONY_BOOT, "F4019732-066E-4E12-8273-346C5641494F", OperatingSystem::None), /// Lenovo Boot Partition (LENOVO_BOOT, "BFBFAFE7-A34F-448A-9A5B-6213EB736C22", OperatingSystem::None), /// Microsoft Reserved Partition (MICROSOFT_RESERVED, "E3C9E316-0B5C-4DB8-817D-F92DF00215AE", OperatingSystem::Windows), /// Basic Data Partition (BASIC, "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", OperatingSystem::Windows), /// Logical Disk Manager Metadata Partition (WINDOWS_METADATA, "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", OperatingSystem::Windows), /// Logical Disk Manager Data Partition (WINDOWS_DATA, "AF9B60A0-1431-4F62-BC68-3311714A69AD", OperatingSystem::Windows), /// Windows Recovery Environment (WINDOWS_RECOVERY, "DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", OperatingSystem::Windows), /// IBM General Parallel File System Partition (WINDOWS_PARALLEL, "37AFFC90-EF7D-4E96-91C3-2D7AE055B174", OperatingSystem::Windows), /// Storage Spaces Partition (WINDOWS_STORAGESPACES, "E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", OperatingSystem::Windows), /// HP Unix Data Partition (HPUNIX_DATA, "75894C1E-3AEB-11D3-B7C1-7B03A0000000", OperatingSystem::HpUnix), /// HP Unix Service Partition (HPUNIX_SERVICE, "E2A1E728-32E3-11D6-A682-7B03A0000000", OperatingSystem::HpUnix), /// Linux Filesystem Data (LINUX_FS, "0FC63DAF-8483-4772-8E79-3D69D8477DE4", OperatingSystem::Linux), /// Linux RAID Partition (LINUX_RAID, "A19D880F-05FC-4D3B-A006-743F0F84911E", OperatingSystem::Linux), /// Linux Root Partition (x86) (LINUX_ROOT_X86, "44479540-F297-41B2-9AF7-D131D5F0458A", OperatingSystem::Linux), /// Linux Root Partition (x86-64) (LINUX_ROOT_X64, "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", OperatingSystem::Linux), /// Linux Root Partition (32-bit ARM) (LINUX_ROOT_ARM_32, "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3", OperatingSystem::Linux), /// Linux Root Partition (64-bit ARM/AArch64) (LINUX_ROOT_ARM_64, "B921B045-1DF0-41C3-AF44-4C6F280D3FAE", OperatingSystem::Linux), /// Linux Swap Partition (LINUX_SWAP, "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", OperatingSystem::Linux), /// Linux Logical Volume Manager Partition (LINUX_LVM, "E6D6D379-F507-44C2-A23C-238F2A3DF928", OperatingSystem::Linux), /// Linux /home Partition (LINUX_HOME, "933AC7E1-2EB4-4F13-B844-0E14E2AEF915", OperatingSystem::Linux), /// Linux /srv (Server Data) Partition (LINUX_SRV, "3B8F8425-20E0-4F3B-907F-1A25A76F98E8", OperatingSystem::Linux), /// Linux Plain dm-crypt Partition (LINUX_DMCRYPT, "7FFEC5C9-2D00-49B7-8941-3EA10A5586B7", OperatingSystem::Linux), /// Linux LUKS Partition (LINUX_LUKS, "CA7D7CCB-63ED-4C53-861C-1742536059CC", OperatingSystem::Linux), /// Linux Reserved (LINUX_RESERVED, "8DA63339-0007-60C0-C436-083AC8230908", OperatingSystem::Linux), /// FreeBSD Data Partition (FREEBSD_DATA, "516E7CB4-6ECF-11D6-8FF8-00022D09712B", OperatingSystem::FreeBsd), /// FreeBSD Boot Partition (FREEBSD_BOOT, "83BD6B9D-7F41-11DC-BE0B-001560B84F0F", OperatingSystem::FreeBsd), /// FreeBSD Swap Partition (FREEBSD_SWAP, "516E7CB5-6ECF-11D6-8FF8-00022D09712B", OperatingSystem::FreeBsd), /// FreeBSD Unix File System (UFS) Partition (FREEBSD_UFS, "516E7CB6-6ECF-11D6-8FF8-00022D09712B", OperatingSystem::FreeBsd), /// FreeBSD Vinium Volume Manager Partition (FREEBSD_VINIUM, "516E7CB8-6ECF-11D6-8FF8-00022D09712B", OperatingSystem::FreeBsd), /// FreeBSD ZFS Partition (FREEBSD_ZFS, "516E7CBA-6ECF-11D6-8FF8-00022D09712B", OperatingSystem::FreeBsd), /// Apple Hierarchical File System Plus (HFS+) Partition (MACOS_HFSPLUS, "48465300-0000-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple UFS (MACOS_UFS, "55465300-0000-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple ZFS (MACOS_ZFS, "6A898CC3-1DD2-11B2-99A6-080020736631", OperatingSystem::MacOs), /// Apple RAID Partition (MACOS_RAID, "52414944-0000-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// APple RAID Partition, offline (MACOS_RAID_OFFLINE, "52414944-5F4F-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple Boot Partition (Recovery HD) (MACOS_RECOVERY, "426F6F74-0000-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple Label (MACOS_LABEL, "4C616265-6C00-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple TV Recovery Partition (MACOS_TV_RECOVERY, "5265636F-7665-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple Core Storage Partition (MACOS_CORE, "53746F72-6167-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Apple SoftRAID_Status (MACOS_SOFTRAID_STATUS, "B6FA30DA-92D2-4A9A-96F1-871EC6486200", OperatingSystem::MacOs), /// Apple SoftRAID_Scratch (MACOS_SOFTRAID_SCRATCH, "2E313465-19B9-463F-8126-8A7993773801", OperatingSystem::MacOs), /// Apple SoftRAID_Volume (MACOS_SOFTRAID_VOLUME, "FA709C7E-65B1-4593-BFD5-E71D61DE9B02", OperatingSystem::MacOs), /// Apple SOftRAID_Cache (MACOS_SOFTRAID_CACHE, "BBBA6DF5-F46F-4A89-8F59-8765B2727503", OperatingSystem::MacOs), /// Apple APFS (MACOS_APFS, "7C3457EF-0000-11AA-AA11-00306543ECAC", OperatingSystem::MacOs), /// Solaris Boot Partition (SOLARIS_BOOT, "6A82CB45-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Root Partition (SOLARIS_ROOT, "6A85CF4D-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Swap Partition (SOLARIS_SWAP, "6A87C46F-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Backup Partition (SOLARIS_BACKUP, "6A8B642B-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris /var Partition (SOLARIS_VAR, "6A8EF2E9-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris /home Partition (SOLARIS_HOME, "6A90BA39-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Alternate Sector (SOLARIS_ALT, "6A9283A5-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Reserved (SOLARIS_RESERVED1, "6A945A3B-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Reserved (SOLARIS_RESERVED2, "6A9630D1-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Reserved (SOLARIS_RESERVED3, "6A980767-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Reserved (SOLARIS_RESERVED4, "6A96237F-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// Solaris Reserved (SOLARIS_RESERVED5, "6A8D2AC7-1DD2-11B2-99A6-080020736631", OperatingSystem::Solaris), /// NetBSD Swap Partition (NETBSD_SWAP, "49F48D32-B10E-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// NetBSD FFS Partition (NETBSD_FFS, "49F48D5A-B10E-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// NetBSD LFS Partition (NETBSD_LFS, "49F48D82-B10E-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// NetBSD RAID Partition (NETBSD_RAID, "49F48DAA-B10E-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// NetBSD Concatenated Partition (NETBSD_CONCAT, "2DB519C4-B10F-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// NetBSD Encrypted Partition (NETBSD_ENCRYPTED, "2DB519EC-B10F-11DC-B99B-0019D1879648", OperatingSystem::NetBsd), /// ChromeOS Kernel (CHROME_KERNEL, "FE3A2A5D-4F32-41A7-B725-ACCC3285A309", OperatingSystem::Chrome), /// ChromeOS rootfs (CHROME_ROOTFS, "3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC",OperatingSystem::Chrome), /// ChromeOS Future Use (CHROME_FUTURE, "2E0A753D-9E48-43B0-8337-B15192CB1B5E",OperatingSystem::Chrome), /// CoreOS /usr partition (coreos-usr) (COREOS_USR, "5DFBF5F4-2848-4BAC-AA5E-0D9A20B745A6", OperatingSystem::CoreOs), /// CoreOS Resizable rootfs (coreos-resize) (COREOS_ROOTFS_RESIZE, "3884DD41-8582-4404-B9A8-E9B84F2DF50E", OperatingSystem::CoreOs), /// CoreOS OEM customizations (coreos-reserved) (COREOS_OEM, "C95DC21A-DF0E-4340-8D7B-26CBFA9A03E0", OperatingSystem::CoreOs), /// CoreOS Root filesystem on RAID (coreos-root-raid) (COREOS_ROOT_RAID, "BE9067B9-EA49-4F15-B4F6-F36F8C9E1818", OperatingSystem::CoreOs), /// Haiku BFS (HAIKU_BFS, "42465331-3BA3-10F1-802A-4861696B7521", OperatingSystem::Haiku), /// MidnightBSD Boot Partition (MIDNIGHT_BOOT, "85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// MidnightBSD Data Partition (MIDNIGHT_DATA, "85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// MidnightBSD Swap Partition (MIDNIGHT_SWAP, "85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// MidnightBSD Unix File System (UFS) Partition (MIDNIGHT_UFS, "0394EF8B-237E-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// MidnightBSD Vinium Volume Manager Partition (MIDNIGHT_VINIUM, "85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// MidnightBSD ZFS Partition (MIDNIGHT_ZFS, "85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7", OperatingSystem::MidnightBsd), /// Ceph Journal (CEPH_JOURNAL, "45B0969E-9B03-4F30-B4C6-B4B80CEFF106", OperatingSystem::Ceph), /// Ceph dm-crypt Encryted Journal (CEPH_CRYPT_JOURNAL, "45B0969E-9B03-4F30-B4C6-5EC00CEFF106", OperatingSystem::Ceph), /// Ceph OSD (CEPH_OSD, "4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D", OperatingSystem::Ceph), /// Ceph dm-crypt OSD (CEPH_CRYPT, "4FBD7E29-9D25-41B8-AFD0-5EC00CEFF05D", OperatingSystem::Ceph), /// Ceph Disk In Creation (CEPH_DISK_CREATION, "89C57F98-2FE5-4DC0-89C1-F3AD0CEFF2BE", OperatingSystem::Ceph), /// Ceph dm-crypt Disk In Creation (CEPH_CRYPT_CREATION, "89C57F98-2FE5-4DC0-89C1-5EC00CEFF2BE", OperatingSystem::Ceph), /// OpenBSD Data Partition (OPENBSD_DATA, "824CC7A0-36A8-11E3-890A-952519AD3F61", OperatingSystem::OpenBsd), /// QNX Power-safe (QNX6) File System (QNX_FS, "CEF5A9AD-73BC-4601-89F3-CDEEEEE321A1", OperatingSystem::QNX), /// Plan 9 Partition (PLAN9_PART, "C91818F9-8025-47AF-89D2-F030D7000C2C", OperatingSystem::Plan9), /// VMWare vmkcore (coredump partition) (VMWARE_COREDUMP, "9D275380-40AD-11DB-BF97-000C2911D1B8", OperatingSystem::VmWare), /// VMWare VMFS Filesystem Partition (VMWARE_VMFS, "AA31E02A-400F-11DB-9590-000C2911D1B8", OperatingSystem::VmWare), /// VMware Reserved (VMWARE_RESERVED, "9198EFFC-31C0-11DB-8F78-000C2911D1B8", OperatingSystem::VmWare), /// Android Bootloader (ANDROID_BOOTLOADER, "2568845D-2332-4675-BC39-8FA5A4748D15", OperatingSystem::Android), /// Android Bootloader2 (ANDROID_BOOTLOADER2, "114EAFFE-1552-4022-B26E-9B053604CF84", OperatingSystem::Android), /// Android Boot (ANDROID_BOOT, "49A4D17F-93A3-45C1-A0DE-F50B2EBE2599", OperatingSystem::Android), /// Android Recovery (ANDROID_RECOVERY, "4177C722-9E92-4AAB-8644-43502BFD5506", OperatingSystem::Android), /// Android Misc (ANDROID_MISC, "EF32A33B-A409-486C-9141-9FFB711F6266", OperatingSystem::Android), /// Android Metadata (ANDROID_META, "20AC26BE-20B7-11E3-84C5-6CFDB94711E9", OperatingSystem::Android), /// Android System (ANDROID_SYSTEM, "38F428E6-D326-425D-9140-6E0EA133647C", OperatingSystem::Android), /// Android Cache (ANDROID_CACHE, "A893EF21-E428-470A-9E55-0668FD91A2D9", OperatingSystem::Android), /// Android Data (ANDROID_DATA, "DC76DDA9-5AC1-491C-AF42-A82591580C0D", OperatingSystem::Android), /// Android Persistent (ANDROID_PERSISTENT, "EBC597D0-2053-4B15-8B64-E0AAC75F4DB1", OperatingSystem::Android), /// Android Factory (ANDROID_FACTORY, "8F68CC74-C5E5-48DA-BE91-A0C8C15E9C80", OperatingSystem::Android), /// Android Fastboot/Tertiary (ANDROID_FASTBOOT, "767941D0-2085-11E3-AD3B-6CFDB94711E9", OperatingSystem::Android), /// Android OEM (ANDROID_OEM, "AC6D7924-EB71-4DF8-B48D-E267B27148FF", OperatingSystem::Android), /// ONIE Boot (ONIE_BOOT, "7412F7D5-A156-4B13-81DC-867174929325", OperatingSystem::Onie), /// ONIE Config (ONIE_CONFIG, "D4E6E2CD-4469-46F3-B5CB-1BFF57AFC149", OperatingSystem::Onie), /// PowerPC PReP Boot (PPC_BOOT, "9E1A2D38-C612-4316-AA26-8B49521E5A8B", OperatingSystem::PowerPc), /// FreeDesktop Shared Boot Loader Configuration (FREEDESK_BOOT, "BC13C2FF-59E6-4262-A352-B275FD6F7172", OperatingSystem::FreeDesktop), /// Atari Basic Data Partition (GEM, BGM, F32) (ATARI_DATA, "734E5AFE-F61A-11E6-BC64-92361F002671", OperatingSystem::Atari), } gpt-3.1.0/tests/fixtures/gpt-headers.txt000064400000000000000000000030231046102023000163740ustar 00000000000000sudo dd if=gpt/tests/fixtures/gpt-linux-disk-01.img bs=512 count=1 skip=1 > lba.1 Primary Header of gpt-linux-disk-01.img ------------------------------------------ Signature: 0x4546492050415254 Revision: 0x00000100 HeaderSize: 92 HeaderCRC32: 0x3e9607da HeaderCRC32 (calculated): 0x3e9607da Reserved: 0x00000000 MyLBA: 1 AlternateLBA: 95 FirstUsableLBA: 34 LastUsableLBA: 62 PartitionEntryLBA: 2 NumberOfPartitionEntries: 128 SizeOfPartitionEntry: 128 PartitionEntryArrayCRC32: 0x90e9ba6 ========================================== sudo dd if=gpt/tests/fixtures/gpt-linux-disk-01.img bs=512 count=1 skip=95 > lba.last Backup Header ------------------------------------------ Signature: 0x4546492050415254 Revision: 0x00000100 HeaderSize: 92 HeaderCRC32: 0xeacc533e HeaderCRC32 (calculated): 0xeacc533e Reserved: 0x00000000 MyLBA: 95 AlternateLBA: 1 FirstUsableLBA: 34 LastUsableLBA: 62 PartitionEntryLBA: 63 NumberOfPartitionEntries: 128 SizeOfPartitionEntry: 128 PartitionEntryArrayCRC32: 0x90e9ba6gpt-3.1.0/tests/fixtures/gpt-linux-disk-01.img000064400000000000000000001400001046102023000172200ustar 00000000000000îþÿÿ_UªEFI PART\Ú–>_">XÈ/ñSÇÓA“¤¿¬ߟ€€¦› ¯=ƃ„rGŽy=iØG}ä@‚Ìo…9@H ^Ù¶">primary¯=ƃ„rGŽy=iØG}ä@‚Ìo…9@H ^Ù¶">primaryEFI PART\>SÌê_">XÈ/ñSÇÓA“¤¿¬ߟ?€€¦› gpt-3.1.0/tests/fixtures/test-headers.txt000064400000000000000000000027571046102023000165760ustar 00000000000000sudo dd if=gpt/tests/fixtures/test.img bs=512 count=1 skip=1 > lba.1 Primary Header of test.img ------------------------------------------ Signature: 0x4546492050415254 Revision: 0x00000100 HeaderSize: 92 HeaderCRC32: 0x24929e70 HeaderCRC32 (calculated): 0x24929e70 Reserved: 0x00000000 MyLBA: 1 AlternateLBA: 20479 FirstUsableLBA: 2048 LastUsableLBA: 20446 PartitionEntryLBA: 2 NumberOfPartitionEntries: 128 SizeOfPartitionEntry: 128 PartitionEntryArrayCRC32: 0xd9c4150d ========================================== sudo dd if=gpt/tests/fixtures/test.img bs=512 count=1 skip=20479 > lba.last Backup Header ------------------------------------------ Signature: 0x4546492050415254 Revision: 0x00000100 HeaderSize: 92 HeaderCRC32: 0x9e29fd39 HeaderCRC32 (calculated): 0x9e29fd39 Reserved: 0x00000000 MyLBA: 20479 AlternateLBA: 1 FirstUsableLBA: 2048 LastUsableLBA: 20446 PartitionEntryLBA: 20447 NumberOfPartitionEntries: 128 SizeOfPartitionEntry: 128 PartitionEntryArrayCRC32: 0xd9c4150dgpt-3.1.0/tests/fixtures/test.img000064400000000000000000500000001046102023000151020ustar 00000000000000îÿÿÿÿOUªEFI PART\pž’$ÿOÞOhøO4¬M¥xnҳ ÄÙ¯=ƃ„rGŽy=iØG}ä¥fþ ØOC°.‰½K¡¹¯=ƃ„rGŽy=iØG}äÞcþ%æGí‹–¿=víc¯=ƃ„rGŽy=iØG}ä¥fþ ØOC°.‰½K¡¹¯=ƃ„rGŽy=iØG}äÞcþ%æGí‹–¿=vícEFI PART\9ý)žÿOÞOhøO4¬M¥xnÒ³îßO€€ ÄÙgpt-3.1.0/tests/gpt.rs000064400000000000000000000270501046102023000127250ustar 00000000000000use gpt; use gpt::disk; use std::collections::BTreeMap; use std::convert::TryFrom; use std::io::{SeekFrom, Write}; use std::path; use tempfile::NamedTempFile; #[test] fn test_gptconfig_empty() { let tempdisk = NamedTempFile::new().expect("failed to create tempfile disk"); let cfg = { let c1 = gpt::GptConfig::new(); let c2 = gpt::GptConfig::default(); assert_eq!(c1, c2); c1 }; let lb_size = disk::LogicalBlockSize::Lb4096; let disk = cfg .initialized(false) .logical_block_size(lb_size) .open(tempdisk.path()) .unwrap(); assert_eq!(*disk.logical_block_size(), lb_size); assert_eq!(disk.primary_header(), None); assert_eq!(disk.backup_header(), None); assert!(disk.partitions().is_empty()); } #[test] fn test_gptdisk_linux_01() { let diskpath = path::Path::new("tests/fixtures/gpt-linux-disk-01.img"); let lb_size = disk::LogicalBlockSize::Lb512; let gdisk = gpt::GptConfig::new().open(diskpath).unwrap(); assert_eq!(*gdisk.logical_block_size(), lb_size); assert!(gdisk.primary_header().is_some()); assert!(gdisk.backup_header().is_some()); assert_eq!(gdisk.partitions().len(), 1); let h1 = gdisk.primary_header().unwrap(); assert_eq!(h1.current_lba, 1); assert_eq!(h1.backup_lba, 95); let h2 = gdisk.backup_header().unwrap(); assert_eq!(h2.current_lba, 95); assert_eq!(h2.backup_lba, 1); let p1 = &gdisk.partitions().get(&1_u32).unwrap(); assert_eq!(p1.name, "primary"); let p1_start = p1.bytes_start(*gdisk.logical_block_size()).unwrap(); assert_eq!(p1_start, 0x22 * 512); let p1_len = p1.bytes_len(*gdisk.logical_block_size()).unwrap(); assert_eq!(p1_len, (0x3E - 0x22 + 1) * 512); } #[test] fn test_gptdisk_linux_01_write_fidelity_with_device() { let diskpath = path::Path::new("tests/fixtures/gpt-linux-disk-01.img"); // Assumes that test_gptdisk_linux_01 has passed, no need to check answers. let mut gdisk = gpt::GptConfig::new().open(diskpath).unwrap(); let good_header1 = gdisk.primary_header().unwrap().clone(); let good_header2 = gdisk.backup_header().unwrap().clone(); let good_partitions = gdisk.partitions().clone(); println!("good header1={:?}", good_header1); println!("good header2={:?}", good_header2); println!("good partitions={:#?}", good_partitions); // Test that we can write this test partition table to an in-memory buffer // instead, then load the results and verify they should be the same. let image_size = usize::try_from(std::fs::metadata(diskpath).unwrap().len()).unwrap(); let mem_device = Box::new(std::io::Cursor::new(vec![0_u8; image_size])); gdisk.update_disk_device(mem_device, true); let mut mem_device = gdisk.write().unwrap(); // Write this memory buffer to a temp file, and load from the file to verify // that we wrote the data to the memory buffer correctly. let mut tempdisk = NamedTempFile::new().expect("failed to create tempfile disk"); let mut gpt_in_mem = vec![0_u8; image_size]; let _ = mem_device.seek(SeekFrom::Start(0)).unwrap(); mem_device.read_exact(&mut gpt_in_mem).unwrap(); tempdisk.write_all(&gpt_in_mem).unwrap(); tempdisk.flush().unwrap(); let gdisk_file = gpt::GptConfig::new().open(tempdisk.path()).unwrap(); println!("file header1={:?}", gdisk_file.primary_header().unwrap()); println!("file header2={:?}", gdisk_file.backup_header().unwrap()); println!("file partitions={:#?}", gdisk_file.partitions()); assert_eq!(gdisk_file.primary_header().unwrap(), &good_header1); assert_eq!(gdisk_file.backup_header().unwrap(), &good_header2); assert_eq!(gdisk_file.partitions().clone(), good_partitions); // Test that if we read it back from this memory buffer, it matches the known good. let gdisk_mem = gpt::GptConfig::new().open_from_device(mem_device).unwrap(); assert_eq!(gdisk_mem.primary_header().unwrap(), &good_header1); assert_eq!(gdisk_mem.backup_header().unwrap(), &good_header2); assert_eq!(gdisk_mem.partitions().clone(), good_partitions); } #[test] fn test_create_simple_on_device() { const TOTAL_BYTES: usize = 1024 * 64; let mut mem_device = Box::new(std::io::Cursor::new(vec![0_u8; TOTAL_BYTES])); // Create a protective MBR at LBA0 let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( u32::try_from((TOTAL_BYTES / 512) - 1).unwrap_or(0xFF_FF_FF_FF)); mbr.overwrite_lba0(&mut mem_device).unwrap(); let mut gdisk = gpt::GptConfig::default() .initialized(false) .writable(true) .logical_block_size(disk::LogicalBlockSize::Lb512) .create_from_device(mem_device, None) .unwrap(); // Initialize the headers using a blank partition table gdisk.update_partitions(BTreeMap::::new()).unwrap(); // At this point, gdisk.primary_header() and gdisk.backup_header() are populated... // Add a few partitions to demonstrate how... gdisk.add_partition("test1", 1024 * 12, gpt::partition_types::BASIC, 0, None).unwrap(); gdisk.add_partition("test2", 1024 * 18, gpt::partition_types::LINUX_FS, 0, None).unwrap(); let mut mem_device = gdisk.write().unwrap(); mem_device.seek(std::io::SeekFrom::Start(0)).unwrap(); let mut final_bytes = vec![0_u8; TOTAL_BYTES]; mem_device.read_exact(&mut final_bytes).unwrap(); } #[allow(dead_code)] /// todo resolve in: https://github.com/Quyzi/gpt/pull/74 fn test_create_aligned_on_device() { const TOTAL_BYTES: usize = 48 * 1024; // 48KiB, 96 Blocks const ALIGNMENT: u64 = 4096 / 512; // 8 LBA alignment let mut mem_device = Box::new(std::io::Cursor::new(vec![0u8; TOTAL_BYTES])); let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(u32::try_from((TOTAL_BYTES / 512) - 1).unwrap_or(0xFF_FF_FF_FF)); mbr.overwrite_lba0(&mut mem_device).unwrap(); let mut gdisk = gpt::GptConfig::default() .initialized(false) .writable(true) .logical_block_size(disk::LogicalBlockSize::Lb512) .create_from_device(mem_device, None) .unwrap(); gdisk.update_partitions(BTreeMap::::new()).unwrap(); // 00-33: MBR, GPT Header / Info // 40-51: Part 1 - 0.75 disk pages // 56-61: Part 2 - 0.75 disk pagess // 62-95: GPT Backup assert!(gdisk.add_partition("test1", 6 * 1024, gpt::partition_types::BASIC, 0, Some(ALIGNMENT)).is_ok(), "unexpected error writing first aligned partition: should start at LBA 40, end at 59"); assert!(gdisk.add_partition("test2", 8 * 1024, gpt::partition_types::LINUX_FS, 0, Some(ALIGNMENT)).is_err(), "expected error writing over-sized second aligned partition: impossible addressing starting at LBA 56 ending at LBA 63 shouldn't fit with GPT backup"); assert!(gdisk.add_partition("test2", 6 * 1024, gpt::partition_types::LINUX_FS, 0, Some(ALIGNMENT)).is_ok(), "unexpected error writing second aligned partition: should start at LBA 56, end at 61"); let mut mem_device = gdisk.write().unwrap(); let mut final_bytes = vec![0u8; TOTAL_BYTES]; mem_device.read_exact(&mut final_bytes).unwrap(); } fn t_read_bytes(device: &mut gpt::DiskDeviceObject, offset: u64, bytes: usize) -> Vec { let mut buf = vec![0_u8; bytes]; device.seek(std::io::SeekFrom::Start(offset)).unwrap(); device.read_exact(&mut buf).unwrap(); buf } fn test_helper_gptdisk_write_efi_unused_partition_entries(lb_size: disk::LogicalBlockSize) { // Test that we write zeros to unused areas of the partition array, so that // if we're creating a partition table from scratch (not loading an existing // table and modifying it) it will create a partition array that is UEFI // compliant (has 128 entries) and unused entries are properly initialized // with zeros. let lb_bytes: u64 = lb_size.into(); let lb_bytes_usize = lb_bytes as usize; // protective MBR + GPT header + GPT partition array let header_lbs = 1 + 1 + ((128 * 128) / lb_bytes); assert_eq!((128 * 128) % lb_bytes, 0); let data_lbs = 10; // GPT partition array + GPT header let footer_lbs = ((128 * 128) / lb_bytes) + 1; let total_lbs = header_lbs + data_lbs + footer_lbs; let total_bytes = (total_lbs * lb_bytes) as usize; // Initialize the buffer with all '255' values so we can tell what's been overwritten vs preserved. let mem_device = Box::new(std::io::Cursor::new(vec![255u8; total_bytes])); // Setup a new partition table and add a couple entries to it. let mut gdisk = gpt::GptConfig::default() .initialized(false) .writable(true) .logical_block_size(lb_size) .create_from_device(mem_device, None) .unwrap(); // Initialize the headers using a blank partition table. gdisk.update_partitions(BTreeMap::::new()).unwrap(); let part1_bytes = 3 * lb_bytes; gdisk.add_partition("test1", part1_bytes, gpt::partition_types::BASIC, 0, None).unwrap(); gdisk.add_partition("test2", (data_lbs * lb_bytes) - part1_bytes, gpt::partition_types::LINUX_FS, 0, None).unwrap(); // Write out the table and get back the memory buffer so we can validate its contents. let mut mem_device = gdisk.write().unwrap(); // Should NOT have overwritten the MBR (we have to generate a protective MBR explicitly using mbr module) assert_eq!(t_read_bytes(&mut mem_device, 0, lb_bytes_usize), vec![255u8; lb_bytes_usize]); // Should have overwritten the header assert_ne!(t_read_bytes(&mut mem_device, lb_bytes, 92), vec![255u8; 92]); // According to the spec, the rest of the sector containing the header should be zeros. assert_eq!(t_read_bytes(&mut mem_device, lb_bytes + 92, lb_bytes_usize - 92), vec![0_u8; lb_bytes_usize - 92]); // The first two partition entries should have been overwritten with non-zero data. let first_two = t_read_bytes(&mut mem_device, 2 * lb_bytes, 128 * 2); assert_ne!(first_two, vec![255u8; 128 * 2]); assert_ne!(first_two, vec![0_u8; 128 * 2]); // The remaining entries should have been overwritten with all zeros. assert_eq!(t_read_bytes(&mut mem_device, (2 * lb_bytes) + (128 * 2), 126 * 128), vec![0_u8; 126 * 128]); // The data area should be completely undisturbed... let data_bytes = (data_lbs as usize) * lb_bytes_usize; assert_eq!(t_read_bytes(&mut mem_device, header_lbs * lb_bytes, data_bytes), vec![255u8; data_bytes]); // The first two partition entries in the volume footer should have been overwritten with non-zero data. // The remaining entries should have been overwritten with all zeros. let first_two = t_read_bytes(&mut mem_device, (header_lbs + data_lbs) * lb_bytes, 128 * 2); assert_ne!(first_two, vec![255u8; 128 * 2]); assert_ne!(first_two, vec![0_u8; 128 * 2]); // The remaining entries should have been overwritten with all zeros. assert_eq!(t_read_bytes(&mut mem_device, (2 * lb_bytes) + (128 * 2), 126 * 128), vec![0_u8; 126 * 128]); // Should have overwritten the backup header assert_ne!(t_read_bytes(&mut mem_device, total_bytes as u64 - lb_bytes, 92), vec![255u8; 92]); // Remainder of the sector with the backup header should be all zeros assert_eq!(t_read_bytes(&mut mem_device, total_bytes as u64 - lb_bytes + 92, lb_bytes_usize - 92), vec![0_u8; lb_bytes_usize - 92]); } #[test] fn test_gptdisk_write_efi_unused_partition_entries_512() { test_helper_gptdisk_write_efi_unused_partition_entries(disk::LogicalBlockSize::Lb512); } #[test] fn test_gptdisk_write_efi_unused_partition_entries_4096() { test_helper_gptdisk_write_efi_unused_partition_entries(disk::LogicalBlockSize::Lb4096); } gpt-3.1.0/tests/mbr.rs000064400000000000000000000063201046102023000127100ustar 00000000000000 use tempfile; use gpt::{disk, mbr}; use std::fs::File; use std::io::Read; #[test] fn test_mbr_partrecord() { let pr0 = mbr::PartRecord::zero(); let data0 = pr0.as_bytes().unwrap(); assert_eq!(data0.len(), 16); assert_eq!(data0, [0x00; 16]); let pr1 = mbr::PartRecord::new_protective(None); let data1 = pr1.as_bytes().unwrap(); assert_eq!(data0.len(), data1.len()); assert_ne!(data0, data1); } #[test] fn test_mbr_protective() { let m0 = mbr::ProtectiveMBR::new(); let data0 = m0.as_bytes().unwrap(); assert_eq!(data0.len(), 512); assert_eq!(data0[510], 0x55); assert_eq!(data0[511], 0xAA); let m1 = mbr::ProtectiveMBR::with_lb_size(0x01); let data1 = m1.as_bytes().unwrap(); assert_eq!(data0.len(), data1.len()); assert_ne!(data0, data1); assert_eq!(data1[510], 0x55); assert_eq!(data1[511], 0xAA); } #[test] fn test_mbr_write() { let mut tempdisk = tempfile::tempfile().unwrap(); let m0 = mbr::ProtectiveMBR::new(); let data0 = m0.as_bytes().unwrap(); m0.overwrite_lba0(&mut tempdisk).unwrap(); m0.update_conservative(&mut tempdisk).unwrap(); let mut buf = Vec::new(); let size = tempdisk.read_to_end(&mut buf).unwrap(); assert!(size != 0); assert_eq!(buf, data0); } #[test] fn test_mbr_read() { let mut diskf = File::open("tests/fixtures/gpt-linux-disk-01.img").unwrap(); let m0 = mbr::ProtectiveMBR::from_disk(&mut diskf, disk::LogicalBlockSize::Lb512).unwrap(); assert_eq!(m0.bootcode().to_vec(), vec![0; 440]); assert_eq!(m0.disk_signature().to_vec(), vec![0; 4]); } #[test] fn test_mbr_rw_roundtrip() { let mut tempdisk = tempfile::tempfile().unwrap(); let mut m0 = mbr::ProtectiveMBR::new(); let newsig = [0x11, 0x22, 0x33, 0x44]; m0.set_disk_signature(newsig); m0.overwrite_lba0(&mut tempdisk).unwrap(); let data0 = m0.as_bytes().unwrap(); let m1 = mbr::ProtectiveMBR::from_disk(&mut tempdisk, disk::LogicalBlockSize::Lb512).unwrap(); let data1 = m1.as_bytes().unwrap(); assert_eq!(m0.bootcode().to_vec(), m1.bootcode().to_vec()); assert_eq!(m0.disk_signature().to_vec(), m1.disk_signature().to_vec()); assert_eq!(m0.disk_signature().to_vec(), newsig); assert_eq!(data0, data1); } #[test] fn test_mbr_bootcode() { let mut tempdisk = tempfile::tempfile().unwrap(); let m0 = mbr::ProtectiveMBR::new(); m0.overwrite_lba0(&mut tempdisk).unwrap(); let b0 = mbr::read_bootcode(&mut tempdisk).unwrap(); assert_eq!(b0.len(), 440); assert_eq!(b0.to_vec(), vec![0; 440]); let b1 = [0xAA; 440]; mbr::write_bootcode(&mut tempdisk, &b1).unwrap(); let b2 = mbr::read_bootcode(&mut tempdisk).unwrap(); assert_eq!(b1.to_vec(), b2.to_vec()); } #[test] fn test_mbr_disksig() { let mut tempdisk = tempfile::tempfile().unwrap(); let m0 = mbr::ProtectiveMBR::new(); m0.overwrite_lba0(&mut tempdisk).unwrap(); let s0 = mbr::read_disk_signature(&mut tempdisk).unwrap(); assert_eq!(s0.len(), 4); assert_eq!(s0.to_vec(), vec![0; 4]); let s1 = [0x00, 0x11, 0x22, 0x33]; mbr::write_disk_signature(&mut tempdisk, &s1).unwrap(); let s2 = mbr::read_disk_signature(&mut tempdisk).unwrap(); assert_eq!(s1.to_vec(), s2.to_vec()); } gpt-3.1.0/tests/test.rs000064400000000000000000000060731046102023000131140ustar 00000000000000use simplelog; use uuid; use gpt::disk; use gpt::header::{read_header, write_header, Header}; use gpt::partition::{read_partitions, Partition}; use gpt::partition_types::Type; use simplelog::{Config, SimpleLogger}; use std::io::Write; use std::path::Path; use std::str::FromStr; use tempfile::NamedTempFile; #[test] fn test_read_header() { let expected_header = Header { signature: "EFI PART".to_string(), revision: 65536, header_size_le: 92, crc32: 1050019802, reserved: 0, current_lba: 1, backup_lba: 95, first_usable: 34, last_usable: 62, disk_guid: uuid::Uuid::from_str("f12fc858-c753-41d3-93a4-bfac001cdf9f").unwrap(), part_start: 2, num_parts: 128, part_size: 128, crc32_parts: 151952294, }; let expected_partition = Partition { part_type_guid: gpt::partition_types::LINUX_FS, part_guid: uuid::Uuid::from_str("6fcc8240-3985-4840-901f-a05e7fd9b69d").unwrap(), first_lba: 34, last_lba: 62, flags: 0, name: "primary".to_string(), }; let diskpath = Path::new("tests/fixtures/gpt-linux-disk-01.img"); let h = read_header(diskpath, disk::DEFAULT_SECTOR_SIZE).unwrap(); println!("header: {:?}", h); assert_eq!(h, expected_header); let p = read_partitions(diskpath, &h, disk::DEFAULT_SECTOR_SIZE).unwrap(); println!("Partitions: {:?}", p); assert_eq!(*p.get(&1).unwrap(), expected_partition); } #[test] fn test_write_header() { let _ = SimpleLogger::init(simplelog::LevelFilter::Trace, Config::default()); let mut tempdisk = NamedTempFile::new().expect("failed to create tempfile disk"); { let data: [u8; 4096] = [0; 4096]; println!("Creating blank header file for testing"); for _ in 0..100 { tempdisk.as_file_mut().write_all(&data).unwrap(); } }; println!("Writing header"); let w = write_header( tempdisk.path(), Some(uuid::Uuid::from_str("f400b934-48ef-4381-8f26-459f6b33c7df").unwrap()), disk::DEFAULT_SECTOR_SIZE, ); println!("Wrote header: {:?}", w); println!("Reading header"); let h = read_header(tempdisk.path(), disk::DEFAULT_SECTOR_SIZE).unwrap(); println!("header: {:#?}", h); let p = Partition { part_type_guid: Type::from_str("0FC63DAF-8483-4772-8E79-3D69D8477DE4").unwrap(), part_guid: uuid::Uuid::new_v4(), first_lba: 36, last_lba: 40, flags: 0, name: "gpt test".to_string(), }; p.write(tempdisk.path(), 0, h.part_start, disk::DEFAULT_SECTOR_SIZE) .unwrap(); } #[test] fn test_partition_type_fromstr() { assert_eq!(gpt::partition_types::Type::from_str("933AC7E1-2EB4-4F13-B844-0E14E2AEF915").unwrap(), gpt::partition_types::LINUX_HOME); assert_eq!(gpt::partition_types::Type::from_str("114EAFFE-1552-4022-B26E-9B053604CF84").unwrap(), gpt::partition_types::ANDROID_BOOTLOADER2); assert_eq!(gpt::partition_types::Type::from_str("00000000-0000-0000-0000-000000000000").unwrap(), gpt::partition_types::UNUSED); }gpt-3.1.0/tests/write-partition000064400000000000000000017204401046102023000146550ustar 00000000000000îþÿÿÏUªEFI PART\V,¯Ï"­ê?Vì<D¤`úo°;Ý`€ñUßÅž–°E›0O´Æ´¸ ïñTÁW}ÇÁO‹ézÇ\±Ôö$8rusty partitionEFI PART\!´‹¡Ï"­ê?Vì<D¤`úo°;Ý`€ñUßÅ