├── .gitignore ├── src ├── modes │ ├── mod.rs │ ├── device.rs │ ├── apply.rs │ └── clean.rs ├── arguments.rs ├── main.rs └── block_device.rs ├── .github ├── dependabot.yml └── workflows │ ├── check.yaml │ ├── build.yaml │ ├── release.yml │ └── lint.yaml ├── Cargo.toml ├── README.md ├── flake.nix ├── flake.lock ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /completions 2 | /target 3 | /testing 4 | -------------------------------------------------------------------------------- /src/modes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod apply; 2 | pub mod clean; 3 | pub mod device; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "cargo" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: ".github/workflows" 11 | schedule: 12 | interval: "monthly" 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "overmask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "LGPL-3.0-or-later" 6 | build = "build.rs" 7 | 8 | [profile.release] 9 | codegen-units = 1 10 | lto = true 11 | panic = "abort" 12 | strip = true 13 | 14 | [profile.small] 15 | inherits = "release" 16 | opt-level = "z" 17 | 18 | [dependencies] 19 | block-utils = "0" 20 | clap = { version = "4", features = ["derive"] } 21 | clap_complete = "4" 22 | ctrlc = "3" 23 | nix = { version = "0", features = ["fs"] } 24 | vblk = "0" 25 | 26 | [build-dependencies] 27 | clap = { version = "4", features = ["derive"] } 28 | clap_complete = "4" 29 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check 2 | on: 3 | push: 4 | paths: 5 | - '**.nix' 6 | - flake.lock 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | nix-flake: 12 | name: Nix flake 13 | runs-on: ubuntu-22.04 14 | 15 | steps: 16 | - name: Clone repository 17 | uses: actions/checkout@v6 18 | 19 | - name: Install Nix 20 | uses: DeterminateSystems/nix-installer-action@v21 21 | with: 22 | extra-conf: | 23 | log-lines = 500 24 | 25 | - name: Set up Magic Nix Cache 26 | uses: DeterminateSystems/magic-nix-cache-action@v13 27 | 28 | - name: Check Nix flake inputs 29 | uses: DeterminateSystems/flake-checker-action@v12 30 | with: 31 | fail-mode: true 32 | check-outdated: false 33 | 34 | - name: Check Nix flake outputs 35 | run: nix flake check -v --all-systems 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # overmask 2 | 3 | Add a writeable overlay on top of read-only files 4 | 5 | ## Installation 6 | 7 | Nix flake: `github:ErrorNoInternet/overmask` 8 | 9 | AUR: https://aur.archlinux.org/packages/overmask 10 | 11 | COPR: https://copr.fedorainfracloud.org/coprs/errornointernet/packages 12 | 13 | ### cargo 14 | 15 | ```sh 16 | $ git clone https://github.com/ErrorNoInternet/overmask 17 | $ cd overmask 18 | $ cargo install --path . 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```sh 24 | # required for the virtual block device (/dev/nbd*) 25 | $ sudo modprobe nbd 26 | 27 | # create (empty) files to store data in 28 | $ touch overlay_file mask_file 29 | 30 | # device mode: 31 | # read from /dev/sda, but redirect all writes to overlay_file and 32 | # use mask_file to keep track of what has been written so that 33 | # future reads would be from overlay_file instead of /dev/sda 34 | $ overmask -s /dev/sda -o overlay_file -m mask_file dev 35 | 36 | # you can now send arbitrary write commands to the virtual block device 37 | $ sudo dd if=/dev/zero of=/dev/nbd0 38 | # and all the zeros would be in overlay_file instead of /dev/sda 39 | ``` 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | paths: 5 | - '**.nix' 6 | - '**.rs' 7 | - Cargo.* 8 | - flake.lock 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | overmask: 14 | name: overmask 15 | runs-on: ubuntu-22.04 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | - system: aarch64-linux 22 | - system: x86_64-linux 23 | 24 | steps: 25 | - name: Clone repository 26 | uses: actions/checkout@v6 27 | 28 | - name: Install QEMU 29 | run: | 30 | sudo apt update -y 31 | sudo apt install -y qemu-user-static 32 | 33 | - name: Install Nix 34 | uses: DeterminateSystems/nix-installer-action@v21 35 | with: 36 | extra-conf: | 37 | log-lines = 500 38 | 39 | - name: Set up Cachix 40 | uses: cachix/cachix-action@v16 41 | with: 42 | name: errornobinaries 43 | authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" 44 | 45 | - name: Build for ${{ matrix.system }} 46 | run: nix build -L --system ${{ matrix.system }} 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | overmask: 8 | name: overmask 9 | runs-on: ubuntu-22.04 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: x86_64-unknown-linux-musl 16 | path: target/x86_64-unknown-linux-musl/release/overmask 17 | 18 | steps: 19 | - name: Clone repository 20 | uses: actions/checkout@v6 21 | 22 | - name: Install Nix 23 | uses: DeterminateSystems/nix-installer-action@v21 24 | with: 25 | extra-conf: | 26 | log-lines = 500 27 | 28 | - name: Set up Magic Nix Cache 29 | uses: DeterminateSystems/magic-nix-cache-action@v13 30 | 31 | - name: Compile for ${{ matrix.target }} 32 | run: nix develop -c cargo b -r --target ${{ matrix.target }} 33 | 34 | - name: Upload build artifacts 35 | uses: svenstaro/upload-release-action@v2 36 | with: 37 | repo_token: ${{ secrets.GITHUB_TOKEN }} 38 | tag: ${{ github.ref }} 39 | file: ${{ matrix.path }} 40 | asset_name: overmask_${{ matrix.target }} 41 | -------------------------------------------------------------------------------- /src/modes/device.rs: -------------------------------------------------------------------------------- 1 | use crate::{block_device::Virtual, Files}; 2 | use std::path::PathBuf; 3 | use vblk::mount; 4 | 5 | pub fn main( 6 | files: Files, 7 | nbd_device: &PathBuf, 8 | nbd_timeout: u64, 9 | print_operations: bool, 10 | trim_no_punch_holes: bool, 11 | ) { 12 | let mut virtual_block_device = Virtual { 13 | files, 14 | print_operations, 15 | trim_no_punch_holes, 16 | }; 17 | unsafe { 18 | if let Err(error) = mount(&mut virtual_block_device, nbd_device, |device| { 19 | println!( 20 | "successfully opened virtual block device at {}", 21 | nbd_device.to_string_lossy() 22 | ); 23 | 24 | if let Err(error) = device.set_timeout(std::time::Duration::from_secs(nbd_timeout)) { 25 | eprintln!( 26 | "overmask: couldn't set virtual block device timeout to {nbd_timeout} seconds: {error}", 27 | ); 28 | } 29 | 30 | if let Err(error) = ctrlc::set_handler(move || { 31 | if let Err(error) = device.unmount() { 32 | eprintln!("overmask: couldn't unmount virtual block device: {error}"); 33 | } 34 | }) { 35 | eprintln!("overmask: couldn't add ctrlc handler: {error}"); 36 | } 37 | 38 | Ok(()) 39 | }) { 40 | eprintln!("overmask: couldn't mount virtual block device: {error}"); 41 | } 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/arguments.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use std::path::PathBuf; 3 | 4 | /// Add a writeable overlay on top of read-only files 5 | #[derive(Debug, Parser)] 6 | #[command(version)] 7 | pub struct Arguments { 8 | /// Where original read-only data should be read from 9 | #[arg(short, long, value_name = "FILE")] 10 | pub seed_file: PathBuf, 11 | 12 | /// Where modified (written) data should be stored 13 | #[arg(short, long, value_name = "FILE")] 14 | pub overlay_file: PathBuf, 15 | 16 | /// Where a mask of the modified data should be stored 17 | #[arg(short, long, value_name = "FILE")] 18 | pub mask_file: PathBuf, 19 | 20 | /// Block size for all read and write operations 21 | #[arg(short, long, value_name = "BYTES", default_value_t = 512)] 22 | pub block_size: u32, 23 | 24 | /// Ignore IO errors from the underlying files 25 | #[arg(short, long)] 26 | pub ignore_errors: bool, 27 | 28 | #[command(subcommand)] 29 | pub subcommand: MainSubcommand, 30 | } 31 | 32 | #[derive(Debug, Subcommand)] 33 | pub enum MainSubcommand { 34 | /// Apply the overlay on top of the seed using the mask 35 | #[command(visible_aliases = ["a"])] 36 | Apply { 37 | #[arg(long)] 38 | force: bool, 39 | }, 40 | 41 | /// Deduplicate data between the seed and overlay 42 | #[command(visible_aliases = ["c"])] 43 | Clean { 44 | /// Truncate the overlay and mask files to the last used byte 45 | #[arg(short, long)] 46 | truncate: bool, 47 | }, 48 | 49 | /// Create a virtual block device to capture writes 50 | #[command(visible_aliases = ["d", "dev"])] 51 | Device { 52 | /// nbd device file (requires nbd kernel module) 53 | #[arg(short, long, default_value = "/dev/nbd0")] 54 | nbd_device: PathBuf, 55 | 56 | /// nbd device timeout in seconds 57 | #[arg(short = 't', long, value_name = "SECONDS", default_value_t = 60)] 58 | nbd_timeout: u64, 59 | 60 | /// Print every IO operation (`read()`, `write()`, `flush()`, etc) 61 | #[arg(short, long)] 62 | print_operations: bool, 63 | 64 | /// Don't punch holes (fallocate) in overlay and mask on `trim()` 65 | #[arg(short = 'T', long)] 66 | trim_no_punch_holes: bool, 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "overmask - Add a writeable overlay on top of read-only files"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | 7 | flake-parts = { 8 | url = "github:hercules-ci/flake-parts"; 9 | inputs.nixpkgs-lib.follows = "nixpkgs"; 10 | }; 11 | 12 | rust-overlay = { 13 | url = "github:oxalica/rust-overlay"; 14 | inputs.nixpkgs.follows = "nixpkgs"; 15 | }; 16 | }; 17 | 18 | outputs = { 19 | flake-parts, 20 | nixpkgs, 21 | rust-overlay, 22 | self, 23 | ... 24 | } @ inputs: 25 | flake-parts.lib.mkFlake {inherit inputs;} { 26 | systems = [ 27 | "aarch64-linux" 28 | "x86_64-linux" 29 | ]; 30 | 31 | perSystem = { 32 | system, 33 | pkgs, 34 | ... 35 | }: let 36 | rust = pkgs.rust-bin.selectLatestNightlyWith (toolchain: 37 | toolchain.default.override { 38 | targets = [ 39 | "x86_64-unknown-linux-gnu" 40 | "x86_64-unknown-linux-musl" 41 | ]; 42 | extensions = [ 43 | "rust-src" 44 | "rust-analyzer-preview" 45 | ]; 46 | }); 47 | in { 48 | _module.args.pkgs = import nixpkgs { 49 | inherit system; 50 | overlays = [rust-overlay.overlays.default]; 51 | }; 52 | 53 | devShells.default = pkgs.mkShell { 54 | name = "overmask"; 55 | 56 | buildInputs = with pkgs; [ 57 | pkg-config 58 | rust 59 | taplo 60 | udev 61 | ]; 62 | 63 | RUST_BACKTRACE = 1; 64 | }; 65 | 66 | packages = rec { 67 | overmask = pkgs.rustPlatform.buildRustPackage { 68 | pname = "overmask"; 69 | version = 70 | self.shortRev 71 | or self.dirtyShortRev; 72 | 73 | src = pkgs.lib.cleanSource ./.; 74 | cargoLock.lockFile = ./Cargo.lock; 75 | 76 | nativeBuildInputs = with pkgs; [ 77 | pkg-config 78 | rust 79 | ]; 80 | 81 | buildInputs = with pkgs; [ 82 | udev 83 | ]; 84 | }; 85 | default = overmask; 86 | }; 87 | }; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod arguments; 2 | mod block_device; 3 | mod modes; 4 | 5 | use crate::arguments::{Arguments, MainSubcommand}; 6 | use crate::block_device::get_size; 7 | use clap::Parser; 8 | use std::{fs, process::exit}; 9 | 10 | const MASK: u8 = 0xff; 11 | 12 | pub struct Files { 13 | pub seed: fs::File, 14 | pub seed_size: u64, 15 | 16 | pub overlay: fs::File, 17 | pub overlay_size: u64, 18 | 19 | pub mask: fs::File, 20 | pub mask_size: u64, 21 | 22 | pub block_size: u32, 23 | pub ignore_errors: bool, 24 | } 25 | 26 | fn main() { 27 | let arguments = Arguments::parse(); 28 | 29 | let seed = match fs::File::open(&arguments.seed_file) { 30 | Ok(file) => file, 31 | Err(error) => { 32 | eprintln!("overmask: couldn't open seed file: {error}"); 33 | exit(1); 34 | } 35 | }; 36 | let overlay = match fs::File::options() 37 | .read(true) 38 | .write(true) 39 | .open(&arguments.overlay_file) 40 | { 41 | Ok(file) => file, 42 | Err(error) => { 43 | eprintln!("overmask: couldn't open overlay file: {error}"); 44 | exit(1); 45 | } 46 | }; 47 | let mask = match fs::File::options() 48 | .read(true) 49 | .write(true) 50 | .open(&arguments.mask_file) 51 | { 52 | Ok(file) => file, 53 | Err(error) => { 54 | eprintln!("overmask: couldn't open mask file: {error}"); 55 | exit(1); 56 | } 57 | }; 58 | let seed_size = get_size(&arguments.seed_file); 59 | let overlay_size = get_size(&arguments.overlay_file); 60 | let mask_size = get_size(&arguments.mask_file); 61 | println!("seed: {seed_size} bytes, overlay: {overlay_size} bytes, mask: {mask_size} bytes"); 62 | 63 | let files = Files { 64 | seed, 65 | seed_size, 66 | overlay, 67 | overlay_size, 68 | mask, 69 | mask_size, 70 | block_size: arguments.block_size, 71 | ignore_errors: arguments.ignore_errors, 72 | }; 73 | match arguments.subcommand { 74 | MainSubcommand::Apply { force } => modes::apply::main(&files, &arguments.seed_file, force), 75 | MainSubcommand::Clean { truncate } => modes::clean::main(&files, truncate), 76 | MainSubcommand::Device { 77 | nbd_device, 78 | nbd_timeout, 79 | print_operations, 80 | trim_no_punch_holes, 81 | } => modes::device::main( 82 | files, 83 | &nbd_device, 84 | nbd_timeout, 85 | print_operations, 86 | trim_no_punch_holes, 87 | ), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | paths: 5 | - '**.nix' 6 | - '**.rs' 7 | - Cargo.* 8 | - flake.lock 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | cargo-toml: 14 | name: Cargo.toml 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - name: Clone repository 19 | uses: actions/checkout@v6 20 | 21 | - name: Install Nix 22 | uses: DeterminateSystems/nix-installer-action@v21 23 | with: 24 | extra-conf: | 25 | log-lines = 500 26 | 27 | - name: Set up Magic Nix Cache 28 | uses: DeterminateSystems/magic-nix-cache-action@v13 29 | 30 | - name: Run taplo lint 31 | run: nix develop -c taplo lint Cargo.toml 32 | 33 | - name: Run taplo fmt 34 | if: always() 35 | run: nix develop -c taplo fmt --check Cargo.toml 36 | 37 | rust: 38 | name: Rust 39 | runs-on: ubuntu-22.04 40 | 41 | steps: 42 | - name: Clone repository 43 | uses: actions/checkout@v6 44 | 45 | - name: Install Nix 46 | uses: DeterminateSystems/nix-installer-action@v21 47 | with: 48 | extra-conf: | 49 | log-lines = 500 50 | 51 | - name: Set up Magic Nix Cache 52 | uses: DeterminateSystems/magic-nix-cache-action@v13 53 | 54 | - name: Set up build cache 55 | uses: actions/cache@v4 56 | with: 57 | path: | 58 | ~/.cargo/bin/ 59 | ~/.cargo/registry/index/ 60 | ~/.cargo/registry/cache/ 61 | ~/.cargo/git/db/ 62 | target/ 63 | key: build-${{ runner.os }}-${{ hashFiles('Cargo.toml') }}-${{ hashFiles('flake.nix') }}-${{ hashFiles('**.lock') }} 64 | 65 | - name: cargo clippy 66 | run: nix develop -c cargo clippy -- -Dwarnings -Wclippy::pedantic 67 | 68 | - name: cargo fmt 69 | if: always() 70 | run: nix develop -c cargo fmt --check 71 | 72 | nix: 73 | name: Nix 74 | runs-on: ubuntu-22.04 75 | 76 | steps: 77 | - name: Clone repository 78 | uses: actions/checkout@v6 79 | 80 | - name: Install Nix 81 | uses: DeterminateSystems/nix-installer-action@v21 82 | with: 83 | extra-conf: | 84 | log-lines = 500 85 | 86 | - name: Set up Magic Nix Cache 87 | uses: DeterminateSystems/magic-nix-cache-action@v13 88 | 89 | - name: Check formatting 90 | run: nix run nixpkgs#alejandra -- -c . 91 | 92 | - name: Run static code analysis 93 | if: always() 94 | run: nix run nixpkgs#statix -- check 95 | 96 | - name: Scan for dead code 97 | if: always() 98 | run: nix run nixpkgs#deadnix -- -f 99 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1712014858, 11 | "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", 12 | "owner": "hercules-ci", 13 | "repo": "flake-parts", 14 | "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "hercules-ci", 19 | "repo": "flake-parts", 20 | "type": "github" 21 | } 22 | }, 23 | "flake-utils": { 24 | "inputs": { 25 | "systems": "systems" 26 | }, 27 | "locked": { 28 | "lastModified": 1705309234, 29 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 30 | "owner": "numtide", 31 | "repo": "flake-utils", 32 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "numtide", 37 | "repo": "flake-utils", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1713297878, 44 | "narHash": "sha256-hOkzkhLT59wR8VaMbh1ESjtZLbGi+XNaBN6h49SPqEc=", 45 | "owner": "NixOS", 46 | "repo": "nixpkgs", 47 | "rev": "66adc1e47f8784803f2deb6cacd5e07264ec2d5c", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "NixOS", 52 | "ref": "nixos-unstable", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "root": { 58 | "inputs": { 59 | "flake-parts": "flake-parts", 60 | "nixpkgs": "nixpkgs", 61 | "rust-overlay": "rust-overlay" 62 | } 63 | }, 64 | "rust-overlay": { 65 | "inputs": { 66 | "flake-utils": "flake-utils", 67 | "nixpkgs": [ 68 | "nixpkgs" 69 | ] 70 | }, 71 | "locked": { 72 | "lastModified": 1713492869, 73 | "narHash": "sha256-Zv+ZQq3X+EH6oogkXaJ8dGN8t1v26kPZgC5bki04GnM=", 74 | "owner": "oxalica", 75 | "repo": "rust-overlay", 76 | "rev": "1e9264d1214d3db00c795b41f75d55b5e153758e", 77 | "type": "github" 78 | }, 79 | "original": { 80 | "owner": "oxalica", 81 | "repo": "rust-overlay", 82 | "type": "github" 83 | } 84 | }, 85 | "systems": { 86 | "locked": { 87 | "lastModified": 1681028828, 88 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 89 | "owner": "nix-systems", 90 | "repo": "default", 91 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 92 | "type": "github" 93 | }, 94 | "original": { 95 | "owner": "nix-systems", 96 | "repo": "default", 97 | "type": "github" 98 | } 99 | } 100 | }, 101 | "root": "root", 102 | "version": 7 103 | } 104 | -------------------------------------------------------------------------------- /src/modes/apply.rs: -------------------------------------------------------------------------------- 1 | use crate::{Files, MASK}; 2 | use std::{fs, os::unix::fs::FileExt, path::PathBuf, process::exit}; 3 | 4 | pub fn main(files: &Files, seed_file: &PathBuf, force: bool) { 5 | if !force { 6 | println!("This is the only mode that will write data to your seed file."); 7 | println!("If you are sure you want to do this, specify the --force flag."); 8 | exit(2); 9 | } 10 | 11 | let writeable_seed = match fs::File::options().read(true).write(true).open(seed_file) { 12 | Ok(file) => file, 13 | Err(error) => { 14 | eprintln!("overmask: couldn't open seed file: {error}"); 15 | exit(1); 16 | } 17 | }; 18 | let mut overlay_buffer = vec![0; files.block_size as usize]; 19 | let mut mask_buffer = vec![0; files.block_size as usize]; 20 | let mut blocks_applied = 0; 21 | 22 | let mut last_percent = 0.0; 23 | let block_limit = files.mask_size / u64::from(files.block_size); 24 | for block in 0..block_limit { 25 | #[allow(clippy::cast_precision_loss)] 26 | let percent = block as f64 / block_limit as f64 * 100.0; 27 | if percent - last_percent > 0.1 { 28 | last_percent = percent; 29 | println!("applying blocks: {percent:.1}% ({block}/{block_limit})"); 30 | } 31 | let offset = block * u64::from(files.block_size); 32 | 33 | if let Err(error) = files.mask.read_at(&mut mask_buffer, offset) { 34 | eprintln!( 35 | "overmask: couldn't read {} bytes from mask file at offset {offset}: {error}", 36 | files.block_size, 37 | ); 38 | if !files.ignore_errors { 39 | exit(1); 40 | } 41 | } 42 | if mask_buffer.iter().all(|&byte| byte == 0) { 43 | continue; 44 | } 45 | 46 | if let Err(error) = files.overlay.read_at(&mut overlay_buffer, offset) { 47 | eprintln!( 48 | "overmask: couldn't read {} bytes from overlay file at offset {offset}: {error}", 49 | files.block_size, 50 | ); 51 | if !files.ignore_errors { 52 | exit(1); 53 | } 54 | } 55 | 56 | let mut buffer = Vec::with_capacity(files.block_size as usize); 57 | let mut possible_start = None; 58 | for (i, (mask, overlay)) in mask_buffer.iter().zip(&overlay_buffer).enumerate() { 59 | if mask == &MASK { 60 | if possible_start.is_none() { 61 | possible_start = Some(i); 62 | } 63 | buffer.push(*overlay); 64 | } else if let Some(start) = possible_start { 65 | if let Err(error) = writeable_seed.write_all_at(&buffer, offset + start as u64) { 66 | eprintln!( 67 | "overmask: couldn't write {} bytes to seed file at offset {}: {error}", 68 | buffer.len(), 69 | offset + start as u64 70 | ); 71 | if !files.ignore_errors { 72 | exit(1) 73 | } 74 | } 75 | buffer.clear(); 76 | possible_start = None; 77 | } 78 | } 79 | if let Some(start) = possible_start { 80 | if let Err(error) = writeable_seed.write_all_at(&buffer, offset + start as u64) { 81 | eprintln!( 82 | "overmask: couldn't write {} bytes to seed file at offset {}: {error}", 83 | buffer.len(), 84 | offset + start as u64 85 | ); 86 | if !files.ignore_errors { 87 | exit(1) 88 | } 89 | } 90 | } 91 | 92 | blocks_applied += 1; 93 | } 94 | println!( 95 | "successfully applied {blocks_applied} blocks ({} bytes) to seed", 96 | blocks_applied * files.block_size 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/modes/clean.rs: -------------------------------------------------------------------------------- 1 | use crate::Files; 2 | use std::{os::unix::fs::FileExt, process::exit}; 3 | 4 | pub fn main(files: &Files, truncate: bool) { 5 | println!("deduplicating seed and overlay files..."); 6 | 7 | let mut seed_buffer = vec![0; files.block_size as usize]; 8 | let mut overlay_buffer = vec![0; files.block_size as usize]; 9 | let zeros = vec![0; files.block_size as usize]; 10 | let mut blocks_freed = 0; 11 | 12 | let mut last_percent = 0.0; 13 | let block_limit = files.seed_size / u64::from(files.block_size); 14 | for block in 0..block_limit { 15 | #[allow(clippy::cast_precision_loss)] 16 | let percent = block as f64 / block_limit as f64 * 100.0; 17 | if percent - last_percent > 0.1 { 18 | last_percent = percent; 19 | println!("comparing blocks: {percent:.1}% ({block}/{block_limit})"); 20 | } 21 | let offset = block * u64::from(files.block_size); 22 | 23 | if let Err(error) = files.overlay.read_at(&mut overlay_buffer, offset) { 24 | eprintln!( 25 | "overmask: couldn't read {} bytes from overlay file at offset {offset}: {error}", 26 | files.block_size 27 | ); 28 | if !files.ignore_errors { 29 | exit(1); 30 | } 31 | } 32 | if let Err(error) = files.seed.read_at(&mut seed_buffer, offset) { 33 | eprintln!( 34 | "overmask: couldn't read {} bytes from seed file at offset {offset}: {error}", 35 | files.block_size 36 | ); 37 | if !files.ignore_errors { 38 | exit(1); 39 | } 40 | } 41 | 42 | if seed_buffer == overlay_buffer { 43 | if let Err(error) = files.mask.write_all_at(&zeros, offset) { 44 | eprintln!( 45 | "overmask: couldn't write {} bytes to mask file at offset {offset}: {error}", 46 | files.block_size 47 | ); 48 | if !files.ignore_errors { 49 | exit(1); 50 | } 51 | } 52 | if let Err(error) = files.overlay.write_all_at(&zeros, offset) { 53 | eprintln!( 54 | "overmask: couldn't write {} bytes to overlay file at offset {offset}: {error}", 55 | files.block_size 56 | ); 57 | if !files.ignore_errors { 58 | exit(1); 59 | } 60 | } 61 | blocks_freed += 1; 62 | } 63 | } 64 | println!( 65 | "successfully zeroed {blocks_freed} blocks ({} bytes)", 66 | blocks_freed * files.block_size 67 | ); 68 | 69 | if truncate { 70 | do_truncate(files); 71 | } 72 | } 73 | 74 | fn do_truncate(files: &Files) { 75 | println!("locating end of mask file..."); 76 | 77 | let mut mask_buffer = vec![0; files.block_size as usize]; 78 | let mut end_of_file = None; 79 | 80 | let mut last_percent = 0.0; 81 | let block_limit = files.mask_size / u64::from(files.block_size); 82 | for block in (0..block_limit).rev() { 83 | #[allow(clippy::cast_precision_loss)] 84 | let percent = 100.0 - block as f64 / block_limit as f64 * 100.0; 85 | if percent - last_percent > 0.1 { 86 | last_percent = percent; 87 | println!("checking blocks: {percent:.1}% ({block}/{block_limit})"); 88 | } 89 | let offset = block * u64::from(files.block_size); 90 | 91 | if let Err(error) = files.mask.read_at(&mut mask_buffer, offset) { 92 | eprintln!( 93 | "overmask: couldn't read {} bytes from mask file at offset {offset}: {error}", 94 | files.block_size 95 | ); 96 | if !files.ignore_errors { 97 | exit(1); 98 | } 99 | } 100 | if !mask_buffer.iter().all(|&byte| byte == 0) { 101 | break; 102 | } 103 | end_of_file = Some(offset); 104 | } 105 | if let Some(offset) = end_of_file { 106 | println!("truncating overlay and mask files to {offset} bytes..."); 107 | if let Err(error) = files.mask.set_len(offset) { 108 | eprintln!("overmask: couldn't truncate mask file to {offset} bytes: {error}"); 109 | exit(1); 110 | } 111 | if let Err(error) = files.overlay.set_len(offset) { 112 | eprintln!("overmask: couldn't truncate overlay file to {offset} bytes: {error}"); 113 | exit(1); 114 | } 115 | println!("successfully truncated overlay and mask files to {offset} bytes"); 116 | } else { 117 | println!("no unused block found"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/block_device.rs: -------------------------------------------------------------------------------- 1 | use crate::{Files, MASK}; 2 | use nix::fcntl::{fallocate, FallocateFlags}; 3 | use std::{ 4 | fs, 5 | io::{self, Write}, 6 | os::unix::fs::FileExt, 7 | path::PathBuf, 8 | process::exit, 9 | }; 10 | use vblk::BlockDevice; 11 | 12 | pub struct Virtual { 13 | pub files: Files, 14 | pub print_operations: bool, 15 | pub trim_no_punch_holes: bool, 16 | } 17 | 18 | impl BlockDevice for Virtual { 19 | fn read(&mut self, offset: u64, bytes: &mut [u8]) -> io::Result<()> { 20 | if self.print_operations { 21 | println!("read(offset={offset} bytes={})", bytes.len()); 22 | } 23 | 24 | let mut buffer = vec![0; bytes.len()]; 25 | let mut mask_buffer = vec![0; bytes.len()]; 26 | if let Err(error) = self.files.mask.read_at(&mut mask_buffer, offset) { 27 | eprintln!( 28 | "overmask: couldn't read {} bytes from mask file at offset {offset}: {error}", 29 | bytes.len(), 30 | ); 31 | if !self.files.ignore_errors { 32 | return Err(error); 33 | } 34 | } 35 | if mask_buffer.iter().all(|&byte| byte == 0) { 36 | if let Err(error) = self.files.seed.read_at(&mut buffer, offset) { 37 | eprintln!( 38 | "overmask: couldn't read {} bytes from seed file at offset {offset}: {error}", 39 | bytes.len(), 40 | ); 41 | if !self.files.ignore_errors { 42 | return Err(error); 43 | } 44 | } 45 | } else if mask_buffer.iter().all(|&byte| byte == MASK) { 46 | if let Err(error) = self.files.overlay.read_at(&mut buffer, offset) { 47 | eprintln!( 48 | "overmask: couldn't read {} bytes from overlay file at offset {offset}: {error}", 49 | bytes.len(), 50 | ); 51 | if !self.files.ignore_errors { 52 | return Err(error); 53 | } 54 | } 55 | } else { 56 | if let Err(error) = self.files.seed.read_at(&mut buffer, offset) { 57 | eprintln!( 58 | "overmask: couldn't read {} bytes from seed file at offset {offset}: {error}", 59 | bytes.len(), 60 | ); 61 | if !self.files.ignore_errors { 62 | return Err(error); 63 | } 64 | } 65 | let mut overlay_buffer = vec![0; bytes.len()]; 66 | if let Err(error) = self.files.overlay.read_at(&mut overlay_buffer, offset) { 67 | eprintln!( 68 | "overmask: couldn't read {} bytes from overlay file at offset {offset}: {error}", 69 | bytes.len(), 70 | ); 71 | if !self.files.ignore_errors { 72 | return Err(error); 73 | } 74 | } 75 | 76 | buffer = buffer 77 | .iter() 78 | .zip(overlay_buffer.iter()) 79 | .zip(mask_buffer.iter()) 80 | .map(|((&seed, &overlay), &mask)| if mask == MASK { overlay } else { seed }) 81 | .collect(); 82 | } 83 | 84 | bytes.copy_from_slice(&buffer[..]); 85 | Ok(()) 86 | } 87 | 88 | fn write(&mut self, offset: u64, bytes: &[u8]) -> io::Result<()> { 89 | if self.print_operations { 90 | println!("write(offset={offset} bytes={})", bytes.len()); 91 | } 92 | 93 | if let Err(error) = self.files.overlay.write_all_at(bytes, offset) { 94 | eprintln!( 95 | "overmask: couldn't write {} bytes to overlay file at offset {offset}: {error}", 96 | bytes.len(), 97 | ); 98 | if !self.files.ignore_errors { 99 | return Err(error); 100 | } 101 | } 102 | if let Err(error) = self 103 | .files 104 | .mask 105 | .write_all_at(&vec![MASK; bytes.len()], offset) 106 | { 107 | eprintln!( 108 | "overmask: couldn't write {} bytes to mask file at offset {offset}: {error}", 109 | bytes.len(), 110 | ); 111 | if !self.files.ignore_errors { 112 | return Err(error); 113 | } 114 | } 115 | Ok(()) 116 | } 117 | 118 | fn flush(&mut self) -> io::Result<()> { 119 | if self.print_operations { 120 | println!("flush()"); 121 | } 122 | 123 | if let Err(error) = self.files.overlay.flush() { 124 | eprintln!("overmask: couldn't flush overlay file: {error}"); 125 | if !self.files.ignore_errors { 126 | return Err(error); 127 | } 128 | } 129 | if let Err(error) = self.files.mask.flush() { 130 | eprintln!("overmask: couldn't flush mask file: {error}"); 131 | if !self.files.ignore_errors { 132 | return Err(error); 133 | } 134 | } 135 | Ok(()) 136 | } 137 | 138 | fn trim(&mut self, offset: u64, len: u32) -> io::Result<()> { 139 | if self.print_operations { 140 | println!("trim(offset={offset} len={len})"); 141 | } 142 | 143 | if !self.trim_no_punch_holes { 144 | let flags = FallocateFlags::FALLOC_FL_KEEP_SIZE | FallocateFlags::FALLOC_FL_PUNCH_HOLE; 145 | if let Err(error) = fallocate( 146 | &self.files.mask, 147 | flags, 148 | offset.try_into().unwrap(), 149 | len.into(), 150 | ) { 151 | eprintln!("overmask: couldn't punch hole of size {len} in mask file at offset {offset}: {error}"); 152 | } 153 | if let Err(error) = fallocate( 154 | &self.files.overlay, 155 | flags, 156 | offset.try_into().unwrap(), 157 | len.into(), 158 | ) { 159 | eprintln!("overmask: couldn't punch hole of size {len} in overlay file at offset {offset}: {error}"); 160 | } 161 | } 162 | Ok(()) 163 | } 164 | 165 | fn unmount(&mut self) { 166 | if self.print_operations { 167 | println!("unmount()"); 168 | } 169 | } 170 | 171 | fn block_size(&self) -> u32 { 172 | self.files.block_size 173 | } 174 | 175 | fn blocks(&self) -> u64 { 176 | self.files.seed_size / u64::from(self.files.block_size) 177 | } 178 | } 179 | 180 | pub fn get_size(path: &PathBuf) -> u64 { 181 | if block_utils::is_block_device(path).unwrap_or(false) { 182 | match block_utils::get_device_info(path) { 183 | Ok(device_info) => device_info.capacity, 184 | Err(error) => { 185 | eprintln!("overmask: couldn't query block device: {error}"); 186 | exit(1) 187 | } 188 | } 189 | } else { 190 | match fs::File::open(path) { 191 | Ok(file) => match file.metadata() { 192 | Ok(metadata) => metadata.len(), 193 | Err(error) => { 194 | eprintln!("overmask: couldn't query file metadata: {error}"); 195 | exit(1) 196 | } 197 | }, 198 | Err(error) => { 199 | eprintln!("overmask: couldn't open file: {error}"); 200 | exit(1) 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.11" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "utf8parse", 26 | ] 27 | 28 | [[package]] 29 | name = "anstyle" 30 | version = "1.0.10" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 33 | 34 | [[package]] 35 | name = "anstyle-parse" 36 | version = "0.2.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 39 | dependencies = [ 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-query" 45 | version = "1.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 48 | dependencies = [ 49 | "windows-sys 0.52.0", 50 | ] 51 | 52 | [[package]] 53 | name = "anstyle-wincon" 54 | version = "3.0.2" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 57 | dependencies = [ 58 | "anstyle", 59 | "windows-sys 0.52.0", 60 | ] 61 | 62 | [[package]] 63 | name = "autocfg" 64 | version = "1.1.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 67 | 68 | [[package]] 69 | name = "bitflags" 70 | version = "1.3.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 73 | 74 | [[package]] 75 | name = "bitflags" 76 | version = "2.4.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 79 | 80 | [[package]] 81 | name = "block-utils" 82 | version = "0.11.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "4c9a6288820a15b0f95b1902de1516e1b74dd187c14db33829df07530a038743" 85 | dependencies = [ 86 | "fstab", 87 | "log", 88 | "regex", 89 | "serde", 90 | "serde_json", 91 | "shellscript", 92 | "strum", 93 | "thiserror", 94 | "udev", 95 | "uuid", 96 | ] 97 | 98 | [[package]] 99 | name = "byteorder" 100 | version = "1.5.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 103 | 104 | [[package]] 105 | name = "cc" 106 | version = "1.0.83" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 109 | dependencies = [ 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "cfg-if" 115 | version = "0.1.10" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "1.0.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 124 | 125 | [[package]] 126 | name = "cfg_aliases" 127 | version = "0.2.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 130 | 131 | [[package]] 132 | name = "clap" 133 | version = "4.5.53" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 136 | dependencies = [ 137 | "clap_builder", 138 | "clap_derive", 139 | ] 140 | 141 | [[package]] 142 | name = "clap_builder" 143 | version = "4.5.53" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 146 | dependencies = [ 147 | "anstream", 148 | "anstyle", 149 | "clap_lex", 150 | "strsim", 151 | ] 152 | 153 | [[package]] 154 | name = "clap_complete" 155 | version = "4.5.61" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" 158 | dependencies = [ 159 | "clap", 160 | ] 161 | 162 | [[package]] 163 | name = "clap_derive" 164 | version = "4.5.49" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 167 | dependencies = [ 168 | "heck 0.5.0", 169 | "proc-macro2 1.0.78", 170 | "quote 1.0.35", 171 | "syn 2.0.48", 172 | ] 173 | 174 | [[package]] 175 | name = "clap_lex" 176 | version = "0.7.4" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 179 | 180 | [[package]] 181 | name = "colorchoice" 182 | version = "1.0.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 185 | 186 | [[package]] 187 | name = "crossbeam-utils" 188 | version = "0.7.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 191 | dependencies = [ 192 | "autocfg", 193 | "cfg-if 0.1.10", 194 | "lazy_static", 195 | ] 196 | 197 | [[package]] 198 | name = "ctrlc" 199 | version = "3.4.7" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" 202 | dependencies = [ 203 | "nix 0.30.1", 204 | "windows-sys 0.59.0", 205 | ] 206 | 207 | [[package]] 208 | name = "fstab" 209 | version = "0.4.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "c23024238716ec12ab45f4618673c4e175c16c43efbda9e1ed92189088897820" 212 | dependencies = [ 213 | "log", 214 | ] 215 | 216 | [[package]] 217 | name = "heck" 218 | version = "0.4.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 221 | 222 | [[package]] 223 | name = "heck" 224 | version = "0.5.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 227 | 228 | [[package]] 229 | name = "itoa" 230 | version = "1.0.10" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 233 | 234 | [[package]] 235 | name = "lazy_static" 236 | version = "1.4.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 239 | 240 | [[package]] 241 | name = "libc" 242 | version = "0.2.172" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 245 | 246 | [[package]] 247 | name = "libudev-sys" 248 | version = "0.1.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" 251 | dependencies = [ 252 | "libc", 253 | "pkg-config", 254 | ] 255 | 256 | [[package]] 257 | name = "log" 258 | version = "0.4.20" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 261 | 262 | [[package]] 263 | name = "memchr" 264 | version = "2.7.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 267 | 268 | [[package]] 269 | name = "nix" 270 | version = "0.16.1" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" 273 | dependencies = [ 274 | "bitflags 1.3.2", 275 | "cc", 276 | "cfg-if 0.1.10", 277 | "libc", 278 | "void", 279 | ] 280 | 281 | [[package]] 282 | name = "nix" 283 | version = "0.30.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 286 | dependencies = [ 287 | "bitflags 2.4.2", 288 | "cfg-if 1.0.0", 289 | "cfg_aliases", 290 | "libc", 291 | ] 292 | 293 | [[package]] 294 | name = "overmask" 295 | version = "0.1.0" 296 | dependencies = [ 297 | "block-utils", 298 | "clap", 299 | "clap_complete", 300 | "ctrlc", 301 | "nix 0.30.1", 302 | "vblk", 303 | ] 304 | 305 | [[package]] 306 | name = "pkg-config" 307 | version = "0.3.29" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" 310 | 311 | [[package]] 312 | name = "proc-macro2" 313 | version = "0.4.30" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 316 | dependencies = [ 317 | "unicode-xid", 318 | ] 319 | 320 | [[package]] 321 | name = "proc-macro2" 322 | version = "1.0.78" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 325 | dependencies = [ 326 | "unicode-ident", 327 | ] 328 | 329 | [[package]] 330 | name = "quote" 331 | version = "0.6.13" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 334 | dependencies = [ 335 | "proc-macro2 0.4.30", 336 | ] 337 | 338 | [[package]] 339 | name = "quote" 340 | version = "1.0.35" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 343 | dependencies = [ 344 | "proc-macro2 1.0.78", 345 | ] 346 | 347 | [[package]] 348 | name = "regex" 349 | version = "1.10.3" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 352 | dependencies = [ 353 | "aho-corasick", 354 | "memchr", 355 | "regex-automata", 356 | "regex-syntax", 357 | ] 358 | 359 | [[package]] 360 | name = "regex-automata" 361 | version = "0.4.5" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 364 | dependencies = [ 365 | "aho-corasick", 366 | "memchr", 367 | "regex-syntax", 368 | ] 369 | 370 | [[package]] 371 | name = "regex-syntax" 372 | version = "0.8.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 375 | 376 | [[package]] 377 | name = "rustversion" 378 | version = "1.0.14" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 381 | 382 | [[package]] 383 | name = "ryu" 384 | version = "1.0.16" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 387 | 388 | [[package]] 389 | name = "serde" 390 | version = "1.0.196" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 393 | dependencies = [ 394 | "serde_derive", 395 | ] 396 | 397 | [[package]] 398 | name = "serde_derive" 399 | version = "1.0.196" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 402 | dependencies = [ 403 | "proc-macro2 1.0.78", 404 | "quote 1.0.35", 405 | "syn 2.0.48", 406 | ] 407 | 408 | [[package]] 409 | name = "serde_json" 410 | version = "1.0.113" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" 413 | dependencies = [ 414 | "itoa", 415 | "ryu", 416 | "serde", 417 | ] 418 | 419 | [[package]] 420 | name = "shellscript" 421 | version = "0.3.1" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "15c0d07fa97f8d209609a1a1549bd886bd907f520f75e1c785783167a66d20c4" 424 | 425 | [[package]] 426 | name = "strsim" 427 | version = "0.11.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 430 | 431 | [[package]] 432 | name = "strum" 433 | version = "0.24.1" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 436 | dependencies = [ 437 | "strum_macros", 438 | ] 439 | 440 | [[package]] 441 | name = "strum_macros" 442 | version = "0.24.3" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 445 | dependencies = [ 446 | "heck 0.4.1", 447 | "proc-macro2 1.0.78", 448 | "quote 1.0.35", 449 | "rustversion", 450 | "syn 1.0.109", 451 | ] 452 | 453 | [[package]] 454 | name = "syn" 455 | version = "0.15.44" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 458 | dependencies = [ 459 | "proc-macro2 0.4.30", 460 | "quote 0.6.13", 461 | "unicode-xid", 462 | ] 463 | 464 | [[package]] 465 | name = "syn" 466 | version = "1.0.109" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 469 | dependencies = [ 470 | "proc-macro2 1.0.78", 471 | "quote 1.0.35", 472 | "unicode-ident", 473 | ] 474 | 475 | [[package]] 476 | name = "syn" 477 | version = "2.0.48" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 480 | dependencies = [ 481 | "proc-macro2 1.0.78", 482 | "quote 1.0.35", 483 | "unicode-ident", 484 | ] 485 | 486 | [[package]] 487 | name = "synstructure" 488 | version = "0.10.2" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" 491 | dependencies = [ 492 | "proc-macro2 0.4.30", 493 | "quote 0.6.13", 494 | "syn 0.15.44", 495 | "unicode-xid", 496 | ] 497 | 498 | [[package]] 499 | name = "thiserror" 500 | version = "1.0.56" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" 503 | dependencies = [ 504 | "thiserror-impl", 505 | ] 506 | 507 | [[package]] 508 | name = "thiserror-impl" 509 | version = "1.0.56" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" 512 | dependencies = [ 513 | "proc-macro2 1.0.78", 514 | "quote 1.0.35", 515 | "syn 2.0.48", 516 | ] 517 | 518 | [[package]] 519 | name = "udev" 520 | version = "0.5.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "048df778e99eea028c08cca7853b9b521df6948b59bb29ab8bb737c057f58e6d" 523 | dependencies = [ 524 | "libc", 525 | "libudev-sys", 526 | ] 527 | 528 | [[package]] 529 | name = "unicode-ident" 530 | version = "1.0.12" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 533 | 534 | [[package]] 535 | name = "unicode-xid" 536 | version = "0.1.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 539 | 540 | [[package]] 541 | name = "utf8parse" 542 | version = "0.2.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 545 | 546 | [[package]] 547 | name = "uuid" 548 | version = "1.7.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" 551 | 552 | [[package]] 553 | name = "vblk" 554 | version = "0.1.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "8a6287c39974dff72db95075c36237ec059b40e08e7d7b97364008e082c01bd9" 557 | dependencies = [ 558 | "crossbeam-utils", 559 | "nix 0.16.1", 560 | "zerocopy", 561 | ] 562 | 563 | [[package]] 564 | name = "void" 565 | version = "1.0.2" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 568 | 569 | [[package]] 570 | name = "windows-sys" 571 | version = "0.52.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 574 | dependencies = [ 575 | "windows-targets", 576 | ] 577 | 578 | [[package]] 579 | name = "windows-sys" 580 | version = "0.59.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 583 | dependencies = [ 584 | "windows-targets", 585 | ] 586 | 587 | [[package]] 588 | name = "windows-targets" 589 | version = "0.52.6" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 592 | dependencies = [ 593 | "windows_aarch64_gnullvm", 594 | "windows_aarch64_msvc", 595 | "windows_i686_gnu", 596 | "windows_i686_gnullvm", 597 | "windows_i686_msvc", 598 | "windows_x86_64_gnu", 599 | "windows_x86_64_gnullvm", 600 | "windows_x86_64_msvc", 601 | ] 602 | 603 | [[package]] 604 | name = "windows_aarch64_gnullvm" 605 | version = "0.52.6" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 608 | 609 | [[package]] 610 | name = "windows_aarch64_msvc" 611 | version = "0.52.6" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 614 | 615 | [[package]] 616 | name = "windows_i686_gnu" 617 | version = "0.52.6" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 620 | 621 | [[package]] 622 | name = "windows_i686_gnullvm" 623 | version = "0.52.6" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 626 | 627 | [[package]] 628 | name = "windows_i686_msvc" 629 | version = "0.52.6" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 632 | 633 | [[package]] 634 | name = "windows_x86_64_gnu" 635 | version = "0.52.6" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 638 | 639 | [[package]] 640 | name = "windows_x86_64_gnullvm" 641 | version = "0.52.6" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 644 | 645 | [[package]] 646 | name = "windows_x86_64_msvc" 647 | version = "0.52.6" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 650 | 651 | [[package]] 652 | name = "zerocopy" 653 | version = "0.2.9" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "868979d514791b1d261b9f3d5a2a3497656dbfa3be25986ad1f03b8fbaf55d0f" 656 | dependencies = [ 657 | "byteorder", 658 | "zerocopy-derive", 659 | ] 660 | 661 | [[package]] 662 | name = "zerocopy-derive" 663 | version = "0.1.4" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "b090467ecd0624026e8a6405d343ac7382592530d54881330b3fc8e400280fa5" 666 | dependencies = [ 667 | "proc-macro2 0.4.30", 668 | "syn 0.15.44", 669 | "synstructure", 670 | ] 671 | --------------------------------------------------------------------------------