asahi-bless-0.4.2/.cargo_vcs_info.json0000644000000001510000000000100132270ustar { "git": { "sha1": "a467f581b822550365c5267e47b6b9da2a164fba" }, "path_in_vcs": "asahi-bless" }asahi-bless-0.4.2/Cargo.lock0000644000000242470000000000100112160ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "apple-nvram" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86c1056f6f4803156772f3369ae54d01cdfa36e901a1a271c620c3d445ae7eab" dependencies = [ "adler32", "crc32fast", "nix", ] [[package]] name = "asahi-bless" version = "0.4.2" dependencies = [ "apple-nvram", "clap", "gpt", "uuid", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gpt" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" dependencies = [ "bitflags 2.6.0", "crc", "log", "uuid", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "libc" version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", "pin-utils", ] [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" asahi-bless-0.4.2/Cargo.toml0000644000000021570000000000100112350ustar # 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" name = "asahi-bless" version = "0.4.2" build = false autobins = false autoexamples = false autotests = false autobenches = false description = "A tool to select active boot partition on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" readme = false license = "MIT" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" [lib] name = "asahi_bless" path = "src/lib.rs" [[bin]] name = "asahi-bless" path = "src/main.rs" [dependencies.apple-nvram] version = "0.3" [dependencies.clap] version = "4.4.11" features = ["derive"] [dependencies.gpt] version = "3" [dependencies.uuid] version = "1" asahi-bless-0.4.2/Cargo.toml.orig000064400000000000000000000010421046102023000147060ustar 00000000000000[package] name = "asahi-bless" version = "0.4.2" edition = "2021" license = "MIT" description = "A tool to select active boot partition on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] uuid = "1" gpt = "3" clap = { version = "4.4.11", features = ["derive"] } [dependencies.apple-nvram] path = "../apple-nvram" version = "0.3" asahi-bless-0.4.2/LICENSE000064400000000000000000000020741046102023000130320ustar 00000000000000MIT License Copyright (c) 2022 The Asahi Linux Contributors 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.asahi-bless-0.4.2/offsets.c000064400000000000000000000124551046102023000136460ustar 00000000000000#include #include #include #define MAX_CKSUM_SIZE 8 typedef uint64_t oid_t; typedef uint64_t xid_t; typedef struct obj_phys { uint8_t o_cksum[MAX_CKSUM_SIZE]; oid_t o_oid; xid_t o_xid; uint32_t o_type; uint32_t o_subtype; } obj_phys_t; typedef int64_t paddr_t; typedef struct prange { paddr_t pr_start_paddr; uint64_t pr_block_count; } prange_t; typedef unsigned char uuid_t[16]; #define NX_MAX_FILE_SYSTEMS 100 typedef enum { NX_CNTR_OBJ_CKSUM_SET = 0, NX_CNTR_OBJ_CKSUM_FAIL = 1, NX_NUM_COUNTERS = 32 } nx_counter_id_t; #define NX_EPH_INFO_COUNT 4 typedef struct nx_superblock { obj_phys_t nx_o; uint32_t nx_magic; uint32_t nx_block_size; uint64_t nx_block_count; uint64_t nx_features; uint64_t nx_readonly_compatible_features; uint64_t nx_incompatible_features; uuid_t nx_uuid; oid_t nx_next_oid; xid_t nx_next_xid; uint32_t nx_xp_desc_blocks; uint32_t nx_xp_data_blocks; paddr_t nx_xp_desc_base; paddr_t nx_xp_data_base; uint32_t nx_xp_desc_next; uint32_t nx_xp_data_next; uint32_t nx_xp_desc_index; uint32_t nx_xp_desc_len; uint32_t nx_xp_data_index; uint32_t nx_xp_data_len; oid_t nx_spaceman_oid; oid_t nx_omap_oid; oid_t nx_reaper_oid; uint32_t nx_test_type; uint32_t nx_max_file_systems; oid_t nx_fs_oid[NX_MAX_FILE_SYSTEMS]; uint64_t nx_counters[NX_NUM_COUNTERS]; prange_t nx_blocked_out_prange; oid_t nx_evict_mapping_tree_oid; uint64_t nx_flags; paddr_t nx_efi_jumpstart; uuid_t nx_fusion_uuid; prange_t nx_keylocker; uint64_t nx_ephemeral_info[NX_EPH_INFO_COUNT]; oid_t nx_test_oid; oid_t nx_fusion_mt_oid; oid_t nx_fusion_wbc_oid; prange_t nx_fusion_wbc; uint64_t nx_newest_mounted_version; prange_t nx_mkb_locker; } nx_superblock_t; typedef struct omap_phys { obj_phys_t om_o; uint32_t om_flags; uint32_t om_snap_count; uint32_t om_tree_type; uint32_t om_snapshot_tree_type; oid_t om_tree_oid; oid_t om_snapshot_tree_oid; xid_t om_most_recent_snap; xid_t om_pending_revert_min; xid_t om_pending_revert_max; } omap_phys_t; typedef struct nloc { uint16_t off; uint16_t len; } nloc_t; typedef struct btree_node_phys { obj_phys_t btn_o; uint16_t btn_flags; uint16_t btn_level; uint32_t btn_nkeys; nloc_t btn_table_space; nloc_t btn_free_space; nloc_t btn_key_free_list; nloc_t btn_val_free_list; uint64_t btn_data[]; } btree_node_phys_t; typedef struct btree_info_fixed { uint32_t bt_flags; uint32_t bt_node_size; uint32_t bt_key_size; uint32_t bt_val_size; } btree_info_fixed_t; typedef struct btree_info { btree_info_fixed_t bt_fixed; uint32_t bt_longest_key; uint32_t bt_longest_val; uint64_t bt_key_count; uint64_t bt_node_count; } btree_info_t; typedef uint32_t crypto_flags_t; typedef uint32_t cp_key_class_t; typedef uint32_t cp_key_os_version_t; typedef uint16_t cp_key_revision_t; struct wrapped_meta_crypto_state { uint16_t major_version; uint16_t minor_version; crypto_flags_t cpflags; cp_key_class_t persistent_class; cp_key_os_version_t key_os_version; cp_key_revision_t key_revision; uint16_t unused; } __attribute__((aligned(2), packed)); typedef struct wrapped_meta_crypto_state wrapped_meta_crypto_state_t; #define APFS_MODIFIED_NAMELEN 32 typedef struct apfs_modified_by { uint8_t id[APFS_MODIFIED_NAMELEN]; uint64_t timestamp; xid_t last_xid; } apfs_modified_by_t; #define APFS_MAX_HIST 8 #define APFS_VOLNAME_LEN 256 typedef struct apfs_superblock { obj_phys_t apfs_o; uint32_t apfs_magic; uint32_t apfs_fs_index; uint64_t apfs_features; uint64_t apfs_readonly_compatible_features; uint64_t apfs_incompatible_features; uint64_t apfs_unmount_time; uint64_t apfs_fs_reserve_block_count; uint64_t apfs_fs_quota_block_count; uint64_t apfs_fs_alloc_count; wrapped_meta_crypto_state_t apfs_meta_crypto; uint32_t apfs_root_tree_type; uint32_t apfs_extentref_tree_type; uint32_t apfs_snap_meta_tree_type; oid_t apfs_omap_oid; oid_t apfs_root_tree_oid; oid_t apfs_extentref_tree_oid; oid_t apfs_snap_meta_tree_oid; xid_t apfs_revert_to_xid; oid_t apfs_revert_to_sblock_oid; uint64_t apfs_next_obj_id; uint64_t apfs_num_files; uint64_t apfs_num_directories; uint64_t apfs_num_symlinks; uint64_t apfs_num_other_fsobjects; uint64_t apfs_num_snapshots; uint64_t apfs_total_blocks_alloced; uint64_t apfs_total_blocks_freed; uuid_t apfs_vol_uuid; uint64_t apfs_last_mod_time; uint64_t apfs_fs_flags; apfs_modified_by_t apfs_formatted_by; apfs_modified_by_t apfs_modified_by[APFS_MAX_HIST]; uint8_t apfs_volname[APFS_VOLNAME_LEN]; uint32_t apfs_next_doc_id; uint16_t apfs_role; uint16_t reserved; xid_t apfs_root_to_xid; oid_t apfs_er_state_oid; uint64_t apfs_cloneinfo_id_epoch; uint64_t apfs_cloneinfo_xid; oid_t apfs_snap_meta_ext_oid; uuid_t apfs_volume_group_id; oid_t apfs_integrity_meta_oid; oid_t apfs_fext_tree_oid; uint32_t apfs_fext_tree_type; uint32_t reserved_type; oid_t reserved_oid; } apfs_superblock_t; int main() { printf("%ld\n", offsetof(apfs_superblock_t, apfs_role)); } asahi-bless-0.4.2/src/lib.rs000064400000000000000000000264141046102023000137340ustar 00000000000000// SPDX-License-Identifier: MIT #![allow(dead_code)] use apple_nvram::{nvram_parse, VarType}; use gpt::{disk::LogicalBlockSize, GptConfig}; use std::{ borrow::Cow, collections::HashMap, fs::{File, OpenOptions}, io::{self, Read, Seek, SeekFrom}, ops::Deref, }; use uuid::Uuid; struct NxSuperblock([u8; NxSuperblock::SIZE]); impl NxSuperblock { const SIZE: usize = 1408; const MAGIC: u32 = 1112758350; //'BSXN' const MAX_FILE_SYSTEMS: usize = 100; fn get_buf(&mut self) -> &mut [u8] { &mut self.0 } fn new() -> Self { NxSuperblock([0; NxSuperblock::SIZE]) } fn magic(&self) -> u32 { u32::from_le_bytes(self.0[32..32 + 4].try_into().unwrap()) } fn block_size(&self) -> u32 { u32::from_le_bytes(self.0[36..36 + 4].try_into().unwrap()) } fn xid(&self) -> u64 { u64::from_le_bytes(self.0[16..16 + 8].try_into().unwrap()) } fn omap_oid(&self) -> u64 { u64::from_le_bytes(self.0[160..160 + 8].try_into().unwrap()) } fn xp_desc_blocks(&self) -> u32 { u32::from_le_bytes(self.0[104..104 + 4].try_into().unwrap()) } fn xp_desc_base(&self) -> u64 { u64::from_le_bytes(self.0[112..112 + 8].try_into().unwrap()) } fn fs_oid(&self, i: usize) -> u64 { let at = 184 + 8 * i; u64::from_le_bytes(self.0[at..at + 8].try_into().unwrap()) } } struct OmapPhys<'a>(&'a [u8]); impl OmapPhys<'_> { const SIZE: usize = 88; fn tree_oid(&self) -> u64 { u64::from_le_bytes(self.0[48..48 + 8].try_into().unwrap()) } } struct NLoc<'a>(&'a [u8]); impl NLoc<'_> { fn off(&self) -> u16 { u16::from_le_bytes(self.0[0..2].try_into().unwrap()) } fn len(&self) -> u16 { u16::from_le_bytes(self.0[2..2 + 2].try_into().unwrap()) } } struct KVOff<'a>(&'a [u8]); impl KVOff<'_> { const SIZE: usize = 4; fn k(&self) -> u16 { u16::from_le_bytes(self.0[0..2].try_into().unwrap()) } fn v(&self) -> u16 { u16::from_le_bytes(self.0[2..2 + 2].try_into().unwrap()) } } struct OmapKey<'a>(&'a [u8]); impl OmapKey<'_> { fn oid(&self) -> u64 { u64::from_le_bytes(self.0[0..8].try_into().unwrap()) } fn xid(&self) -> u64 { u64::from_le_bytes(self.0[8..8 + 8].try_into().unwrap()) } } struct OmapVal<'a>(&'a [u8]); impl OmapVal<'_> { fn flags(&self) -> u32 { u32::from_le_bytes(self.0[0..4].try_into().unwrap()) } fn size(&self) -> u32 { u32::from_le_bytes(self.0[4..4 + 4].try_into().unwrap()) } fn paddr(&self) -> u64 { u64::from_le_bytes(self.0[8..8 + 8].try_into().unwrap()) } } struct BTreeInfo; impl BTreeInfo { const SIZE: usize = 40; } struct BTreeNodePhys<'a>(&'a [u8]); impl BTreeNodePhys<'_> { const FIXED_KV_SIZE: u16 = 0x4; const ROOT: u16 = 0x1; const SIZE: usize = 56; fn flags(&self) -> u16 { u16::from_le_bytes(self.0[32..32 + 2].try_into().unwrap()) } fn level(&self) -> u16 { u16::from_le_bytes(self.0[34..34 + 2].try_into().unwrap()) } fn table_space(&self) -> NLoc<'_> { NLoc(&self.0[40..]) } fn nkeys(&self) -> u32 { u32::from_le_bytes(self.0[36..36 + 4].try_into().unwrap()) } } struct ApfsSuperblock<'a>(&'a [u8]); impl ApfsSuperblock<'_> { fn volname(&self) -> &[u8] { &self.0[704..704 + 128] } fn vol_uuid(&self) -> Uuid { Uuid::from_slice(&self.0[240..240 + 16]).unwrap() } fn volume_group_id(&self) -> Uuid { Uuid::from_slice(&self.0[1008..1008 + 16]).unwrap() } fn role(&self) -> u16 { u16::from_le_bytes(self.0[964..964 + 2].try_into().unwrap()) } } const VOL_ROLE_SYSTEM: u16 = 1; fn pread(file: &mut T, pos: u64, target: &mut [u8]) -> io::Result<()> { file.seek(SeekFrom::Start(pos))?; file.read_exact(target) } // should probably fix xids here fn lookup(_disk: &mut File, cur_node: &BTreeNodePhys, key: u64) -> Option { if cur_node.level() != 0 { unimplemented!(); } if cur_node.flags() & BTreeNodePhys::FIXED_KV_SIZE != 0 { let toc_off = cur_node.table_space().off() as usize + BTreeNodePhys::SIZE; let key_start = toc_off + cur_node.table_space().len() as usize; let val_end = cur_node.0.len() - if cur_node.flags() & BTreeNodePhys::ROOT == 0 { 0 } else { BTreeInfo::SIZE }; for i in 0..cur_node.nkeys() as usize { let entry = KVOff(&cur_node.0[(toc_off + i * KVOff::SIZE)..]); let key_off = entry.k() as usize + key_start; let map_key = OmapKey(&cur_node.0[key_off..]); if map_key.oid() == key { let val_off = val_end - entry.v() as usize; let val = OmapVal(&cur_node.0[val_off..]); return Some(val.paddr()); } } None } else { unimplemented!(); } } fn trim_zeroes(s: &[u8]) -> &[u8] { for i in 0..s.len() { if s[i] == 0 { return &s[..i]; } } s } fn scan_volume(disk: &mut File) -> io::Result>> { let mut sb = NxSuperblock::new(); disk.read_exact(sb.get_buf())?; if sb.magic() != NxSuperblock::MAGIC { return Ok(HashMap::new()); } let block_size = sb.block_size() as u64; for i in 0..sb.xp_desc_blocks() { let mut sbc = NxSuperblock::new(); pread( disk, (sb.xp_desc_base() + i as u64) * block_size, sbc.get_buf(), )?; if sbc.magic() == NxSuperblock::MAGIC { if sbc.xid() > sb.xid() { sb = sbc; } } } let mut omap_bytes = vec![0; OmapPhys::SIZE]; pread(disk, sb.omap_oid() * block_size, &mut omap_bytes)?; let omap = OmapPhys(&omap_bytes); let mut node_bytes = vec![0; sb.block_size() as usize]; pread(disk, omap.tree_oid() * block_size, &mut node_bytes)?; let node = BTreeNodePhys(&node_bytes); let mut vgs_found = HashMap::>::new(); for i in 0..NxSuperblock::MAX_FILE_SYSTEMS { let fs_id = sb.fs_oid(i); if fs_id == 0 { continue; } let vsb = lookup(disk, &node, fs_id); let mut asb_bytes = vec![0; sb.block_size() as usize]; if vsb.is_none() { continue; } pread(disk, vsb.unwrap() * sb.block_size() as u64, &mut asb_bytes)?; let asb = ApfsSuperblock(&asb_bytes); if asb.volume_group_id().is_nil() { continue; } if let Ok(name) = std::str::from_utf8(trim_zeroes(asb.volname())) { vgs_found .entry(asb.volume_group_id()) .or_default() .push(Volume { name: name.to_owned(), is_system: asb.role() == VOL_ROLE_SYSTEM, }); } } Ok(vgs_found) } #[derive(Debug)] pub struct Volume { pub name: String, pub is_system: bool, } #[derive(Debug)] pub struct BootCandidate { pub part_uuid: Uuid, pub vg_uuid: Uuid, pub volumes: Vec, } fn swap_uuid(u: &Uuid) -> Uuid { let (a, b, c, d) = u.as_fields(); Uuid::from_fields(a.swap_bytes(), b.swap_bytes(), c.swap_bytes(), d) } #[derive(Debug)] pub enum Error { Parse, SectionTooBig, ApplyError(std::io::Error), OutOfRange, Ambiguous, NvramReadError(std::io::Error), DiskReadError(std::io::Error), VolumeNotFound, } impl From for Error { fn from(e: apple_nvram::Error) -> Self { match e { apple_nvram::Error::ParseError => Error::Parse, apple_nvram::Error::SectionTooBig => Error::SectionTooBig, apple_nvram::Error::ApplyError(e) => Error::ApplyError(e), } } } type Result = std::result::Result; pub fn get_boot_candidates() -> Result> { let disk = GptConfig::new() .writable(false) .logical_block_size(LogicalBlockSize::Lb4096) .open("/dev/nvme0n1") .map_err(Error::DiskReadError)?; let mut cands = Vec::new(); for (i, v) in disk.partitions() { if v.part_type_guid.guid != "7C3457EF-0000-11AA-AA11-00306543ECAC" { continue; } let mut part = File::open(format!("/dev/nvme0n1p{i}")).map_err(Error::DiskReadError)?; for (vg_uuid, volumes) in scan_volume(&mut part).unwrap_or_default() { cands.push(BootCandidate { vg_uuid, volumes, part_uuid: swap_uuid(&v.part_guid), }); } } return Ok(cands); } const ALT_BOOT_VAR: &'static [u8] = b"alt-boot-volume"; pub fn get_boot_volume(device: &str, next: bool) -> Result { let mut file = OpenOptions::new() .read(true) .write(true) .open(device) .map_err(Error::NvramReadError)?; let mut data = Vec::new(); file.read_to_end(&mut data).map_err(Error::NvramReadError)?; let mut nv = nvram_parse(&data)?; let active = nv.active_part_mut(); let v; if next { v = active .get_variable(ALT_BOOT_VAR, VarType::System) .or(active.get_variable(b"boot-volume", VarType::System)) .ok_or(Error::Parse); } else { v = active .get_variable(b"boot-volume", VarType::System) .ok_or(Error::Parse); } let data = String::from_utf8(v?.value().deref().to_vec()).unwrap(); let [_, part_uuid, part_vg_uuid]: [&str; 3] = data.split(":").collect::>().try_into().unwrap(); Ok(BootCandidate { volumes: Vec::new(), part_uuid: Uuid::parse_str(part_uuid).unwrap(), vg_uuid: Uuid::parse_str(part_vg_uuid).unwrap(), }) } pub fn clear_next_boot(device: &str) -> Result { let mut file = OpenOptions::new() .read(true) .write(true) .open(device) .map_err(Error::ApplyError)?; let mut data = Vec::new(); file.read_to_end(&mut data).map_err(Error::ApplyError)?; let mut nv = nvram_parse(&data)?; nv.prepare_for_write(); if nv.active_part_mut().get_variable(ALT_BOOT_VAR, VarType::System).is_none() { return Ok(false); } nv.active_part_mut().remove_variable( ALT_BOOT_VAR, VarType::System, ); nv.apply(&mut file)?; Ok(true) } pub fn set_boot_volume(device: &str, cand: &BootCandidate, next: bool) -> Result<()> { let mut nvram_key: &[u8] = b"boot-volume".as_ref(); if next { nvram_key = ALT_BOOT_VAR.as_ref(); } let boot_str = format!( "EF57347C-0000-AA11-AA11-00306543ECAC:{}:{}", cand.part_uuid .hyphenated() .encode_upper(&mut Uuid::encode_buffer()), cand.vg_uuid .hyphenated() .encode_upper(&mut Uuid::encode_buffer()) ); let mut file = OpenOptions::new() .read(true) .write(true) .open(device) .map_err(Error::ApplyError)?; let mut data = Vec::new(); file.read_to_end(&mut data).map_err(Error::ApplyError)?; let mut nv = nvram_parse(&data)?; nv.prepare_for_write(); nv.active_part_mut().insert_variable( nvram_key, Cow::Owned(boot_str.into_bytes()), VarType::System, ); nv.apply(&mut file)?; Ok(()) } asahi-bless-0.4.2/src/main.rs000064400000000000000000000157661046102023000141220ustar 00000000000000// SPDX-License-Identifier: MIT #![allow(dead_code)] use asahi_bless::{get_boot_candidates, get_boot_volume, set_boot_volume, clear_next_boot, BootCandidate, Error, Volume}; use clap::Parser; use std::{ io::{stdin, stdout, Write}, num::IntErrorKind, process::ExitCode, }; #[cfg(target_os = "macos")] compile_error!("asahi-bless will only work on linux, if you are on macos, use system `bless` instead"); type Result = std::result::Result; #[derive(Parser)] #[command(version)] struct Args { #[arg( short, long, help = "Path to the nvram device." )] device: Option, #[arg( short, long, help = "Set boot volume for next boot only" )] next: bool, #[arg( short, long, conflicts_with_all = &["set_boot", "set_boot_macos"], help = "List boot volume candidates" )] list_volumes: bool, #[arg(long, value_name = "name_or_index", help = "Set boot volume by name or index")] set_boot: Option, #[arg( long, conflicts_with = "set_boot", help = "Set boot volume to macOS if unambiguous" )] set_boot_macos: bool, #[arg(name = "yes", short, long, help = "Do not ask for confirmation")] autoconfirm: bool, #[arg(long, help = "Get currently selected boot target. May be combined with --next to show the next boot target.")] get_boot: bool, #[arg(long, help = "Clear the selected next boot target")] clear_next: bool, } fn error_to_string(e: Error) -> String { match e { Error::Ambiguous => "Unable to find the macos volume. Make sure you have exactly one volume that has a name staring with \"Macintosh\"".to_string(), Error::OutOfRange => "Index out of range".to_string(), Error::Parse => "Unable to parse current nvram contents".to_string(), Error::SectionTooBig => "Ran out of space on nvram".to_string(), Error::ApplyError(e) => format!("Failed to save new nvram contents, try running with sudo? Inner error: {:?}", e), Error::NvramReadError(e) => format!("Failed to read nvram contents, try running with sudo? Inner error: {:?}", e), Error::DiskReadError(e) => format!("Failed to collect boot candidates, try running with sudo? Inner error: {:?}", e), Error::VolumeNotFound => "Unable to find specified volume".to_string(), } } fn main() -> ExitCode { match real_main() { Ok(_) => ExitCode::SUCCESS, Err(e) => { eprintln!("Error: {}", error_to_string(e)); ExitCode::FAILURE } } } fn real_main() -> Result<()> { let args = Args::parse(); let device = match args.device { Some(ref dev) => dev, None => "/dev/mtd/by-name/nvram", }; if args.list_volumes { list_boot_volumes(&args, device)?; } else if args.get_boot { print_boot_target(&args, device)?; } else if args.clear_next { if clear_next_boot(device)? { println!("Cleared next boot target"); } else { println!("Next boot target was already empty"); } } else if let Some(spec) = &args.set_boot { let cands = get_boot_candidates()?; let lc_name = spec.to_lowercase(); for cand in &cands { if cand.volumes.iter().any(|n| n.name.to_lowercase() == lc_name) { set_boot_volume_by_ref(device, &cand, &args, false)?; return Ok(()); } } if let Ok(idx) = spec.parse::() { let cand = cands .into_iter() .nth(idx - 1) .ok_or(Error::OutOfRange)?; set_boot_volume_by_ref(device, &cand, &args, false)?; } else { return Err(Error::VolumeNotFound); } } else if args.set_boot_macos { let cands = get_boot_candidates()?; let macos_cands: Vec<_> = cands .iter() .filter(|c| { c.volumes .first() .map(|n| n.name.starts_with("Macintosh")) .unwrap_or(false) }) .collect(); if macos_cands.len() == 1 { set_boot_volume_by_ref(device, &macos_cands[0], &args, false)?; } else { return Err(Error::Ambiguous); } } else { interactive_main(&args, device)?; } Ok(()) } fn confirm() -> bool { print!("confirm? [y/N]: "); stdout().flush().unwrap(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); input.trim().to_lowercase() == "y" } fn get_vg_name(vg: &[Volume]) -> &str { for v in vg { if v.is_system { return &v.name; } } &vg[0].name } fn print_boot_target(args: &Args, device: &str) -> Result<()> { let cands = get_boot_candidates()?; let default_cand = get_boot_volume(device, args.next)?; for cand in cands { if (cand.part_uuid == default_cand.part_uuid) && (cand.vg_uuid == default_cand.vg_uuid) { println!("{}", get_vg_name(&cand.volumes)); return Ok(()); } } println!("No boot target set"); Ok(()) } fn list_boot_volumes(args: &Args, device: &str) -> Result> { let cands = get_boot_candidates()?; let default_cand = get_boot_volume(device, args.next)?; let mut is_default: &str; for (i, cand) in cands.iter().enumerate() { if (cand.part_uuid == default_cand.part_uuid) && (cand.vg_uuid == default_cand.vg_uuid) { is_default = "*"; } else { is_default = " "; } println!("{}{}) {}", is_default, i + 1, get_vg_name(&cand.volumes)); } Ok(cands) } fn set_boot_volume_by_ref( device: &str, cand: &BootCandidate, args: &Args, interactive: bool, ) -> Result<()> { if !interactive { let as_what = if !args.next { "default boot target" } else { "boot target for next boot only" }; println!("Will set volume {} as the {}", get_vg_name(&cand.volumes), as_what); } if !args.autoconfirm && !interactive { if !confirm() { return Ok(()); } } set_boot_volume(device, cand, args.next)?; Ok(()) } fn interactive_main(args: &Args, device: &str) -> Result<()> { let cands = list_boot_volumes(args, device)?; println!("\nEnter a number to select a boot volume:"); let mut input = String::new(); let index = loop { print!("==> "); stdout().flush().unwrap(); input.clear(); stdin().read_line(&mut input).unwrap(); match input.trim().parse::() { Ok(i @ 1..) if i <= cands.len() => break i - 1, Err(e) if e.kind() == &IntErrorKind::Empty => { eprintln!("No volume selected. Leaving unchanged."); return Ok(()); }, _ => eprintln!("Enter a number from 1 to {}", cands.len()), } }; set_boot_volume_by_ref(device, &cands[index], args, true) }