zcash_script 0.1.8

ffi bindings to zcashd's zcash_script library
Documentation
use libc::c_char;
use std::ffi::CStr;
use std::fs::File;
use std::path::Path;
use std::slice;
use std::str;
use std::sync::{
    atomic::{AtomicUsize, Ordering},
    Mutex,
};

use tracing::{
    callsite::{Callsite, Identifier},
    field::{FieldSet, Value},
    level_enabled,
    metadata::Kind,
    span::Entered,
    subscriber::{Interest, Subscriber},
    Event, Metadata, Span,
};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_core::Once;
use tracing_subscriber::{
    filter::EnvFilter,
    layer::{Layer, SubscriberExt},
    reload::{self, Handle},
    util::SubscriberInitExt,
};

#[cfg(not(target_os = "windows"))]
use std::ffi::OsStr;
#[cfg(not(target_os = "windows"))]
use std::os::unix::ffi::OsStrExt;

#[cfg(target_os = "windows")]
use std::ffi::OsString;
#[cfg(target_os = "windows")]
use std::os::windows::ffi::OsStringExt;

trait ReloadHandle {
    fn reload(&self, new_filter: EnvFilter) -> Result<(), reload::Error>;
}

impl<L, S> ReloadHandle for Handle<L, S>
where
    L: From<EnvFilter> + Layer<S> + 'static,
    S: Subscriber,
{
    fn reload(&self, new_filter: EnvFilter) -> Result<(), reload::Error> {
        self.reload(new_filter)
    }
}

pub struct TracingHandle {
    _file_guard: Option<WorkerGuard>,
    reload_handle: Box<dyn ReloadHandle>,
}

#[no_mangle]
pub extern "C" fn tracing_init(
    #[cfg(not(target_os = "windows"))] log_path: *const u8,
    #[cfg(target_os = "windows")] log_path: *const u16,
    log_path_len: usize,
    initial_filter: *const c_char,
    log_timestamps: bool,
) -> *mut TracingHandle {
    let initial_filter = unsafe { CStr::from_ptr(initial_filter) }
        .to_str()
        .expect("initial filter should be a valid string");

    let log_path = if log_path.is_null() {
        None
    } else {
        Some(unsafe { slice::from_raw_parts(log_path, log_path_len) })
    };

    #[cfg(not(target_os = "windows"))]
    let log_path = log_path.map(OsStr::from_bytes);

    #[cfg(target_os = "windows")]
    let log_path = log_path.map(OsString::from_wide);

    let log_path = log_path.as_ref().map(Path::new);

    let (file_logger, file_no_timestamps, file_guard) = if let Some(log_path) = log_path {
        let file_appender = tracing_appender::rolling::never(
            log_path.parent().unwrap(),
            log_path.file_name().unwrap(),
        );
        let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);

        if log_timestamps {
            (
                Some(
                    tracing_subscriber::fmt::layer()
                        .with_ansi(false)
                        .with_writer(non_blocking),
                ),
                None,
                Some(file_guard),
            )
        } else {
            (
                None,
                Some(
                    tracing_subscriber::fmt::layer()
                        .with_ansi(false)
                        .with_writer(non_blocking)
                        .without_time(),
                ),
                Some(file_guard),
            )
        }
    } else {
        (None, None, None)
    };

    let (stdout_logger, stdout_no_timestamps) = if file_logger.is_none() {
        if log_timestamps {
            (Some(tracing_subscriber::fmt::layer().with_ansi(true)), None)
        } else {
            (
                None,
                Some(
                    tracing_subscriber::fmt::layer()
                        .with_ansi(true)
                        .without_time(),
                ),
            )
        }
    } else {
        (None, None)
    };

    let (filter, reload_handle) = reload::Layer::new(EnvFilter::from(initial_filter));

    tracing_subscriber::registry()
        .with(stdout_logger)
        .with(stdout_no_timestamps)
        .with(file_logger)
        .with(file_no_timestamps)
        .with(filter)
        .init();

    Box::into_raw(Box::new(TracingHandle {
        _file_guard: file_guard,
        reload_handle: Box::new(reload_handle),
    }))
}

#[no_mangle]
pub extern "C" fn tracing_init_test(
    #[cfg(not(target_os = "windows"))] log_path: *const u8,
    #[cfg(target_os = "windows")] log_path: *const u16,
    log_path_len: usize,
    initial_filter: *const c_char,
) -> *mut TracingHandle {
    let initial_filter = unsafe { CStr::from_ptr(initial_filter) }
        .to_str()
        .expect("initial filter should be a valid string");

    let log_path = unsafe { slice::from_raw_parts(log_path, log_path_len) };

    #[cfg(not(target_os = "windows"))]
    let log_path = OsStr::from_bytes(log_path);

    #[cfg(target_os = "windows")]
    let log_path = OsString::from_wide(log_path);

    let log_path = Path::new(&log_path);

    let file = File::create(log_path).expect("can create log file for test");

    let file_logger = tracing_subscriber::fmt::layer()
        .with_writer(Mutex::new(file))
        .without_time();

    let (filter, reload_handle) = reload::Layer::new(EnvFilter::from(initial_filter));

    tracing_subscriber::registry()
        .with(file_logger)
        .with(filter)
        .init();

    Box::into_raw(Box::new(TracingHandle {
        _file_guard: None,
        reload_handle: Box::new(reload_handle),
    }))
}

#[no_mangle]
pub extern "C" fn tracing_free(handle: *mut TracingHandle) {
    drop(unsafe { Box::from_raw(handle) });
}

#[no_mangle]
pub extern "C" fn tracing_reload(handle: *mut TracingHandle, new_filter: *const c_char) -> bool {
    let handle = unsafe { &mut *handle };

    match unsafe { CStr::from_ptr(new_filter) }
        .to_str()
        .map(EnvFilter::new)
    {
        Err(e) => {
            tracing::error!("New filter is not valid UTF-8: {}", e);
            false
        }
        Ok(new_filter) => {
            if let Err(e) = handle.reload_handle.reload(new_filter) {
                tracing::error!("Filter reload failed: {}", e);
                false
            } else {
                true
            }
        }
    }
}

pub struct FfiCallsite {
    interest: AtomicUsize,
    meta: Option<Metadata<'static>>,
    registration: Once,
    fields: Vec<&'static str>,
}

impl FfiCallsite {
    fn new(fields: Vec<&'static str>) -> Self {
        FfiCallsite {
            interest: AtomicUsize::new(0),
            meta: None,
            registration: Once::new(),
            fields,
        }
    }

    #[inline(always)]
    fn is_enabled(&self) -> bool {
        let interest = self.interest();
        if interest.is_always() {
            return true;
        }
        if interest.is_never() {
            return false;
        }

        tracing::dispatcher::get_default(|current| current.enabled(self.metadata()))
    }

    #[inline(always)]
    pub fn register(&'static self) {
        self.registration
            .call_once(|| tracing::callsite::register(self));
    }

    #[inline(always)]
    fn interest(&self) -> Interest {
        match self.interest.load(Ordering::Relaxed) {
            0 => Interest::never(),
            2 => Interest::always(),
            _ => Interest::sometimes(),
        }
    }
}

impl Callsite for FfiCallsite {
    fn set_interest(&self, interest: Interest) {
        let interest = match () {
            _ if interest.is_never() => 0,
            _ if interest.is_always() => 2,
            _ => 1,
        };
        self.interest.store(interest, Ordering::SeqCst);
    }

    #[inline(always)]
    fn metadata(&self) -> &Metadata<'static> {
        self.meta.as_ref().unwrap()
    }
}

#[no_mangle]
pub extern "C" fn tracing_callsite(
    name: *const c_char,
    target: *const c_char,
    level: *const c_char,
    file: *const c_char,
    line: u32,
    fields: *const *const c_char,
    fields_len: usize,
    is_span: bool,
) -> *mut FfiCallsite {
    let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
    let target = unsafe { CStr::from_ptr(target) }.to_str().unwrap();
    let level = unsafe { CStr::from_ptr(level) }.to_str().unwrap();
    let file = unsafe { CStr::from_ptr(file) }.to_str().unwrap();

    let fields = unsafe { slice::from_raw_parts(fields, fields_len) };
    let fields = fields
        .iter()
        .map(|&p| unsafe { CStr::from_ptr(p) })
        .map(|cs| cs.to_str())
        .collect::<Result<Vec<_>, _>>()
        .unwrap();

    let level = level.parse().unwrap();

    // We immediately convert the new callsite into a pointer to erase lifetime
    // information. The caller MUST ensure that the callsite is stored statically.
    let site = Box::into_raw(Box::new(FfiCallsite::new(fields)));
    let site_ref = unsafe { &*site };

    let meta: Metadata<'static> = Metadata::new(
        name,
        target,
        level,
        Some(file),
        Some(line),
        None,
        FieldSet::new(&site_ref.fields, Identifier(site_ref)),
        if is_span { Kind::SPAN } else { Kind::EVENT },
    );
    unsafe { &mut *site }.meta = Some(meta);

    site_ref.register();
    site
}

macro_rules! repeat {
    (0, $val:expr) => {
        []
    };
    (1, $val:expr) => {
        [$val]
    };
    (2, $val:expr) => {
        [$val, $val]
    };
    (3, $val:expr) => {
        [$val, $val, $val]
    };
    (4, $val:expr) => {
        [$val, $val, $val, $val]
    };
    (5, $val:expr) => {
        [$val, $val, $val, $val, $val]
    };
    (6, $val:expr) => {
        [$val, $val, $val, $val, $val, $val]
    };
    (7, $val:expr) => {
        [$val, $val, $val, $val, $val, $val, $val]
    };
    (8, $val:expr) => {
        [$val, $val, $val, $val, $val, $val, $val, $val]
    };
    (9, $val:expr) => {
        [$val, $val, $val, $val, $val, $val, $val, $val, $val]
    };
    (10, $val:expr) => {
        [$val, $val, $val, $val, $val, $val, $val, $val, $val, $val]
    };
    (11, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (12, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (13, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (14, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (15, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val,
        ]
    };
    (16, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val,
        ]
    };
    (17, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val,
        ]
    };
    (18, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val,
        ]
    };
    (19, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val,
        ]
    };
    (20, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val,
        ]
    };
    (21, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (22, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (23, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (24, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (25, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (26, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (27, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (28, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
        ]
    };
    (29, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val,
        ]
    };
    (30, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val,
        ]
    };
    (31, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val,
        ]
    };
    (32, $val:expr) => {
        [
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val, $val,
            $val, $val, $val, $val,
        ]
    };
}

#[no_mangle]
pub extern "C" fn tracing_span_create(
    callsite: *const FfiCallsite,
    field_values: *const *const c_char,
    fields_len: usize,
) -> *mut Span {
    let callsite = unsafe { &*callsite };
    let field_values = unsafe { slice::from_raw_parts(field_values, fields_len) };

    let meta = callsite.metadata();
    assert!(meta.is_span());

    let span = if level_enabled!(*meta.level()) && callsite.is_enabled() {
        let mut fi = meta.fields().iter();
        let mut vi = field_values
            .iter()
            .map(|&p| unsafe { CStr::from_ptr(p) })
            .map(|cs| cs.to_string_lossy());

        use tracing::field::display;
        macro_rules! new_span {
            ($n:tt) => {
                Span::new(
                    meta,
                    &meta.fields().value_set(&repeat!(
                        $n,
                        (
                            &fi.next().unwrap(),
                            Some(&display(vi.next().unwrap().as_ref()) as &dyn Value)
                        )
                    )),
                )
            };
        }

        // https://github.com/tokio-rs/tracing/issues/782 might help improve things here.
        match field_values.len() {
            0 => new_span!(0),
            1 => new_span!(1),
            2 => new_span!(2),
            3 => new_span!(3),
            4 => new_span!(4),
            5 => new_span!(5),
            6 => new_span!(6),
            7 => new_span!(7),
            8 => new_span!(8),
            9 => new_span!(9),
            10 => new_span!(10),
            11 => new_span!(11),
            12 => new_span!(12),
            13 => new_span!(13),
            14 => new_span!(14),
            15 => new_span!(15),
            16 => new_span!(16),
            17 => new_span!(17),
            18 => new_span!(18),
            19 => new_span!(19),
            20 => new_span!(20),
            21 => new_span!(21),
            22 => new_span!(22),
            23 => new_span!(23),
            24 => new_span!(24),
            25 => new_span!(25),
            26 => new_span!(26),
            27 => new_span!(27),
            28 => new_span!(28),
            29 => new_span!(29),
            30 => new_span!(30),
            31 => new_span!(31),
            32 => new_span!(32),
            _ => unimplemented!(),
        }
    } else {
        Span::none()
    };

    Box::into_raw(Box::new(span))
}

#[no_mangle]
pub extern "C" fn tracing_span_clone(span: *const Span) -> *mut Span {
    unsafe { span.as_ref() }
        .map(|span| Box::into_raw(Box::new(span.clone())))
        .unwrap_or(std::ptr::null_mut())
}

#[no_mangle]
pub extern "C" fn tracing_span_free(span: *mut Span) {
    if !span.is_null() {
        drop(unsafe { Box::from_raw(span) });
    }
}

#[no_mangle]
pub extern "C" fn tracing_span_enter(span: *const Span) -> *mut Entered<'static> {
    unsafe { span.as_ref() }
        .map(|span| Box::into_raw(Box::new(span.enter())))
        .unwrap_or(std::ptr::null_mut())
}

#[no_mangle]
pub extern "C" fn tracing_span_exit(guard: *mut Entered) {
    if !guard.is_null() {
        drop(unsafe { Box::from_raw(guard) });
    }
}

#[no_mangle]
pub extern "C" fn tracing_log(
    callsite: *const FfiCallsite,
    field_values: *const *const c_char,
    fields_len: usize,
) {
    let callsite = unsafe { &*callsite };
    let field_values = unsafe { slice::from_raw_parts(field_values, fields_len) };

    let meta = callsite.metadata();
    assert!(meta.is_event());

    if level_enabled!(*meta.level()) && callsite.is_enabled() {
        let mut fi = meta.fields().iter();
        let mut vi = field_values
            .iter()
            .map(|&p| unsafe { CStr::from_ptr(p) })
            .map(|cs| cs.to_string_lossy());

        use tracing::field::display;
        macro_rules! dispatch {
            ($n:tt) => {
                Event::dispatch(
                    meta,
                    &meta.fields().value_set(&repeat!(
                        $n,
                        (
                            &fi.next().unwrap(),
                            Some(&display(vi.next().unwrap().as_ref()) as &dyn Value)
                        )
                    )),
                )
            };
        }

        // https://github.com/tokio-rs/tracing/issues/782 might help improve things here.
        match field_values.len() {
            1 => dispatch!(1),
            2 => dispatch!(2),
            3 => dispatch!(3),
            4 => dispatch!(4),
            5 => dispatch!(5),
            6 => dispatch!(6),
            7 => dispatch!(7),
            8 => dispatch!(8),
            9 => dispatch!(9),
            10 => dispatch!(10),
            11 => dispatch!(11),
            12 => dispatch!(12),
            13 => dispatch!(13),
            14 => dispatch!(14),
            15 => dispatch!(15),
            16 => dispatch!(16),
            17 => dispatch!(17),
            18 => dispatch!(18),
            19 => dispatch!(19),
            20 => dispatch!(20),
            21 => dispatch!(21),
            22 => dispatch!(22),
            23 => dispatch!(23),
            24 => dispatch!(24),
            25 => dispatch!(25),
            26 => dispatch!(26),
            27 => dispatch!(27),
            28 => dispatch!(28),
            29 => dispatch!(29),
            30 => dispatch!(30),
            31 => dispatch!(31),
            32 => dispatch!(32),
            _ => unimplemented!(),
        }
    }
}