mkwim 0.1.0

Create a bootable Windows installation image from an ISO
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Jarkko Sakkinen

//! Minimal, direct FFI to libwim (the C library behind wimlib).

use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::ptr::NonNull;

use anyhow::{Context, Result, anyhow};

/// Opaque handle to an in-memory WIM, owned by libwim.
#[repr(C)]
pub struct WimStruct {
    _private: [u8; 0],
}

/// Treat path strings as UTF-8 (the only case that matters on Linux).
const WIMLIB_INIT_FLAG_ASSUME_UTF8: c_int = 0x0000_0001;

#[link(name = "wim")]
unsafe extern "C" {
    fn wimlib_global_init(init_flags: c_int) -> c_int;
    fn wimlib_global_cleanup();
    fn wimlib_open_wim(
        wim_file: *const c_char,
        open_flags: c_int,
        wim_ret: *mut *mut WimStruct,
    ) -> c_int;
    fn wimlib_split(
        wim: *mut WimStruct,
        swm_name: *const c_char,
        part_size: u64,
        write_flags: c_int,
    ) -> c_int;
    fn wimlib_free(wim: *mut WimStruct);
    fn wimlib_get_error_string(code: c_int) -> *const c_char;
}

/// Translate a non-zero libwim return code into an [`anyhow::Error`] carrying
/// the library's own description of the failure.
fn wim_error(code: c_int) -> anyhow::Error {
    let message = unsafe {
        // SAFETY: libwim returns either null or a valid NUL-terminated error
        // message for the duration of this call.
        let message = wimlib_get_error_string(code);
        if message.is_null() {
            format!("unknown wimlib error (code {code})")
        } else {
            CStr::from_ptr(message).to_string_lossy().into_owned()
        }
    };
    anyhow!("wimlib: {message} (code {code})")
}

fn c_path(path: &Path) -> Result<CString> {
    CString::new(path.as_os_str().as_bytes())
        .with_context(|| format!("path contains an interior NUL: {}", path.display()))
}

/// RAII guard for the process-wide libwim state.
///
/// `wimlib_global_init` must be called before any other libwim function and
/// the library must stay alive for as long as any [`Wim`] handle exists.
pub struct WimLib;

impl WimLib {
    /// Initialise libwim. Exactly one [`WimLib`] should be kept alive for the
    /// duration of the program.
    pub fn new() -> Result<Self> {
        let code = unsafe {
            // SAFETY: this is the documented process-wide initialisation call.
            wimlib_global_init(WIMLIB_INIT_FLAG_ASSUME_UTF8)
        };
        if code != 0 {
            return Err(wim_error(code));
        }
        Ok(Self)
    }
}

impl Drop for WimLib {
    fn drop(&mut self) {
        unsafe {
            // SAFETY: every `Wim` borrows this guard, so safe code cannot drop
            // it while a WIM handle remains alive.
            wimlib_global_cleanup();
        }
    }
}

/// An open WIM file. Frees the underlying `WimStruct` on drop.
pub struct Wim<'lib> {
    ptr: NonNull<WimStruct>,
    _lib: PhantomData<&'lib WimLib>,
}

impl<'lib> Wim<'lib> {
    /// Open an on-disk WIM file read-only.
    pub fn open(_lib: &'lib WimLib, path: &Path) -> Result<Self> {
        let path = c_path(path)?;
        let mut ptr = std::ptr::null_mut();
        let code = unsafe {
            // SAFETY: `path` is NUL-terminated and lives through the call, and
            // `ptr` is valid writable storage for the returned handle.
            wimlib_open_wim(path.as_ptr(), 0, std::ptr::addr_of_mut!(ptr))
        };
        if code != 0 {
            return Err(wim_error(code));
        }
        let ptr = NonNull::new(ptr).ok_or_else(|| anyhow!("wimlib returned a null WIM handle"))?;
        Ok(Self {
            ptr,
            _lib: PhantomData,
        })
    }

    /// Split this WIM into `swm_name`, `swm_name2`, ... parts, each at most
    /// `part_size` bytes. Windows Setup recognises the resulting `install.swm`
    /// set automatically.
    pub fn split(&self, swm_path: &Path, part_size: u64) -> Result<()> {
        let swm_path = c_path(swm_path)?;
        let code = unsafe {
            // SAFETY: this handle is owned by `self`; `swm_path` is
            // NUL-terminated and lives through the call.
            wimlib_split(self.ptr.as_ptr(), swm_path.as_ptr(), part_size, 0)
        };
        if code != 0 {
            return Err(wim_error(code));
        }
        Ok(())
    }
}

impl Drop for Wim<'_> {
    fn drop(&mut self) {
        unsafe {
            // SAFETY: `ptr` was returned by wimlib and is uniquely owned here.
            wimlib_free(self.ptr.as_ptr());
        }
    }
}