Skip to main content

vmgstool/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![forbid(unsafe_code)]
5#![expect(missing_docs)]
6
7#[cfg(all(not(test), feature = "encryption"))]
8crypto::ensure_single_backend!();
9
10// The version in this crate's Cargo.toml file should be updated using the
11// semver standard when changes are made, which triggers CI to automatically
12// publish a new version (without the test_helpers feature).
13//
14// CI also builds a set of separate vmgstool-dev binaries with the test_helpers
15// feature enabled for use in integration tests.
16
17mod storage_backend;
18#[cfg(feature = "test_helpers")]
19mod test;
20mod uefi_nvram;
21mod vmgs_json;
22
23#[cfg(feature = "test_helpers")]
24use crate::test::TestOperation;
25use anyhow::Result;
26use clap::Args;
27use clap::Parser;
28use clap::Subcommand;
29use disk_backend::Disk;
30use disk_vhd1::Vhd1Disk;
31use fs_err::File;
32use pal_async::DefaultPool;
33use std::io::prelude::*;
34use std::path::Path;
35use std::path::PathBuf;
36use thiserror::Error;
37use uefi_nvram::UefiNvramOperation;
38use vmgs::Error as VmgsError;
39use vmgs::GspType;
40use vmgs::Vmgs;
41use vmgs::vmgs_helpers::get_active_header;
42use vmgs::vmgs_helpers::read_headers;
43use vmgs::vmgs_helpers::validate_header;
44use vmgs_format::EncryptionAlgorithm;
45use vmgs_format::FileId;
46use vmgs_format::VMGS_BYTES_PER_BLOCK;
47use vmgs_format::VMGS_DEFAULT_CAPACITY;
48use vmgs_format::VMGS_ENCRYPTION_KEY_SIZE;
49use vmgs_format::VmgsHeader;
50
51const ONE_MEGA_BYTE: u64 = 1024 * 1024;
52const ONE_GIGA_BYTE: u64 = ONE_MEGA_BYTE * 1024;
53const VHD_DISK_FOOTER_PACKED_SIZE: u64 = 512;
54
55#[derive(Debug, Error)]
56pub(crate) enum Error {
57    #[error("VMGS file IO")]
58    VmgsFile(#[source] std::io::Error),
59    #[error("VHD file error")]
60    Vhd1(#[source] disk_vhd1::OpenError),
61    #[error("Invalid disk")]
62    InvalidDisk(#[source] disk_backend::InvalidDisk),
63    #[error("Internal VMGS error")]
64    Vmgs(#[from] VmgsError),
65    #[error("VMGS file already exists")]
66    FileExists,
67    #[cfg(feature = "encryption")]
68    #[error("Adding encryption key")]
69    EncryptionKey(#[source] VmgsError),
70    #[error("Data file / STDOUT IO")]
71    DataFile(#[source] std::io::Error),
72    #[error("The VMGS file has zero size")]
73    ZeroSize,
74    #[error("Invalid VMGS file size: {0} {1}")]
75    InvalidVmgsFileSize(u64, String),
76    #[error("Key file IO")]
77    KeyFile(#[source] std::io::Error),
78    #[error("Key must be {0} bytes long, is {1} bytes instead")]
79    InvalidKeySize(u64, u64),
80    #[error("File is not encrypted")]
81    NotEncrypted,
82    #[error("File must be decrypted to perform this operation but no key was provided")]
83    EncryptedNoKey,
84    #[error("VmgsStorageBackend")]
85    VmgsStorageBackend(#[from] storage_backend::EncryptionNotSupported),
86    #[error("NVRAM storage")]
87    NvramStorage(#[from] uefi_nvram_storage::NvramStorageError),
88    #[error("UEFI NVRAM variable parsing")]
89    NvramParsing(#[from] uefi_nvram_specvars::ParseError),
90    #[error("NVRAM entry not found: {0}")]
91    MissingNvramEntry(ucs2::Ucs2LeVec),
92    #[error("GUID parsing")]
93    Guid(#[from] guid::ParseError),
94    #[error("JSON parsing")]
95    SerdeJson(#[from] serde_json::Error),
96    #[error("Bad JSON contents: {0}")]
97    Json(String),
98    #[error("File ID {0:?} already exists. Use `--allow-overwrite` to ignore.")]
99    FileIdExists(FileId),
100    #[error("VMGS file is encrypted using GspById")]
101    GspByIdEncryption,
102    #[error("VMGS file is encrypted using an unknown encryption scheme")]
103    GspUnknown,
104    #[error("VMGS file is using an unknown encryption algorithm")]
105    EncryptionUnknown,
106    #[error("Unable to parse IGVM file")]
107    IgvmFile(#[source] anyhow::Error),
108}
109
110/// Automation requires certain exit codes to be guaranteed
111/// main matches Error enum to ExitCode
112///
113/// - query-encryption must return NotEncrypted if file is not encrypted,
114///   GspById if the file contains a VMID, and GspUnknown if neither
115///   a VMID nor a key protector are present. Success indicates GspKey.
116/// - dump-headers must return Empty when the file is blank.
117/// - query-size must return NotFound when the file id is uninitialized.
118/// - Error is returned for all other errors.
119#[derive(Debug, Clone, Copy)]
120#[repr(i32)]
121enum ExitCode {
122    Error = 1,
123    NotEncrypted = 2,
124    Empty = 3,
125    NotFound = 4,
126    V1Format = 5,
127    GspById = 6,
128    GspUnknown = 7,
129}
130
131#[derive(Debug, Clone, Copy, clap::ValueEnum)]
132#[repr(u32)]
133pub(crate) enum ResourceCode {
134    #[value(name = "NONCONFIDENTIAL")]
135    NonConfidential = 13510,
136    #[value(name = "SNP")]
137    Snp = 13515,
138    #[value(name = "SNP_NO_HCL")]
139    SnpNoHcl = 13516,
140    #[value(name = "TDX")]
141    Tdx = 13520,
142    #[value(name = "TDX_NO_HCL")]
143    TdxNoHcl = 13521,
144}
145
146#[derive(Args)]
147struct FilePathArg {
148    /// VMGS file path
149    #[clap(short = 'f', long, alias = "filepath")]
150    file_path: PathBuf,
151}
152
153#[derive(Args)]
154struct KeyPathArg {
155    /// Encryption key file path. The file must contain a key that is 32 bytes long.
156    #[clap(short = 'k', long, alias = "keypath")]
157    key_path: Option<PathBuf>,
158}
159
160#[derive(Args)]
161struct FileIdArg {
162    /// VMGS File ID
163    #[clap(short = 'i', long, alias = "fileid", value_parser = parse_file_id)]
164    file_id: FileId,
165}
166
167#[derive(Parser)]
168#[clap(name = "vmgstool", about = "Tool to interact with VMGS files.")]
169#[clap(long_about = r#"Tool to interact with VMGS files.
170
171Unless otherwise noted, everything written to STDOUT and STDERR is unstable
172and subject to change. Automated consumers of VmgsTool should generally parse
173only the exit code. In some cases, the STDOUT of specific subcommands may be
174made stable (ex: query-size). STDERR is for human-readable debug messages and
175is never stable."#)]
176struct CliArgs {
177    /// Print trace level traces from all crates, rather than just info level
178    /// traces from the vmgstool crate.
179    #[clap(short = 'v', long)]
180    verbose: bool,
181
182    #[clap(subcommand)]
183    opt: Options,
184}
185
186#[derive(Subcommand)]
187enum Options {
188    /// Create and initialize `filepath` as a VMGS file of size `filesize`.
189    ///
190    /// `keypath` and `encryptionalgorithm` must both be specified if encrypted
191    /// guest state is required.
192    Create {
193        #[command(flatten)]
194        file_path: FilePathArg,
195        /// VMGS file size, default = 4194816 (~4MB)
196        #[clap(short = 's', long, alias = "filesize")]
197        file_size: Option<u64>,
198        /// Encryption key file path. The file must contain a key that is 32 bytes long.
199        ///
200        /// `encryptionalgorithm` must also be specified when using this flag.
201        #[clap(
202            short = 'k',
203            long,
204            alias = "keypath",
205            requires = "encryption_algorithm"
206        )]
207        key_path: Option<PathBuf>,
208        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
209        ///
210        /// `keypath` must also be specified when using this flag.
211        #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
212        encryption_algorithm: Option<EncryptionAlgorithm>,
213        /// Force creation of the VMGS file. If the VMGS filepath already exists,
214        /// this flag allows an existing file to be overwritten.
215        #[clap(long, alias = "forcecreate")]
216        force_create: bool,
217    },
218    /// Write data into the specified file ID of the VMGS file.
219    ///
220    /// The proper key file must be specified to write encrypted data.
221    Write {
222        #[command(flatten)]
223        file_path: FilePathArg,
224        /// Data file path to read
225        #[clap(short = 'd', long, alias = "datapath")]
226        data_path: PathBuf,
227        #[command(flatten)]
228        file_id: FileIdArg,
229        #[command(flatten)]
230        key_path: KeyPathArg,
231        /// Overwrite the VMGS data at `fileid`, even if it already exists with nonzero size
232        #[clap(long, alias = "allowoverwrite")]
233        allow_overwrite: bool,
234    },
235    /// Dump/read data from the specified file ID of the VMGS file.
236    ///
237    /// The proper key file must be specified to read encrypted data. If the data
238    /// is encrypted and no key is specified, the data will be dumped without
239    /// decrypting.
240    Dump {
241        #[command(flatten)]
242        file_path: FilePathArg,
243        /// Data file path to write
244        #[clap(short = 'd', long, alias = "datapath")]
245        data_path: Option<PathBuf>,
246        #[command(flatten)]
247        file_id: FileIdArg,
248        #[command(flatten)]
249        key_path: KeyPathArg,
250        /// When dumping to stdout, dump data as raw bytes instead of ASCII hex
251        #[clap(long, conflicts_with = "data_path")]
252        raw_stdout: bool,
253    },
254    /// Dump headers of the VMGS file at `filepath` to the console.
255    DumpHeaders {
256        #[command(flatten)]
257        file_path: FilePathArg,
258    },
259    /// Get the size of the specified `fileid` within the VMGS file
260    ///
261    /// The STDOUT of this subcommand is stable and contains only the file size.
262    QuerySize {
263        #[command(flatten)]
264        file_path: FilePathArg,
265        #[command(flatten)]
266        file_id: FileIdArg,
267    },
268    /// Replace the current encryption key with a new provided key
269    ///
270    /// Both key files must contain a key that is 32 bytes long.
271    UpdateKey {
272        #[command(flatten)]
273        file_path: FilePathArg,
274        /// Current encryption key file path.
275        #[clap(short = 'k', long, alias = "keypath")]
276        key_path: PathBuf,
277        /// New encryption key file path.
278        #[clap(short = 'n', long, alias = "newkeypath")]
279        new_key_path: PathBuf,
280        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
281        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
282        encryption_algorithm: EncryptionAlgorithm,
283    },
284    /// Encrypt an existing VMGS file
285    Encrypt {
286        #[command(flatten)]
287        file_path: FilePathArg,
288        /// Encryption key file path. The file must contain a key that is 32 bytes long.
289        #[clap(short = 'k', long, alias = "keypath")]
290        key_path: PathBuf,
291        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
292        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
293        encryption_algorithm: EncryptionAlgorithm,
294    },
295    /// Query whether a VMGS file is encrypted
296    QueryEncryption {
297        #[command(flatten)]
298        file_path: FilePathArg,
299    },
300    /// Move data to a new file id
301    Move {
302        #[command(flatten)]
303        file_path: FilePathArg,
304        /// Source VMGS File ID
305        #[clap(long, alias = "src", value_parser = parse_file_id)]
306        src_file_id: FileId,
307        /// Destination VMGS File ID
308        #[clap(long, alias = "dst", value_parser = parse_file_id)]
309        dst_file_id: FileId,
310        #[command(flatten)]
311        key_path: KeyPathArg,
312        /// Overwrite the VMGS data at `dst_file_id`, even if it already exists
313        #[clap(long, alias = "allowoverwrite")]
314        allow_overwrite: bool,
315    },
316    /// Delete a file id
317    Delete {
318        #[command(flatten)]
319        file_path: FilePathArg,
320        #[command(flatten)]
321        file_id: FileIdArg,
322    },
323    /// Dump information about all the File IDs allocated in the VMGS file.
324    DumpFileTable {
325        #[command(flatten)]
326        file_path: FilePathArg,
327        #[command(flatten)]
328        key_path: KeyPathArg,
329    },
330    /// UEFI NVRAM operations
331    UefiNvram {
332        #[clap(subcommand)]
333        operation: UefiNvramOperation,
334    },
335    /// Copy the IGVM file from a DLL into file ID 8 of the VMGS file.
336    CopyIgvmfile {
337        #[command(flatten)]
338        file_path: FilePathArg,
339        /// DLL file path to read
340        #[clap(short = 'd', long, alias = "datapath")]
341        data_path: PathBuf,
342        /// Overwrite the VMGS data at file ID 8 (FileId::GUEST_FIRMWARE), even if it already exists with nonzero size
343        #[clap(long, alias = "allowoverwrite")]
344        allow_overwrite: bool,
345        /// Resource code
346        #[clap(short = 'r', long, alias = "resourcecode", value_enum)]
347        resource_code: ResourceCode,
348    },
349    #[cfg(feature = "test_helpers")]
350    /// Create a test VMGS file
351    Test {
352        #[clap(subcommand)]
353        operation: TestOperation,
354    },
355}
356
357fn parse_file_id(file_id: &str) -> Result<FileId, std::num::ParseIntError> {
358    Ok(match file_id {
359        "FILE_TABLE" => FileId::FILE_TABLE,
360        "BIOS_NVRAM" => FileId::BIOS_NVRAM,
361        "TPM_PPI" => FileId::TPM_PPI,
362        "TPM_NVRAM" => FileId::TPM_NVRAM,
363        "RTC_SKEW" => FileId::RTC_SKEW,
364        "ATTEST" => FileId::ATTEST,
365        "KEY_PROTECTOR" => FileId::KEY_PROTECTOR,
366        "VM_UNIQUE_ID" => FileId::VM_UNIQUE_ID,
367        "GUEST_FIRMWARE" => FileId::GUEST_FIRMWARE,
368        "CUSTOM_UEFI" => FileId::CUSTOM_UEFI,
369        "GUEST_WATCHDOG" => FileId::GUEST_WATCHDOG,
370        "HW_KEY_PROTECTOR" => FileId::HW_KEY_PROTECTOR,
371        "GUEST_SECRET_KEY" => FileId::GUEST_SECRET_KEY,
372        "HIBERNATION_TOKEN" => FileId::HIBERNATION_TOKEN,
373        "PLATFORM_SEED" => FileId::PLATFORM_SEED,
374        "PROVENANCE_DOC" => FileId::PROVENANCE_DOC,
375        "TPM_NVRAM_BACKUP" => FileId::TPM_NVRAM_BACKUP,
376        "EXTENDED_FILE_TABLE" => FileId::EXTENDED_FILE_TABLE,
377        "TPM_185_NVRAM" => FileId::TPM_185_NVRAM,
378        v => FileId(v.parse::<u32>()?),
379    })
380}
381
382fn parse_encryption_algorithm(algorithm: &str) -> Result<EncryptionAlgorithm, &'static str> {
383    match algorithm {
384        "AES_GCM" => Ok(EncryptionAlgorithm::AES_GCM),
385        _ => Err("Encryption algorithm not supported"),
386    }
387}
388
389fn extract_version(ver: u32) -> String {
390    let major = (ver >> 16) & 0xFF;
391    let minor = ver & 0xFF;
392    format!("{major}.{minor}")
393}
394
395fn parse_legacy_args() -> Vec<String> {
396    use std::env;
397    let mut args: Vec<String> = env::args().collect();
398    if let Some(cmd) = args.get(1) {
399        let cmd_lower = cmd.to_ascii_lowercase();
400        let new_cmd = match &cmd_lower[..] {
401            "-c" | "-create" => Some("create"),
402            "-w" | "-write" => Some("write"),
403            "-r" | "-dump" => Some("dump"),
404            "-rh" | "-dumpheaders" => Some("dump-headers"),
405            "-qs" | "-querysize" => Some("query-size"),
406            "-uk" | "-updatekey" => Some("update-key"),
407            "-e" | "-encrypt" => Some("encrypt"),
408            _ => None,
409        };
410
411        if let Some(new_cmd) = new_cmd {
412            // The tracing subscriber has not been initialized yet.
413            eprintln!("Warning: Using legacy arguments. Please migrate to the new syntax.");
414            args[1] = new_cmd.to_string();
415
416            let mut index = 2;
417            while let Some(arg) = args.get(index) {
418                let arg_lower = arg.to_ascii_lowercase();
419                if let Some(new_arg) = match &arg_lower[..] {
420                    "-f" | "-filepath" => Some("--file-path"),
421                    "-s" | "-filesize" => Some("--file-size"),
422                    "-i" | "-fileid" => Some("--file-id"),
423                    "-d" | "-datapath" => Some("--data-path"),
424                    "-ow" | "-allowoverwrite" => Some("--allow-overwrite"),
425                    "-k" | "-keypath" => Some("--key-path"),
426                    "-n" | "-newkeypath" => Some("--new-key-path"),
427                    "-ea" | "-encryptionalgorithm" => Some("--encryption-algorithm"),
428                    "-fc" | "-forcecreate" => Some("--force-create"),
429                    _ => None,
430                } {
431                    args[index] = new_arg.to_string();
432                }
433                index += 1;
434            }
435        }
436    }
437    args
438}
439
440/// Initialize tracing
441pub fn init_tracing(verbose: bool) {
442    use tracing::level_filters::LevelFilter;
443    use tracing_subscriber::filter::Targets;
444    use tracing_subscriber::layer::SubscriberExt;
445    use tracing_subscriber::util::SubscriberInitExt;
446
447    let targets = if verbose {
448        Targets::new().with_default(LevelFilter::TRACE)
449    } else {
450        Targets::new()
451            .with_default(LevelFilter::OFF)
452            .with_target("vmgstool", LevelFilter::INFO)
453    };
454
455    tracing_subscriber::fmt()
456        .with_ansi(false)
457        .log_internal_errors(true)
458        .with_writer(std::io::stderr)
459        .with_max_level(LevelFilter::TRACE)
460        .finish()
461        .with(targets)
462        .init();
463}
464
465fn main() {
466    DefaultPool::run_with(async |_| match do_main().await {
467        Ok(_) => tracing::info!("The operation completed successfully."),
468        Err(e) => {
469            let exit_code = match e {
470                Error::NotEncrypted => ExitCode::NotEncrypted,
471                Error::GspByIdEncryption => ExitCode::GspById,
472                Error::GspUnknown => ExitCode::GspUnknown,
473                Error::Vmgs(VmgsError::EmptyFile) | Error::ZeroSize => ExitCode::Empty,
474                Error::Vmgs(VmgsError::FileInfoNotAllocated(_)) => ExitCode::NotFound,
475                Error::Vmgs(VmgsError::V1Format) => ExitCode::V1Format,
476                _ => ExitCode::Error,
477            };
478
479            match e {
480                // all relevant info is already logged in `vmgs_file_query_encryption`
481                Error::NotEncrypted | Error::GspByIdEncryption | Error::GspUnknown => {}
482                // these are not necessarily errors, so just log the inner value as info
483                Error::Vmgs(inner)
484                    if matches!(
485                        inner,
486                        VmgsError::EmptyFile | VmgsError::FileInfoNotAllocated(_)
487                    ) =>
488                {
489                    tracing::info!("{}", inner)
490                }
491                // anything else is unexpected and should be logged as error
492                e => {
493                    tracing::error!("{}", e);
494                    let mut error_source = std::error::Error::source(&e);
495                    while let Some(e2) = error_source {
496                        tracing::error!("{}", e2);
497                        error_source = e2.source();
498                    }
499                }
500            };
501
502            tracing::info!(
503                "The operation completed with exit code: {} ({:?})",
504                exit_code as i32,
505                exit_code
506            );
507
508            std::process::exit(exit_code as i32);
509        }
510    })
511}
512
513async fn do_main() -> Result<(), Error> {
514    let args = CliArgs::parse_from(parse_legacy_args());
515    init_tracing(args.verbose);
516
517    match args.opt {
518        Options::Create {
519            file_path,
520            file_size,
521            key_path,
522            encryption_algorithm,
523            force_create,
524        } => {
525            let encryption_alg_key = encryption_algorithm.map(|x| (x, key_path.unwrap()));
526            vmgs_file_create(
527                file_path.file_path,
528                file_size,
529                force_create,
530                encryption_alg_key,
531            )
532            .await
533            .map(|_| ())
534        }
535        Options::Dump {
536            file_path,
537            data_path,
538            file_id,
539            key_path,
540            raw_stdout,
541        } => {
542            vmgs_file_read(
543                file_path.file_path,
544                data_path,
545                file_id.file_id,
546                key_path.key_path,
547                raw_stdout,
548            )
549            .await
550        }
551        Options::Write {
552            file_path,
553            data_path,
554            file_id,
555            key_path,
556            allow_overwrite,
557        } => {
558            vmgs_file_write(
559                file_path.file_path,
560                data_path,
561                file_id.file_id,
562                key_path.key_path,
563                allow_overwrite,
564            )
565            .await
566        }
567        Options::DumpHeaders { file_path } => vmgs_file_dump_headers(file_path.file_path).await,
568        Options::QuerySize { file_path, file_id } => {
569            vmgs_file_query_file_size(file_path.file_path, file_id.file_id)
570                .await
571                .map(|_| ())
572        }
573        Options::UpdateKey {
574            file_path,
575            key_path,
576            new_key_path,
577            encryption_algorithm,
578        } => {
579            vmgs_file_update_key(
580                file_path.file_path,
581                encryption_algorithm,
582                Some(key_path),
583                new_key_path,
584            )
585            .await
586        }
587        Options::Encrypt {
588            file_path,
589            key_path,
590            encryption_algorithm,
591        } => {
592            vmgs_file_update_key(
593                file_path.file_path,
594                encryption_algorithm,
595                None as Option<PathBuf>,
596                key_path,
597            )
598            .await
599        }
600        Options::QueryEncryption { file_path } => {
601            vmgs_file_query_encryption(file_path.file_path).await
602        }
603        Options::Move {
604            file_path,
605            src_file_id,
606            dst_file_id,
607            key_path,
608            allow_overwrite,
609        } => {
610            vmgs_file_move(
611                file_path.file_path,
612                src_file_id,
613                dst_file_id,
614                key_path.key_path,
615                allow_overwrite,
616            )
617            .await
618        }
619        Options::Delete { file_path, file_id } => {
620            vmgs_file_delete(file_path.file_path, file_id.file_id).await
621        }
622        Options::DumpFileTable {
623            file_path,
624            key_path,
625        } => vmgs_file_dump_file_table(file_path.file_path, key_path.key_path).await,
626        Options::UefiNvram { operation } => uefi_nvram::do_command(operation).await,
627        Options::CopyIgvmfile {
628            file_path,
629            data_path,
630            allow_overwrite,
631            resource_code,
632        } => {
633            vmgs_file_copy_igvmfile(
634                file_path.file_path,
635                data_path,
636                allow_overwrite,
637                resource_code,
638            )
639            .await
640        }
641        #[cfg(feature = "test_helpers")]
642        Options::Test { operation } => test::do_command(operation).await,
643    }
644}
645
646async fn vmgs_file_update_key(
647    file_path: impl AsRef<Path>,
648    encryption_alg: EncryptionAlgorithm,
649    key_path: Option<impl AsRef<Path>>,
650    new_key_path: impl AsRef<Path>,
651) -> Result<(), Error> {
652    let new_encryption_key = read_key_path(new_key_path)?;
653    let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
654
655    vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key.as_ref()).await
656}
657
658#[cfg_attr(not(feature = "encryption"), expect(unused_variables))]
659async fn vmgs_update_key(
660    vmgs: &mut Vmgs,
661    encryption_alg: EncryptionAlgorithm,
662    new_encryption_key: &[u8],
663) -> Result<(), Error> {
664    #[cfg(not(feature = "encryption"))]
665    unreachable!("encryption requires the encryption feature");
666    #[cfg(feature = "encryption")]
667    {
668        tracing::info!("Updating encryption key");
669        vmgs.update_encryption_key(new_encryption_key, encryption_alg)
670            .await
671            .map_err(Error::EncryptionKey)?;
672
673        Ok(())
674    }
675}
676
677async fn vmgs_file_create(
678    path: impl AsRef<Path>,
679    file_size: Option<u64>,
680    force_create: bool,
681    encryption_alg_key: Option<(EncryptionAlgorithm, impl AsRef<Path>)>,
682) -> Result<Vmgs, Error> {
683    let disk = vhdfiledisk_create(path, file_size, force_create)?;
684
685    let encryption_key = encryption_alg_key
686        .as_ref()
687        .map(|(_, key_path)| read_key_path(key_path))
688        .transpose()?;
689    let encryption_alg_key =
690        encryption_alg_key.map(|(alg, _)| (alg, encryption_key.as_ref().unwrap()));
691
692    let vmgs = vmgs_create(disk, encryption_alg_key).await?;
693
694    Ok(vmgs)
695}
696
697fn vhdfiledisk_create(
698    path: impl AsRef<Path>,
699    req_file_size: Option<u64>,
700    force_create: bool,
701) -> Result<Disk, Error> {
702    const MIN_VMGS_FILE_SIZE: u64 = 4 * VMGS_BYTES_PER_BLOCK as u64;
703    const SECTOR_SIZE: u64 = 512;
704
705    // validate the VHD size
706    let file_size = req_file_size.unwrap_or(VMGS_DEFAULT_CAPACITY);
707    if file_size < MIN_VMGS_FILE_SIZE || !file_size.is_multiple_of(SECTOR_SIZE) {
708        return Err(Error::InvalidVmgsFileSize(
709            file_size,
710            format!(
711                "Must be a multiple of {} and at least {}",
712                SECTOR_SIZE, MIN_VMGS_FILE_SIZE
713            ),
714        ));
715    }
716
717    // check if the file already exists so we know whether to try to preserve
718    // the size and footer later
719    let exists = Path::new(path.as_ref()).exists();
720
721    // open/create the file
722    tracing::info!("Creating file: {}", path.as_ref().display());
723    let file = match fs_err::OpenOptions::new()
724        .read(true)
725        .write(true)
726        .create(true)
727        .create_new(!force_create)
728        .open(path.as_ref())
729    {
730        Ok(file) => file,
731        Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
732            return Err(Error::FileExists);
733        }
734        Err(err) => return Err(Error::VmgsFile(err)),
735    };
736
737    // determine if a resize is necessary
738    let existing_size = exists
739        .then(|| {
740            Ok(file
741                .metadata()?
742                .len()
743                .checked_sub(VHD_DISK_FOOTER_PACKED_SIZE))
744        })
745        .transpose()
746        .map_err(Error::VmgsFile)?
747        .flatten();
748    let needs_resize =
749        !exists || existing_size.is_none_or(|existing_size| file_size != existing_size);
750
751    // resize the file if necessary
752    let default_label = if file_size == VMGS_DEFAULT_CAPACITY {
753        " (default)"
754    } else {
755        ""
756    };
757    if needs_resize {
758        tracing::info!(
759            "Setting file size to {}{}{}",
760            file_size,
761            default_label,
762            existing_size
763                .map(|s| format!(" (previous size: {s})"))
764                .unwrap_or_default(),
765        );
766        file.set_len(file_size).map_err(Error::VmgsFile)?;
767    } else {
768        tracing::info!(
769            "File size is already {}{}, skipping resize",
770            file_size,
771            default_label
772        );
773    }
774
775    // attempt to open the VHD file if it already existed
776    let disk = if needs_resize {
777        None
778    } else {
779        Vhd1Disk::open_fixed(file.try_clone().map_err(Error::VmgsFile)?.into(), false)
780            .inspect_err(|e| tracing::info!("No valid VHD header found in existing file: {e:#}"))
781            .ok()
782    };
783
784    // format the VHD if necessary
785    let disk = match disk {
786        Some(disk) => {
787            tracing::info!("Valid VHD footer already exists, skipping VHD format");
788            disk
789        }
790        None => {
791            tracing::info!("Formatting VHD");
792            Vhd1Disk::make_fixed(file.file()).map_err(Error::Vhd1)?;
793            Vhd1Disk::open_fixed(file.into(), false).map_err(Error::Vhd1)?
794        }
795    };
796
797    Disk::new(disk).map_err(Error::InvalidDisk)
798}
799
800#[cfg_attr(
801    not(feature = "encryption"),
802    expect(unused_mut),
803    expect(unused_variables)
804)]
805async fn vmgs_create(
806    disk: Disk,
807    encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
808) -> Result<Vmgs, Error> {
809    tracing::info!("Formatting VMGS");
810    let mut vmgs = Vmgs::format_new(disk, None).await?;
811
812    if let Some((algorithm, encryption_key)) = encryption_alg_key {
813        tracing::info!("Adding encryption key");
814        #[cfg(feature = "encryption")]
815        vmgs.update_encryption_key(encryption_key, algorithm)
816            .await
817            .map_err(Error::EncryptionKey)?;
818        #[cfg(not(feature = "encryption"))]
819        unreachable!("Encryption requires the encryption feature");
820    }
821
822    Ok(vmgs)
823}
824
825async fn vmgs_file_write(
826    file_path: impl AsRef<Path>,
827    data_path: impl AsRef<Path>,
828    file_id: FileId,
829    key_path: Option<impl AsRef<Path>>,
830    allow_overwrite: bool,
831) -> Result<(), Error> {
832    tracing::info!(
833        "Opening source (raw data file): {}",
834        data_path.as_ref().display()
835    );
836
837    let mut file = File::open(data_path.as_ref()).map_err(Error::DataFile)?;
838    let mut buf = Vec::new();
839
840    file.read_to_end(&mut buf).map_err(Error::DataFile)?;
841
842    tracing::info!("Read {} bytes", buf.len());
843
844    let encrypt = key_path.is_some();
845    let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteIgnore).await?;
846
847    vmgs_write(&mut vmgs, file_id, &buf, encrypt, allow_overwrite).await?;
848
849    Ok(())
850}
851
852async fn vmgs_write(
853    vmgs: &mut Vmgs,
854    file_id: FileId,
855    data: &[u8],
856    encrypt: bool,
857    allow_overwrite: bool,
858) -> Result<(), Error> {
859    tracing::info!("Writing {}", file_id);
860
861    if let Ok(info) = vmgs.get_file_info(file_id) {
862        if !allow_overwrite && info.valid_bytes > 0 {
863            return Err(Error::FileIdExists(file_id));
864        }
865        if !encrypt && info.encrypted {
866            tracing::warn!("Overwriting encrypted file with plaintext data")
867        }
868    }
869
870    if encrypt {
871        #[cfg(feature = "encryption")]
872        vmgs.write_file_encrypted(file_id, data).await?;
873        #[cfg(not(feature = "encryption"))]
874        unreachable!("Encryption requires the encryption feature");
875    } else {
876        vmgs.write_file_allow_overwrite_encrypted(file_id, data)
877            .await?;
878    }
879
880    Ok(())
881}
882
883/// Get data from VMGS file, and write to `data_path`.
884async fn vmgs_file_read(
885    file_path: impl AsRef<Path>,
886    data_path: Option<impl AsRef<Path>>,
887    file_id: FileId,
888    key_path: Option<impl AsRef<Path>>,
889    raw_stdout: bool,
890) -> Result<(), Error> {
891    let decrypt = key_path.is_some();
892    let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
893
894    let file_info = vmgs.get_file_info(file_id)?;
895    if !decrypt && file_info.encrypted {
896        tracing::warn!("Reading encrypted file without decrypting");
897    }
898
899    let buf = vmgs_read(&mut vmgs, file_id, decrypt).await?;
900
901    tracing::info!("Read {} bytes", buf.len());
902    if buf.len() != file_info.valid_bytes as usize {
903        tracing::warn!("Bytes read from VMGS doesn't match file info");
904    }
905
906    if let Some(path) = data_path {
907        tracing::info!("Writing contents to {}", path.as_ref().display());
908        let mut file = File::create(path.as_ref()).map_err(Error::DataFile)?;
909        file.write_all(&buf).map_err(Error::DataFile)?;
910    } else {
911        tracing::info!("Writing contents to stdout");
912        if raw_stdout {
913            let mut stdout = std::io::stdout();
914            stdout.write_all(&buf).map_err(Error::DataFile)?;
915        } else {
916            for c in buf.chunks(16) {
917                for b in c {
918                    print!("0x{:02x},", b);
919                }
920                println!(
921                    "{:missing$}// {}",
922                    ' ',
923                    c.iter()
924                        .map(|c| if c.is_ascii_graphic() {
925                            *c as char
926                        } else {
927                            '.'
928                        })
929                        .collect::<String>(),
930                    missing = (16 - c.len()) * 5 + 1
931                );
932            }
933        }
934    }
935
936    Ok(())
937}
938
939async fn vmgs_read(vmgs: &mut Vmgs, file_id: FileId, decrypt: bool) -> Result<Vec<u8>, Error> {
940    tracing::info!("Reading {}", file_id);
941    Ok(if decrypt {
942        vmgs.read_file(file_id).await?
943    } else {
944        vmgs.read_file_raw(file_id).await?
945    })
946}
947
948async fn vmgs_file_move(
949    file_path: impl AsRef<Path>,
950    src: FileId,
951    dst: FileId,
952    key_path: Option<impl AsRef<Path>>,
953    allow_overwrite: bool,
954) -> Result<(), Error> {
955    let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
956
957    vmgs_move(&mut vmgs, src, dst, allow_overwrite).await
958}
959
960async fn vmgs_move(
961    vmgs: &mut Vmgs,
962    src: FileId,
963    dst: FileId,
964    allow_overwrite: bool,
965) -> Result<(), Error> {
966    tracing::info!("Moving {} to {}", src, dst);
967
968    vmgs.move_file(src, dst, allow_overwrite).await?;
969
970    Ok(())
971}
972
973async fn vmgs_file_delete(file_path: impl AsRef<Path>, file_id: FileId) -> Result<(), Error> {
974    let mut vmgs = vmgs_file_open(
975        file_path,
976        None as Option<PathBuf>,
977        OpenMode::ReadWriteIgnore,
978    )
979    .await?;
980
981    vmgs_delete(&mut vmgs, file_id).await
982}
983
984async fn vmgs_delete(vmgs: &mut Vmgs, file_id: FileId) -> Result<(), Error> {
985    tracing::info!("Deleting {}", file_id);
986
987    vmgs.delete_file(file_id).await?;
988
989    Ok(())
990}
991
992async fn vmgs_file_dump_file_table(
993    file_path: impl AsRef<Path>,
994    key_path: Option<impl AsRef<Path>>,
995) -> Result<(), Error> {
996    let vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
997
998    vmgs_dump_file_table(&vmgs)
999}
1000
1001fn vmgs_dump_file_table(vmgs: &Vmgs) -> Result<(), Error> {
1002    println!("FILE TABLE");
1003    println!(
1004        "{0:^7} {1:^25} {2:^9} {3:^9} {4:^9}",
1005        "File ID", "File Name", "Allocated", "Valid", "Encrypted",
1006    );
1007    println!(
1008        "{} {} {} {} {}",
1009        "-".repeat(7),
1010        "-".repeat(25),
1011        "-".repeat(9),
1012        "-".repeat(9),
1013        "-".repeat(9),
1014    );
1015    for (file_id, file_info) in vmgs.dump_file_table() {
1016        println!(
1017            "{0:>7} {1:^25?} {2:>9} {3:>9} {4:^9}",
1018            file_id.0,
1019            file_id,
1020            file_info.allocated_bytes,
1021            file_info.valid_bytes,
1022            file_info.encrypted,
1023        );
1024    }
1025
1026    Ok(())
1027}
1028
1029async fn vmgs_file_dump_headers(file_path: impl AsRef<Path>) -> Result<(), Error> {
1030    tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
1031
1032    let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
1033    let disk = vhdfiledisk_open(file, OpenMode::ReadOnlyIgnore)?;
1034
1035    let (headers, res0) = match read_headers(disk).await {
1036        Ok(headers) => (Some(headers), Ok(())),
1037        Err((e, headers)) => (headers, Err(e.into())),
1038    };
1039
1040    if let Some(headers) = headers {
1041        let res1 = vmgs_dump_headers(&headers.0, &headers.1);
1042        if res0.is_err() { res0 } else { res1 }
1043    } else {
1044        res0
1045    }
1046}
1047
1048fn vmgs_dump_headers(header1: &VmgsHeader, header2: &VmgsHeader) -> Result<(), Error> {
1049    println!("FILE HEADERS");
1050    println!("{0:<23} {1:^70} {2:^70}", "Field", "Header 1", "Header 2");
1051    println!("{} {} {}", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1052
1053    let signature1 = format!("{:#018x}", header1.signature);
1054    let signature2 = format!("{:#018x}", header2.signature);
1055    println!(
1056        "{0:<23} {1:>70} {2:>70}",
1057        "Signature:", signature1, signature2
1058    );
1059
1060    println!(
1061        "{0:<23} {1:>70} {2:>70}",
1062        "Version:",
1063        extract_version(header1.version),
1064        extract_version(header2.version)
1065    );
1066    println!(
1067        "{0:<23} {1:>70x} {2:>70x}",
1068        "Checksum:", header1.checksum, header2.checksum
1069    );
1070    println!(
1071        "{0:<23} {1:>70} {2:>70}",
1072        "Sequence:", header1.sequence, header2.sequence
1073    );
1074    println!(
1075        "{0:<23} {1:>70} {2:>70}",
1076        "HeaderSize:", header1.header_size, header2.header_size
1077    );
1078
1079    let file_table_offset1 = format!("{:#010x}", header1.file_table_offset);
1080    let file_table_offset2 = format!("{:#010x}", header2.file_table_offset);
1081    println!(
1082        "{0:<23} {1:>70} {2:>70}",
1083        "FileTableOffset:", file_table_offset1, file_table_offset2
1084    );
1085
1086    println!(
1087        "{0:<23} {1:>70} {2:>70}",
1088        "FileTableSize:", header1.file_table_size, header2.file_table_size
1089    );
1090
1091    let encryption_algorithm1 = format!("{:#06x}", header1.encryption_algorithm.0);
1092    let encryption_algorithm2 = format!("{:#06x}", header2.encryption_algorithm.0);
1093    println!(
1094        "{0:<23} {1:>70} {2:>70}",
1095        "EncryptionAlgorithm:", encryption_algorithm1, encryption_algorithm2
1096    );
1097
1098    let markers1 = format!("{:#06x}", header1.markers.into_bits());
1099    let markers2 = format!("{:#06x}", header2.markers.into_bits());
1100
1101    println!("{0:<23} {1:>70} {2:>70}", "Markers:", markers1, markers2);
1102
1103    println!("{0:<23}", "MetadataKey1:");
1104
1105    let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[0].nonce));
1106    let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[0].nonce));
1107    println!(
1108        "    {0:<19} {1:>70} {2:>70}",
1109        "Nonce:", key1_nonce, key2_nonce
1110    );
1111
1112    let key1_reserved = format!("{:#010x}", header1.metadata_keys[0].reserved);
1113    let key2_reserved = format!("{:#010x}", header2.metadata_keys[0].reserved);
1114    println!(
1115        "    {0:<19} {1:>70} {2:>70}",
1116        "Reserved:", key1_reserved, key2_reserved
1117    );
1118
1119    let key1_auth_tag = format!(
1120        "0x{}",
1121        hex::encode(header1.metadata_keys[0].authentication_tag)
1122    );
1123    let key2_auth_tag = format!(
1124        "0x{}",
1125        hex::encode(header2.metadata_keys[0].authentication_tag)
1126    );
1127    println!(
1128        "    {0:<19} {1:>70} {2:>70}",
1129        "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1130    );
1131
1132    let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[0].encryption_key));
1133    let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[0].encryption_key));
1134    println!(
1135        "    {0:<19} {1:>70} {2:>70}",
1136        "EncryptionKey:", key1_encryption_key, key2_encryption_key
1137    );
1138
1139    println!("{0:<23}", "MetadataKey2:");
1140    let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[1].nonce));
1141    let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[1].nonce));
1142    println!(
1143        "    {0:<19} {1:>70} {2:>70}",
1144        "Nonce:", key1_nonce, key2_nonce
1145    );
1146
1147    let key1_reserved = format!("0x{:#010x}", header1.metadata_keys[1].reserved);
1148    let key2_reserved = format!("0x{:#010x}", header2.metadata_keys[1].reserved);
1149    println!(
1150        "    {0:<19} {1:>70} {2:>70}",
1151        "Reserved:", key1_reserved, key2_reserved
1152    );
1153
1154    let key1_auth_tag = format!(
1155        "0x{}",
1156        hex::encode(header1.metadata_keys[1].authentication_tag)
1157    );
1158    let key2_auth_tag = format!(
1159        "0x{}",
1160        hex::encode(header2.metadata_keys[1].authentication_tag)
1161    );
1162    println!(
1163        "    {0:<19} {1:>70} {2:>70}",
1164        "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1165    );
1166
1167    let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[1].encryption_key));
1168    let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[1].encryption_key));
1169    println!(
1170        "    {0:<19} {1:>70} {2:>70}",
1171        "EncryptionKey:", key1_encryption_key, key2_encryption_key
1172    );
1173
1174    let key1_reserved1 = format!("0x{:#010x}", header1.reserved_1);
1175    let key2_reserved1 = format!("0x{:#010x}", header2.reserved_1);
1176    println!(
1177        "{0:<23} {1:>70} {2:>70}",
1178        "Reserved:", key1_reserved1, key2_reserved1
1179    );
1180
1181    println!("{} {} {}\n", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1182
1183    print!("Verifying header 1... ");
1184    let header1_result = validate_header(header1);
1185    match &header1_result {
1186        Ok(_) => println!("[VALID]"),
1187        Err(e) => println!("[INVALID] Error: {}", e),
1188    }
1189
1190    print!("Verifying header 2... ");
1191    let header2_result = validate_header(header2);
1192    match &header2_result {
1193        Ok(_) => println!("[VALID]"),
1194        Err(e) => println!("[INVALID] Error: {}", e),
1195    }
1196
1197    match get_active_header(header1_result, header2_result) {
1198        Ok(active_index) => match active_index {
1199            0 => println!("Active header is 1"),
1200            1 => println!("Active header is 2"),
1201            _ => unreachable!(),
1202        },
1203        Err(e) => {
1204            println!("Unable to determine active header");
1205            return Err(Error::Vmgs(e));
1206        }
1207    }
1208
1209    Ok(())
1210}
1211
1212#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1213#[expect(clippy::enum_variant_names)]
1214enum OpenMode {
1215    /// Open read-only. Ignore encryption status.
1216    ReadOnlyIgnore,
1217    /// Open read-only. Warn if encrypted and no key was provided.
1218    ReadOnlyWarn,
1219    /// Open read-write. Ignore encryption status.
1220    ReadWriteIgnore,
1221    /// Open read-write. Fail if encrypted and no key was provided.
1222    ReadWriteRequire,
1223}
1224
1225impl OpenMode {
1226    fn write(&self) -> bool {
1227        match self {
1228            OpenMode::ReadOnlyIgnore | OpenMode::ReadOnlyWarn => false,
1229            OpenMode::ReadWriteIgnore | OpenMode::ReadWriteRequire => true,
1230        }
1231    }
1232}
1233
1234async fn vmgs_file_open(
1235    file_path: impl AsRef<Path>,
1236    key_path: Option<impl AsRef<Path>>,
1237    open_mode: OpenMode,
1238) -> Result<Vmgs, Error> {
1239    tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
1240    let file = fs_err::OpenOptions::new()
1241        .read(true)
1242        .write(open_mode.write())
1243        .open(file_path.as_ref())
1244        .map_err(Error::VmgsFile)?;
1245
1246    let disk = vhdfiledisk_open(file, open_mode)?;
1247
1248    let encryption_key = key_path.map(read_key_path).transpose()?;
1249
1250    let res = vmgs_open(disk, encryption_key.as_ref(), open_mode).await;
1251
1252    if matches!(
1253        res,
1254        Err(Error::Vmgs(VmgsError::InvalidFormat(_)))
1255            | Err(Error::Vmgs(VmgsError::CorruptFormat(_)))
1256    ) {
1257        tracing::error!("VMGS is corrupted or invalid. Dumping headers.");
1258        let _ = vmgs_file_dump_headers(file_path.as_ref()).await;
1259    }
1260
1261    res
1262}
1263
1264#[cfg_attr(
1265    not(feature = "encryption"),
1266    expect(unused_mut),
1267    expect(unused_variables)
1268)]
1269async fn vmgs_open(
1270    disk: Disk,
1271    encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1272    open_mode: OpenMode,
1273) -> Result<Vmgs, Error> {
1274    let mut vmgs: Vmgs = Vmgs::open(disk, None).await?;
1275
1276    if let Some(encryption_key) = encryption_key {
1277        #[cfg(feature = "encryption")]
1278        vmgs.unlock_with_encryption_key(encryption_key).await?;
1279        #[cfg(not(feature = "encryption"))]
1280        unreachable!("Encryption requires the encryption feature");
1281    } else if vmgs.encrypted() {
1282        match open_mode {
1283            OpenMode::ReadWriteRequire => return Err(Error::EncryptedNoKey),
1284            OpenMode::ReadOnlyWarn => tracing::warn!(
1285                "Opening encrypted VMGS file without decrypting. File ID encryption status may be inaccurate."
1286            ),
1287            OpenMode::ReadOnlyIgnore | OpenMode::ReadWriteIgnore => {}
1288        }
1289    }
1290
1291    Ok(vmgs)
1292}
1293
1294fn read_key_path(path: impl AsRef<Path>) -> Result<[u8; VMGS_ENCRYPTION_KEY_SIZE], Error> {
1295    tracing::info!("Reading encryption key: {}", path.as_ref().display());
1296    let metadata = fs_err::metadata(&path).map_err(Error::KeyFile)?;
1297    if metadata.len() != VMGS_ENCRYPTION_KEY_SIZE as u64 {
1298        return Err(Error::InvalidKeySize(
1299            VMGS_ENCRYPTION_KEY_SIZE as u64,
1300            metadata.len(),
1301        ));
1302    }
1303
1304    let bytes = fs_err::read(&path).map_err(Error::KeyFile)?;
1305    let bytes_sized = bytes.try_into().map_err(|bytes: Vec<u8>| {
1306        Error::InvalidKeySize(VMGS_ENCRYPTION_KEY_SIZE as u64, bytes.len() as u64)
1307    })?;
1308    Ok(bytes_sized)
1309}
1310
1311async fn vmgs_file_query_file_size(
1312    file_path: impl AsRef<Path>,
1313    file_id: FileId,
1314) -> Result<u64, Error> {
1315    let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1316
1317    vmgs_query_file_size(&vmgs, file_id)
1318}
1319
1320fn vmgs_query_file_size(vmgs: &Vmgs, file_id: FileId) -> Result<u64, Error> {
1321    let file_size = vmgs.get_file_info(file_id)?.valid_bytes;
1322
1323    tracing::info!("{} has a size of {}", file_id, file_size);
1324
1325    // STABLE OUTPUT
1326    println!("{file_size}");
1327
1328    Ok(file_size)
1329}
1330
1331async fn vmgs_file_query_encryption(file_path: impl AsRef<Path>) -> Result<(), Error> {
1332    let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1333
1334    let encryption_alg = vmgs.get_encryption_algorithm();
1335    tracing::info!("Encryption algorithm: {:?}", encryption_alg);
1336    let gsp_type = vmgs_get_gsp_type(&vmgs);
1337    tracing::info!("Guest state protection type: {:?}", gsp_type);
1338
1339    match (encryption_alg, gsp_type) {
1340        (EncryptionAlgorithm::NONE, _) => Err(Error::NotEncrypted),
1341        (EncryptionAlgorithm::AES_GCM, GspType::GspKey) => Ok(()),
1342        (EncryptionAlgorithm::AES_GCM, GspType::GspById) => Err(Error::GspByIdEncryption),
1343        (EncryptionAlgorithm::AES_GCM, GspType::None) => Err(Error::GspUnknown),
1344        _ => Err(Error::EncryptionUnknown),
1345    }
1346}
1347
1348fn vmgs_get_gsp_type(vmgs: &Vmgs) -> GspType {
1349    if vmgs.check_file_allocated(FileId::KEY_PROTECTOR) {
1350        GspType::GspKey
1351    } else if vmgs.check_file_allocated(FileId::VM_UNIQUE_ID) {
1352        GspType::GspById
1353    } else {
1354        GspType::None
1355    }
1356}
1357
1358fn vhdfiledisk_open(file: File, open_mode: OpenMode) -> Result<Disk, Error> {
1359    let file_size = file.metadata().map_err(Error::VmgsFile)?.len();
1360    validate_size(file_size)?;
1361
1362    let disk = Disk::new(
1363        Vhd1Disk::open_fixed(file.into(), open_mode == OpenMode::ReadOnlyWarn)
1364            .map_err(Error::Vhd1)?,
1365    )
1366    .map_err(Error::InvalidDisk)?;
1367
1368    Ok(disk)
1369}
1370
1371fn validate_size(file_size: u64) -> Result<(), Error> {
1372    const MAX_VMGS_FILE_SIZE: u64 = 4 * ONE_GIGA_BYTE;
1373
1374    if file_size > MAX_VMGS_FILE_SIZE {
1375        return Err(Error::InvalidVmgsFileSize(
1376            file_size,
1377            format!("Must be less than {}", MAX_VMGS_FILE_SIZE),
1378        ));
1379    }
1380
1381    if file_size == 0 {
1382        return Err(Error::ZeroSize);
1383    }
1384
1385    if file_size < VHD_DISK_FOOTER_PACKED_SIZE {
1386        return Err(Error::InvalidVmgsFileSize(
1387            file_size,
1388            format!("Must be greater than {}", VHD_DISK_FOOTER_PACKED_SIZE),
1389        ));
1390    }
1391
1392    Ok(())
1393}
1394
1395async fn vmgs_file_copy_igvmfile(
1396    file_path: impl AsRef<Path>,
1397    data_path: impl AsRef<Path>,
1398    allow_overwrite: bool,
1399    resource_code: ResourceCode,
1400) -> Result<(), Error> {
1401    let mut vmgs = vmgs_file_open(file_path, None::<PathBuf>, OpenMode::ReadWriteIgnore).await?;
1402
1403    tracing::info!("Reading IGVM file from: {}", data_path.as_ref().display());
1404
1405    let bytes = read_igvmfile(data_path.as_ref(), resource_code).await?;
1406
1407    vmgs_write(
1408        &mut vmgs,
1409        FileId::GUEST_FIRMWARE,
1410        &bytes,
1411        // IGVM file is not encrypted
1412        false,
1413        allow_overwrite,
1414    )
1415    .await?;
1416
1417    Ok(())
1418}
1419
1420async fn read_igvmfile(
1421    dll_path: impl AsRef<Path>,
1422    resource_code: ResourceCode,
1423) -> Result<Vec<u8>, Error> {
1424    use std::io::{Read, Seek, SeekFrom};
1425
1426    let dll_path = dll_path.as_ref();
1427    let file = File::open(dll_path).map_err(Error::DataFile)?;
1428
1429    // Try to find the resource in the DLL
1430    let resource_id = resource_code as u32;
1431    let descriptor = resource_dll_parser::DllResourceDescriptor::new(b"VMFW", resource_id);
1432    let (start, len) = resource_dll_parser::try_find_resource_from_dll(&file, &descriptor)
1433        .map_err(Error::IgvmFile)?
1434        .ok_or_else(|| {
1435            Error::IgvmFile(anyhow::anyhow!(
1436                "Unable to read IGVM resource 'VMFW' id {} from '{}': file is not a valid PE DLL",
1437                resource_id,
1438                dll_path.display()
1439            ))
1440        })?;
1441
1442    // Guard against crafted or corrupted DLLs advertising an unreasonable resource size.
1443    const MAX_IGVM_SIZE: usize = 256 * 1024 * 1024; // 256 MiB
1444    if len > MAX_IGVM_SIZE {
1445        return Err(Error::IgvmFile(anyhow::anyhow!(
1446            "IGVM resource size {} in '{}' exceeds maximum allowed size of {} bytes",
1447            len,
1448            dll_path.display(),
1449            MAX_IGVM_SIZE
1450        )));
1451    }
1452
1453    // Read the resource data
1454    let mut file = file;
1455    file.seek(SeekFrom::Start(start)).map_err(Error::DataFile)?;
1456
1457    let mut bytes = vec![0u8; len];
1458    file.read_exact(&mut bytes).map_err(Error::DataFile)?;
1459
1460    tracing::info!("Successfully loaded IGVM file from DLL");
1461    tracing::info!("Read {} bytes", bytes.len());
1462
1463    Ok(bytes)
1464}
1465
1466#[cfg(test)]
1467mod tests {
1468    use super::*;
1469    use pal_async::async_test;
1470    use tempfile::tempdir;
1471
1472    const ONE_MEGA_BYTE: u64 = 1024 * 1024;
1473
1474    pub(crate) async fn test_vmgs_create(
1475        path: impl AsRef<Path>,
1476        file_size: Option<u64>,
1477        force_create: bool,
1478        encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
1479    ) -> Result<(), Error> {
1480        let disk = vhdfiledisk_create(path, file_size, force_create)?;
1481        let _ = vmgs_create(disk, encryption_alg_key).await?;
1482        Ok(())
1483    }
1484
1485    pub(crate) async fn test_vmgs_open(
1486        path: impl AsRef<Path>,
1487        open_mode: OpenMode,
1488        encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1489    ) -> Result<Vmgs, Error> {
1490        let file = fs_err::OpenOptions::new()
1491            .read(true)
1492            .write(open_mode.write())
1493            .open(path.as_ref())
1494            .map_err(Error::VmgsFile)?;
1495        let disk = vhdfiledisk_open(file, open_mode)?;
1496        let vmgs = vmgs_open(disk, encryption_key, open_mode).await?;
1497        Ok(vmgs)
1498    }
1499
1500    async fn test_vmgs_query_file_size(
1501        file_path: impl AsRef<Path>,
1502        file_id: FileId,
1503    ) -> Result<u64, Error> {
1504        let vmgs =
1505            vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1506
1507        vmgs_query_file_size(&vmgs, file_id)
1508    }
1509
1510    #[cfg(feature = "encryption")]
1511    async fn test_vmgs_query_encryption(
1512        file_path: impl AsRef<Path>,
1513    ) -> Result<EncryptionAlgorithm, Error> {
1514        let vmgs =
1515            vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1516
1517        Ok(vmgs.get_encryption_algorithm())
1518    }
1519
1520    #[cfg(feature = "encryption")]
1521    async fn test_vmgs_update_key(
1522        file_path: impl AsRef<Path>,
1523        encryption_alg: EncryptionAlgorithm,
1524        encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1525        new_encryption_key: &[u8; VMGS_ENCRYPTION_KEY_SIZE],
1526    ) -> Result<(), Error> {
1527        let mut vmgs =
1528            test_vmgs_open(file_path, OpenMode::ReadWriteRequire, encryption_key).await?;
1529
1530        vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key).await
1531    }
1532
1533    // Create a new test file path.
1534    fn new_path() -> (tempfile::TempDir, PathBuf) {
1535        let dir = tempdir().unwrap();
1536        let file_path = dir.path().join("test.vmgs");
1537        (dir, file_path)
1538    }
1539
1540    #[async_test]
1541    async fn read_invalid_file() {
1542        let (_dir, path) = new_path();
1543
1544        let result = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None).await;
1545
1546        assert!(result.is_err());
1547    }
1548
1549    #[async_test]
1550    async fn read_empty_file() {
1551        let (_dir, path) = new_path();
1552
1553        test_vmgs_create(&path, None, false, None).await.unwrap();
1554
1555        let mut vmgs = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None)
1556            .await
1557            .unwrap();
1558        let result = vmgs_read(&mut vmgs, FileId::FILE_TABLE, false).await;
1559        assert!(result.is_err());
1560    }
1561
1562    #[async_test]
1563    async fn read_write_file() {
1564        let (_dir, path) = new_path();
1565        let buf = b"Plain text data".to_vec();
1566
1567        test_vmgs_create(&path, None, false, None).await.unwrap();
1568
1569        let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1570            .await
1571            .unwrap();
1572
1573        vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1574            .await
1575            .unwrap();
1576        let read_buf = vmgs_read(&mut vmgs, FileId::ATTEST, false).await.unwrap();
1577
1578        assert_eq!(buf, read_buf);
1579    }
1580
1581    #[async_test]
1582    async fn multiple_write_file() {
1583        let (_dir, path) = new_path();
1584        let buf_1 = b"Random super sensitive data".to_vec();
1585        let buf_2 = b"Other super secret data".to_vec();
1586        let buf_3 = b"I'm storing so much data".to_vec();
1587
1588        test_vmgs_create(&path, None, false, None).await.unwrap();
1589
1590        let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1591            .await
1592            .unwrap();
1593
1594        vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, false, false)
1595            .await
1596            .unwrap();
1597        let read_buf_1 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1598            .await
1599            .unwrap();
1600
1601        assert_eq!(buf_1, read_buf_1);
1602
1603        vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_2, false, false)
1604            .await
1605            .unwrap();
1606        let read_buf_2 = vmgs_read(&mut vmgs, FileId::TPM_PPI, false).await.unwrap();
1607
1608        assert_eq!(buf_2, read_buf_2);
1609
1610        let result = vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, false).await;
1611        assert!(result.is_err());
1612
1613        vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, true)
1614            .await
1615            .unwrap();
1616        let read_buf_3 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1617            .await
1618            .unwrap();
1619
1620        assert_eq!(buf_2, read_buf_2);
1621        assert_eq!(buf_3, read_buf_3);
1622    }
1623
1624    #[cfg(feature = "encryption")]
1625    #[async_test]
1626    async fn read_write_encrypted_file() {
1627        let (_dir, path) = new_path();
1628        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1629        let buf_1 = b"123".to_vec();
1630
1631        test_vmgs_create(
1632            &path,
1633            None,
1634            false,
1635            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1636        )
1637        .await
1638        .unwrap();
1639
1640        let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1641            .await
1642            .unwrap();
1643
1644        vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1645            .await
1646            .unwrap();
1647        let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1648            .await
1649            .unwrap();
1650
1651        assert!(read_buf == buf_1);
1652
1653        // try to normal write encrypted VMGs
1654        vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1655            .await
1656            .unwrap();
1657
1658        // try to normal read encrypted FileId
1659        let _encrypted_read = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1660            .await
1661            .unwrap();
1662    }
1663
1664    #[cfg(feature = "encryption")]
1665    #[async_test]
1666    async fn encrypted_read_write_plain_file() {
1667        // You shouldn't be able to use encryption if you create the VMGS
1668        // file without encryption.
1669        let (_dir, path) = new_path();
1670        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1671
1672        test_vmgs_create(&path, None, false, None).await.unwrap();
1673
1674        let result = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key)).await;
1675
1676        assert!(result.is_err());
1677    }
1678
1679    #[cfg(feature = "encryption")]
1680    #[async_test]
1681    async fn plain_read_write_encrypted_file() {
1682        let (_dir, path) = new_path();
1683        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1684        let buf_1 = b"123".to_vec();
1685
1686        test_vmgs_create(
1687            &path,
1688            None,
1689            false,
1690            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1691        )
1692        .await
1693        .unwrap();
1694
1695        let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteIgnore, None)
1696            .await
1697            .unwrap();
1698
1699        vmgs_write(&mut vmgs, FileId::VM_UNIQUE_ID, &buf_1, false, false)
1700            .await
1701            .unwrap();
1702        let read_buf = vmgs_read(&mut vmgs, FileId::VM_UNIQUE_ID, false)
1703            .await
1704            .unwrap();
1705
1706        assert!(read_buf == buf_1);
1707    }
1708
1709    #[async_test]
1710    async fn query_size() {
1711        let (_dir, path) = new_path();
1712        let buf = b"Plain text data".to_vec();
1713
1714        test_vmgs_create(&path, None, false, None).await.unwrap();
1715
1716        {
1717            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, None)
1718                .await
1719                .unwrap();
1720
1721            vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1722                .await
1723                .unwrap();
1724        }
1725
1726        let file_size = test_vmgs_query_file_size(&path, FileId::ATTEST)
1727            .await
1728            .unwrap();
1729        assert_eq!(file_size, buf.len() as u64);
1730    }
1731
1732    #[cfg(feature = "encryption")]
1733    #[async_test]
1734    async fn query_encrypted_file() {
1735        let (_dir, path) = new_path();
1736        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1737        let buf_1 = b"123".to_vec();
1738
1739        test_vmgs_create(
1740            &path,
1741            None,
1742            false,
1743            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1744        )
1745        .await
1746        .unwrap();
1747
1748        {
1749            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1750                .await
1751                .unwrap();
1752
1753            vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1754                .await
1755                .unwrap();
1756        }
1757
1758        let file_size = test_vmgs_query_file_size(&path, FileId::BIOS_NVRAM)
1759            .await
1760            .unwrap();
1761        assert_eq!(file_size, buf_1.len() as u64);
1762    }
1763
1764    #[async_test]
1765    async fn test_validate_vmgs_file_not_empty() {
1766        let buf: Vec<u8> = (0..255).collect();
1767        let (_dir, path) = new_path();
1768
1769        // create an empty (zero-length) file
1770        {
1771            fs_err::OpenOptions::new()
1772                .write(true)
1773                .create_new(true)
1774                .open(&path)
1775                .unwrap();
1776        }
1777
1778        // verify the file is zero size
1779        {
1780            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1781            assert!(matches!(result, Err(Error::ZeroSize)));
1782        }
1783
1784        // create an empty vhd of default size
1785        {
1786            vhdfiledisk_create(&path, None, true).unwrap();
1787        }
1788
1789        // verify the file is empty (with non-zero size)
1790        {
1791            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1792            assert!(matches!(result, Err(Error::Vmgs(VmgsError::EmptyFile))));
1793        }
1794
1795        // write some invalid data to the file
1796        {
1797            let mut file = fs_err::OpenOptions::new()
1798                .read(true)
1799                .write(true)
1800                .open(&path)
1801                .unwrap();
1802            file.seek(std::io::SeekFrom::Start(1024)).unwrap();
1803            file.write_all(&buf).unwrap();
1804        }
1805
1806        // verify the vmgs is identified as corrupted
1807        {
1808            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1809            matches!(result, Err(Error::Vmgs(VmgsError::CorruptFormat(_))));
1810        }
1811
1812        // create a valid vmgs
1813        {
1814            test_vmgs_create(&path, None, true, None).await.unwrap();
1815        }
1816
1817        // sanity check that the positive case works
1818        {
1819            test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None)
1820                .await
1821                .unwrap();
1822        }
1823    }
1824
1825    #[async_test]
1826    async fn test_misaligned_size() {
1827        let (_dir, path) = new_path();
1828        //File size must be % 512 to be valid, should produce error and file should not be created
1829        let result = test_vmgs_create(&path, Some(65537), false, None).await;
1830        assert!(result.is_err());
1831        assert!(!path.exists());
1832    }
1833
1834    #[async_test]
1835    async fn test_forcecreate() {
1836        let (_dir, path) = new_path();
1837        let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1838        assert!(result.is_ok());
1839        // Recreating file should fail without force create flag
1840        let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1841        assert!(result.is_err());
1842        // Should be able to resize the file when force create is passed in
1843        let result = test_vmgs_create(&path, Some(8388608), true, None).await;
1844        assert!(result.is_ok());
1845    }
1846
1847    #[cfg(feature = "encryption")]
1848    #[async_test]
1849    async fn test_update_encryption_key() {
1850        let (_dir, path) = new_path();
1851        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1852        let new_encryption_key = [6; VMGS_ENCRYPTION_KEY_SIZE];
1853        let buf_1 = b"123".to_vec();
1854
1855        test_vmgs_create(
1856            &path,
1857            None,
1858            false,
1859            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1860        )
1861        .await
1862        .unwrap();
1863
1864        {
1865            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1866                .await
1867                .unwrap();
1868
1869            vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1870                .await
1871                .unwrap();
1872        }
1873
1874        test_vmgs_update_key(
1875            &path,
1876            EncryptionAlgorithm::AES_GCM,
1877            Some(&encryption_key),
1878            &new_encryption_key,
1879        )
1880        .await
1881        .unwrap();
1882
1883        {
1884            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&new_encryption_key))
1885                .await
1886                .unwrap();
1887
1888            let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1889                .await
1890                .unwrap();
1891            assert!(read_buf == buf_1);
1892        }
1893
1894        // Old key should no longer work
1895        let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&encryption_key)).await;
1896        assert!(result.is_err());
1897    }
1898
1899    #[cfg(feature = "encryption")]
1900    #[async_test]
1901    async fn test_add_encryption_key() {
1902        let (_dir, path) = new_path();
1903        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1904        let buf_1 = b"123".to_vec();
1905
1906        test_vmgs_create(&path, None, false, None).await.unwrap();
1907
1908        test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1909            .await
1910            .unwrap();
1911
1912        let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1913            .await
1914            .unwrap();
1915
1916        vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1917            .await
1918            .unwrap();
1919
1920        let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1921            .await
1922            .unwrap();
1923
1924        assert!(read_buf == buf_1);
1925    }
1926
1927    #[cfg(feature = "encryption")]
1928    #[async_test]
1929    async fn test_query_encryption_update() {
1930        let (_dir, path) = new_path();
1931        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1932
1933        test_vmgs_create(&path, None, false, None).await.unwrap();
1934
1935        let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1936        assert_eq!(encryption_algorithm, EncryptionAlgorithm::NONE);
1937
1938        test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1939            .await
1940            .unwrap();
1941
1942        let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1943        assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1944    }
1945
1946    #[cfg(feature = "encryption")]
1947    #[async_test]
1948    async fn test_query_encryption_new() {
1949        let (_dir, path) = new_path();
1950        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1951
1952        test_vmgs_create(
1953            &path,
1954            None,
1955            false,
1956            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1957        )
1958        .await
1959        .unwrap();
1960
1961        let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1962        assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1963    }
1964
1965    #[async_test]
1966    async fn move_delete_file() {
1967        let (_dir, path) = new_path();
1968        let buf = b"Plain text data".to_vec();
1969
1970        test_vmgs_create(&path, None, false, None).await.unwrap();
1971
1972        let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1973            .await
1974            .unwrap();
1975
1976        vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf, false, false)
1977            .await
1978            .unwrap();
1979        let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1980            .await
1981            .unwrap();
1982        assert_eq!(buf, read_buf);
1983
1984        vmgs_move(
1985            &mut vmgs,
1986            FileId::TPM_NVRAM,
1987            FileId::TPM_NVRAM_BACKUP,
1988            false,
1989        )
1990        .await
1991        .unwrap();
1992        vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1993            .await
1994            .unwrap_err();
1995        let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
1996            .await
1997            .unwrap();
1998        assert_eq!(buf, read_buf);
1999        vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
2000            .await
2001            .unwrap();
2002        vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
2003            .await
2004            .unwrap_err();
2005    }
2006
2007    #[cfg(feature = "encryption")]
2008    #[async_test]
2009    async fn move_delete_file_encrypted() {
2010        let (_dir, path) = new_path();
2011        let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
2012        let buf_1 = b"123".to_vec();
2013        let buf_2 = b"456".to_vec();
2014
2015        test_vmgs_create(
2016            &path,
2017            None,
2018            false,
2019            Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
2020        )
2021        .await
2022        .unwrap();
2023
2024        {
2025            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
2026                .await
2027                .unwrap();
2028
2029            vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_2, true, false)
2030                .await
2031                .unwrap();
2032            vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf_1, true, false)
2033                .await
2034                .unwrap();
2035            let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, true).await.unwrap();
2036            assert!(read_buf == buf_1);
2037
2038            vmgs_move(
2039                &mut vmgs,
2040                FileId::TPM_NVRAM,
2041                FileId::TPM_NVRAM_BACKUP,
2042                false,
2043            )
2044            .await
2045            .unwrap();
2046            let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, true)
2047                .await
2048                .unwrap();
2049            assert!(read_buf == buf_1);
2050        }
2051
2052        // delete the file without decrypting, as the cmdline tool would do
2053        {
2054            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteIgnore, None)
2055                .await
2056                .unwrap();
2057            vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
2058                .await
2059                .unwrap();
2060            vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
2061                .await
2062                .unwrap_err();
2063        }
2064
2065        // make sure the file is not corrupted
2066        {
2067            let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
2068                .await
2069                .unwrap();
2070            let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
2071                .await
2072                .unwrap();
2073            assert!(read_buf == buf_2);
2074        }
2075    }
2076
2077    /// Creates a minimal PE64 DLL with a VMFW resource for testing.
2078    /// The resource contains `payload` at the specified `resource_id`.
2079    fn create_test_vmfw_dll(payload: &[u8], resource_id: u32) -> Vec<u8> {
2080        // PE Header constants
2081        const DOS_HEADER_SIZE: usize = 64;
2082        const PE_SIG_SIZE: usize = 4;
2083        const COFF_HEADER_SIZE: usize = 20;
2084        const OPTIONAL_HEADER_SIZE: usize = 240;
2085        const HEADERS_SIZE: usize = 0x200; // File-aligned
2086        const RSRC_SECTION_SIZE: usize = 0x200;
2087
2088        let mut pe = vec![0u8; HEADERS_SIZE + RSRC_SECTION_SIZE];
2089
2090        // DOS Header
2091        pe[0..2].copy_from_slice(b"MZ"); // e_magic
2092        pe[60..64].copy_from_slice(&64u32.to_le_bytes()); // e_lfanew
2093
2094        let mut offset = DOS_HEADER_SIZE;
2095
2096        // PE Signature
2097        pe[offset..offset + PE_SIG_SIZE].copy_from_slice(b"PE\0\0");
2098        offset += PE_SIG_SIZE;
2099
2100        // COFF File Header (20 bytes)
2101        pe[offset..offset + 2].copy_from_slice(&0x8664u16.to_le_bytes()); // Machine: AMD64
2102        pe[offset + 2..offset + 4].copy_from_slice(&1u16.to_le_bytes()); // NumberOfSections
2103        pe[offset + 16..offset + 18].copy_from_slice(&240u16.to_le_bytes()); // SizeOfOptionalHeader
2104        pe[offset + 18..offset + 20].copy_from_slice(&0x2022u16.to_le_bytes()); // Characteristics
2105        offset += COFF_HEADER_SIZE;
2106
2107        // Optional Header PE32+ (240 bytes)
2108        let opt_start = offset;
2109        pe[opt_start..opt_start + 2].copy_from_slice(&0x20bu16.to_le_bytes()); // Magic: PE32+
2110        pe[opt_start + 56..opt_start + 60].copy_from_slice(&0x3000u32.to_le_bytes()); // SizeOfImage
2111        pe[opt_start + 60..opt_start + 64].copy_from_slice(&0x200u32.to_le_bytes()); // SizeOfHeaders
2112        pe[opt_start + 108..opt_start + 112].copy_from_slice(&16u32.to_le_bytes()); // NumberOfRvaAndSizes
2113
2114        // Data directory entry 2: Resource directory (RVA=0x1000, Size=0x200)
2115        let rsrc_dir_offset = opt_start + 112 + 2 * 8;
2116        pe[rsrc_dir_offset..rsrc_dir_offset + 4].copy_from_slice(&0x1000u32.to_le_bytes());
2117        pe[rsrc_dir_offset + 4..rsrc_dir_offset + 8].copy_from_slice(&0x200u32.to_le_bytes());
2118        offset += OPTIONAL_HEADER_SIZE;
2119
2120        // Section Header for .rsrc
2121        pe[offset..offset + 8].copy_from_slice(b".rsrc\0\0\0");
2122        pe[offset + 8..offset + 12].copy_from_slice(&0x200u32.to_le_bytes()); // VirtualSize
2123        pe[offset + 12..offset + 16].copy_from_slice(&0x1000u32.to_le_bytes()); // VirtualAddress
2124        pe[offset + 16..offset + 20].copy_from_slice(&0x200u32.to_le_bytes()); // SizeOfRawData
2125        pe[offset + 20..offset + 24].copy_from_slice(&0x200u32.to_le_bytes()); // PointerToRawData
2126        pe[offset + 36..offset + 40].copy_from_slice(&0x40000040u32.to_le_bytes()); // Characteristics
2127
2128        // Resource section starts at file offset 0x200 (maps to RVA 0x1000)
2129        let rsrc_base = HEADERS_SIZE;
2130
2131        // Resource directory layout:
2132        // 0x00: Root directory (16 bytes) - 1 named entry for "VMFW"
2133        // 0x10: Root entry (8 bytes) - name RVA + subdirectory RVA
2134        // 0x18: Type name "VMFW" in UTF-16LE with length prefix (10 bytes)
2135        // 0x28: Type directory (16 bytes) - 1 ID entry
2136        // 0x38: Type entry (8 bytes) - ID + subdirectory RVA
2137        // 0x40: Language directory (16 bytes) - 1 ID entry
2138        // 0x50: Language entry (8 bytes) - language ID + data entry RVA
2139        // 0x58: Resource data entry (16 bytes)
2140        // 0x68: Actual payload data
2141
2142        // Root directory
2143        pe[rsrc_base + 12..rsrc_base + 14].copy_from_slice(&1u16.to_le_bytes()); // NumberOfNamedEntries
2144
2145        // Root entry: name offset with high bit set, subdirectory offset with high bit set
2146        pe[rsrc_base + 0x10..rsrc_base + 0x14].copy_from_slice(&0x80000018u32.to_le_bytes());
2147        pe[rsrc_base + 0x14..rsrc_base + 0x18].copy_from_slice(&0x80000028u32.to_le_bytes());
2148
2149        // Type name "VMFW" at 0x18: length (4) + UTF-16LE
2150        pe[rsrc_base + 0x18..rsrc_base + 0x1a].copy_from_slice(&4u16.to_le_bytes());
2151        pe[rsrc_base + 0x1a..rsrc_base + 0x22]
2152            .copy_from_slice(&[b'V', 0, b'M', 0, b'F', 0, b'W', 0]);
2153
2154        // Type directory at 0x28
2155        pe[rsrc_base + 0x28 + 14..rsrc_base + 0x28 + 16].copy_from_slice(&1u16.to_le_bytes()); // NumberOfIdEntries
2156
2157        // Type entry at 0x38: resource ID + subdirectory offset
2158        pe[rsrc_base + 0x38..rsrc_base + 0x3c].copy_from_slice(&resource_id.to_le_bytes());
2159        pe[rsrc_base + 0x3c..rsrc_base + 0x40].copy_from_slice(&0x80000040u32.to_le_bytes());
2160
2161        // Language directory at 0x40
2162        pe[rsrc_base + 0x40 + 14..rsrc_base + 0x40 + 16].copy_from_slice(&1u16.to_le_bytes()); // NumberOfIdEntries
2163
2164        // Language entry at 0x50: language ID + data entry offset (no high bit = data)
2165        pe[rsrc_base + 0x50..rsrc_base + 0x54].copy_from_slice(&0x0409u32.to_le_bytes()); // English US
2166        pe[rsrc_base + 0x54..rsrc_base + 0x58].copy_from_slice(&0x58u32.to_le_bytes());
2167
2168        // Resource data entry at 0x58
2169        let data_rva = 0x1000u32 + 0x68; // RVA of payload
2170        pe[rsrc_base + 0x58..rsrc_base + 0x5c].copy_from_slice(&data_rva.to_le_bytes());
2171        pe[rsrc_base + 0x5c..rsrc_base + 0x60]
2172            .copy_from_slice(&(payload.len() as u32).to_le_bytes());
2173
2174        // Copy payload at 0x68
2175        let payload_offset = rsrc_base + 0x68;
2176        let required_len = payload_offset + payload.len();
2177        if required_len > pe.len() {
2178            pe.resize(required_len, 0);
2179        }
2180        pe[payload_offset..required_len].copy_from_slice(payload);
2181
2182        pe
2183    }
2184
2185    #[async_test]
2186    async fn read_write_igvmfile() {
2187        let dir = tempdir().unwrap();
2188        let path = dir.path().join("test.vmgs");
2189
2190        // Create a test DLL with VMFW resource
2191        let expected_payload = b"TEST_IGVM_FIRMWARE_PAYLOAD_DATA";
2192        let dll_data = create_test_vmfw_dll(expected_payload, ResourceCode::Snp as u32);
2193
2194        // Write the test DLL to a temp file
2195        let dll_path = dir.path().join("test_vmfw.dll");
2196        fs_err::write(&dll_path, &dll_data).unwrap();
2197
2198        test_vmgs_create(&path, Some(ONE_MEGA_BYTE * 8), false, None)
2199            .await
2200            .unwrap();
2201
2202        let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteIgnore, None)
2203            .await
2204            .unwrap();
2205
2206        let buf = read_igvmfile(dll_path, ResourceCode::Snp).await.unwrap();
2207
2208        assert_eq!(buf, expected_payload);
2209
2210        vmgs_write(&mut vmgs, FileId::GUEST_FIRMWARE, &buf, false, false)
2211            .await
2212            .unwrap();
2213
2214        let read_buf = vmgs_read(&mut vmgs, FileId::GUEST_FIRMWARE, false)
2215            .await
2216            .unwrap();
2217
2218        assert_eq!(buf, read_buf);
2219    }
2220}