hyperlight-guest-bin 0.15.0

This crate provides the opinionated bits of the guest library, such as the panic handler, the entry point, the guest logger, the exception handling logic, and third-party code used by our C-API needed to build a native hyperlight-guest binary.
Documentation
/*
Copyright 2025  The Hyperlight Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

use alloc::string::String;
use alloc::vec;
use core::sync::atomic::{AtomicU64, Ordering};

use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType};

use crate::host_comm::call_host_function;
use crate::libc::{
    CLOCK_MONOTONIC, CLOCK_REALTIME, EBADF, EINVAL, EIO, ENOSYS, c_int, c_long, c_void, clockid_t,
    errno, timespec,
};

fn set_errno(val: u32) {
    // SAFETY: single-threaded guest, errno is a global int (__GLOBAL_ERRNO)
    // Bindgen uses u32 for the errno definitions, so we convert it to the expected c_int type here
    unsafe { errno = val as _ };
}

static CURRENT_TIME: AtomicU64 = AtomicU64::new(0);

/// Returns a synthetic monotonically-increasing time starting at Unix epoch
/// increasing 1s each call.
fn current_time() -> (u64, u64) {
    let call_count = CURRENT_TIME.fetch_add(1, Ordering::Relaxed) + 1;
    (call_count, 0)
}

#[unsafe(no_mangle)]
extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> isize {
    if buf.is_null() && count > 0 {
        set_errno(EINVAL);
        return -1;
    }

    if fd != 0 {
        set_errno(EBADF);
        return -1;
    }

    0
}

#[unsafe(no_mangle)]
extern "C" fn write(fd: c_int, buf: *const c_void, count: usize) -> isize {
    if buf.is_null() && count > 0 {
        set_errno(EINVAL);
        return -1;
    }

    if fd != 1 && fd != 2 {
        set_errno(EBADF);
        return -1;
    }

    let slice = unsafe { core::slice::from_raw_parts(buf as *const u8, count) };
    let s = String::from_utf8_lossy(slice);
    match call_host_function::<i32>(
        "HostPrint",
        Some(vec![ParameterValue::String(s.into_owned())]),
        ReturnType::Int,
    ) {
        Ok(_) => count as isize,
        Err(_) => {
            set_errno(EIO);
            -1
        }
    }
}

#[unsafe(no_mangle)]
extern "C" fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_int {
    // The libc bindings generated by bindgen are u32, but we expect them to be clockid_t,
    // so we convert the constants to the expected type here for comparison.
    const CLOCK_ID_REALTIME: clockid_t = CLOCK_REALTIME as _;
    const CLOCK_ID_MONOTONIC: clockid_t = CLOCK_MONOTONIC as _;

    if tp.is_null() {
        set_errno(EINVAL);
        return -1;
    }

    match clk_id {
        CLOCK_ID_REALTIME | CLOCK_ID_MONOTONIC => {
            let (secs, nanos) = current_time();
            unsafe {
                (*tp).tv_sec = secs as c_long;
                (*tp).tv_nsec = nanos as c_long;
            }
            0
        }
        _ => {
            set_errno(EINVAL);
            -1
        }
    }
}

#[unsafe(no_mangle)]
extern "C" fn _exit(ec: c_int) -> ! {
    hyperlight_guest::exit::abort_with_code(&[ec as u8]);
}

#[unsafe(no_mangle)]
extern "C" fn lseek(_fd: c_int, _offset: c_long, _whence: c_int) -> c_long {
    set_errno(ENOSYS);
    -1
}

#[unsafe(no_mangle)]
extern "C" fn close(_fd: c_int) -> c_int {
    0
}