#![deny(rustdoc::broken_intra_doc_links)]
use std::convert::TryFrom;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::panic::UnwindSafe;
use std::path::PathBuf;
use ffi_support::{define_string_destructor, ConcurrentHandleMap, FfiStr, IntoFfi};
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
use once_cell::sync::OnceCell;
pub use glean_core::metrics::MemoryUnit;
pub use glean_core::metrics::TimeUnit;
pub use glean_core::upload::ffi_upload_result::*;
use glean_core::Glean;
pub use glean_core::Lifetime;
mod macros;
mod boolean;
pub mod byte_buffer;
mod counter;
mod custom_distribution;
mod datetime;
mod event;
mod ffi_string_ext;
mod from_raw;
mod handlemap_ext;
mod jwe;
mod labeled;
mod memory_distribution;
pub mod ping_type;
mod quantity;
mod string;
mod string_list;
mod timespan;
mod timing_distribution;
pub mod upload;
mod url;
mod uuid;
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
mod fd_logger;
#[cfg(unix)]
#[macro_use]
mod weak;
use ffi_string_ext::FallibleToString;
use from_raw::*;
use handlemap_ext::HandleMapExtension;
use ping_type::PING_TYPES;
use upload::FfiPingUploadTask;
pub(crate) fn with_glean<R, F>(callback: F) -> R::Value
where
F: UnwindSafe + FnOnce(&Glean) -> Result<R, glean_core::Error>,
R: IntoFfi,
{
let mut error = ffi_support::ExternError::success();
let res =
ffi_support::abort_on_panic::call_with_result(
&mut error,
|| match glean_core::global_glean() {
Some(glean) => {
let glean = glean.lock().unwrap();
callback(&glean)
}
None => Err(glean_core::Error::not_initialized()),
},
);
handlemap_ext::log_if_error(error);
res
}
pub(crate) fn with_glean_mut<R, F>(callback: F) -> R::Value
where
F: UnwindSafe + FnOnce(&mut Glean) -> Result<R, glean_core::Error>,
R: IntoFfi,
{
let mut error = ffi_support::ExternError::success();
let res =
ffi_support::abort_on_panic::call_with_result(
&mut error,
|| match glean_core::global_glean() {
Some(glean) => {
let mut glean = glean.lock().unwrap();
callback(&mut glean)
}
None => Err(glean_core::Error::not_initialized()),
},
);
handlemap_ext::log_if_error(error);
res
}
pub(crate) fn with_glean_value<R, F>(callback: F) -> R::Value
where
F: UnwindSafe + FnOnce(&Glean) -> R,
R: IntoFfi,
{
with_glean(|glean| Ok(callback(glean)))
}
pub(crate) fn with_glean_value_mut<R, F>(callback: F) -> R::Value
where
F: UnwindSafe + FnOnce(&mut Glean) -> R,
R: IntoFfi,
{
with_glean_mut(|glean| Ok(callback(glean)))
}
#[no_mangle]
pub extern "C" fn glean_enable_logging() {
#[cfg(target_os = "android")]
{
let _ = std::panic::catch_unwind(|| {
let filter = android_logger::FilterBuilder::new()
.filter_module("glean_ffi", log::LevelFilter::Debug)
.filter_module("glean_core", log::LevelFilter::Debug)
.filter_module("glean", log::LevelFilter::Debug)
.build();
android_logger::init_once(
android_logger::Config::default()
.with_min_level(log::Level::Debug)
.with_filter(filter)
.with_tag("libglean_ffi"),
);
log::trace!("Android logging should be hooked up!")
});
}
#[cfg(target_os = "ios")]
{
#[cfg(debug_assertions)]
let level = log::LevelFilter::Debug;
#[cfg(not(debug_assertions))]
let level = log::LevelFilter::Info;
let logger = oslog::OsLogger::new("org.mozilla.glean").level_filter(level);
match logger.init() {
Ok(_) => log::trace!("os_log should be hooked up!"),
Err(_) => log::warn!("os_log was already initialized"),
};
}
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
{
match env_logger::try_init() {
Ok(_) => log::trace!("stdout logging should be hooked up!"),
Err(_) => log::warn!("stdout logging was already initialized"),
};
}
}
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
#[no_mangle]
pub unsafe extern "C" fn glean_enable_logging_to_fd(fd: u64) {
if FD_LOGGER.set(fd_logger::FdLogger::new(fd)).is_ok() {
if log::set_logger(FD_LOGGER.get().unwrap()).is_ok() {
log::set_max_level(log::LevelFilter::Debug);
}
}
}
#[repr(C)]
pub struct FfiConfiguration<'a> {
pub data_dir: FfiStr<'a>,
pub package_name: FfiStr<'a>,
pub language_binding_name: FfiStr<'a>,
pub upload_enabled: u8,
pub max_events: Option<&'a i32>,
pub delay_ping_lifetime_io: u8,
}
impl TryFrom<&FfiConfiguration<'_>> for glean_core::Configuration {
type Error = glean_core::Error;
fn try_from(cfg: &FfiConfiguration) -> Result<Self, Self::Error> {
let data_path = cfg.data_dir.to_string_fallible()?;
let data_path = PathBuf::from(data_path);
let application_id = cfg.package_name.to_string_fallible()?;
let language_binding_name = cfg.language_binding_name.to_string_fallible()?;
let upload_enabled = cfg.upload_enabled != 0;
let max_events = cfg.max_events.filter(|&&i| i >= 0).map(|m| *m as usize);
let delay_ping_lifetime_io = cfg.delay_ping_lifetime_io != 0;
let app_build = "Unknown".to_string();
let use_core_mps = false;
Ok(Self {
upload_enabled,
data_path,
application_id,
language_binding_name,
max_events,
delay_ping_lifetime_io,
app_build,
use_core_mps,
})
}
}
#[no_mangle]
pub unsafe extern "C" fn glean_initialize(cfg: *const FfiConfiguration) -> u8 {
assert!(!cfg.is_null());
handlemap_ext::handle_result(|| {
let glean_cfg = glean_core::Configuration::try_from(&*cfg)?;
let glean = Glean::new(glean_cfg)?;
glean_core::setup_glean(glean)?;
log::info!("Glean initialized");
Ok(true)
})
}
#[no_mangle]
pub extern "C" fn glean_on_ready_to_submit_pings() -> u8 {
with_glean_value(|glean| glean.on_ready_to_submit_pings())
}
#[no_mangle]
pub extern "C" fn glean_is_upload_enabled() -> u8 {
with_glean_value(|glean| glean.is_upload_enabled())
}
#[no_mangle]
pub extern "C" fn glean_set_upload_enabled(flag: u8) {
with_glean_value_mut(|glean| glean.set_upload_enabled(flag != 0));
}
#[no_mangle]
pub extern "C" fn glean_submit_ping_by_name(ping_name: FfiStr, reason: FfiStr) -> u8 {
with_glean(|glean| {
Ok(glean.submit_ping_by_name(&ping_name.to_string_fallible()?, reason.as_opt_str()))
})
}
#[no_mangle]
pub extern "C" fn glean_ping_collect(ping_type_handle: u64, reason: FfiStr) -> *mut c_char {
with_glean_value(|glean| {
PING_TYPES.call_infallible(ping_type_handle, |ping_type| {
let ping_maker = glean_core::ping::PingMaker::new();
let data = ping_maker
.collect_string(glean, ping_type, reason.as_opt_str())
.unwrap_or_else(|| String::from(""));
log::info!("Ping({}): {}", ping_type.name.as_str(), data);
data
})
})
}
#[no_mangle]
pub extern "C" fn glean_set_experiment_active(
experiment_id: FfiStr,
branch: FfiStr,
extra_keys: RawStringArray,
extra_values: RawStringArray,
extra_len: i32,
) {
with_glean(|glean| {
let experiment_id = experiment_id.to_string_fallible()?;
let branch = branch.to_string_fallible()?;
let extra = from_raw_string_array_and_string_array(extra_keys, extra_values, extra_len)?;
glean.set_experiment_active(experiment_id, branch, extra);
Ok(())
})
}
#[no_mangle]
pub extern "C" fn glean_set_experiment_inactive(experiment_id: FfiStr) {
with_glean(|glean| {
let experiment_id = experiment_id.to_string_fallible()?;
glean.set_experiment_inactive(experiment_id);
Ok(())
})
}
#[no_mangle]
pub extern "C" fn glean_experiment_test_is_active(experiment_id: FfiStr) -> u8 {
with_glean(|glean| {
let experiment_id = experiment_id.to_string_fallible()?;
Ok(glean.test_is_experiment_active(experiment_id))
})
}
#[no_mangle]
pub extern "C" fn glean_experiment_test_get_data(experiment_id: FfiStr) -> *mut c_char {
with_glean(|glean| {
let experiment_id = experiment_id.to_string_fallible()?;
Ok(glean.test_get_experiment_data_as_json(experiment_id))
})
}
#[no_mangle]
pub extern "C" fn glean_clear_application_lifetime_metrics() {
with_glean_value(|glean| glean.clear_application_lifetime_metrics());
}
#[no_mangle]
pub extern "C" fn glean_flush_rlb_dispatcher() {
#[cfg(unix)]
#[allow(non_upper_case_globals)]
{
weak!(fn rlb_flush_dispatcher() -> ());
if let Some(f) = rlb_flush_dispatcher.get() {
unsafe {
f();
}
} else {
log::info!("No RLB symbol found. Not trying to flush the RLB dispatcher.");
}
}
}
#[no_mangle]
pub extern "C" fn glean_set_dirty_flag(flag: u8) {
with_glean_value_mut(|glean| glean.set_dirty_flag(flag != 0));
}
#[no_mangle]
pub extern "C" fn glean_is_dirty_flag_set() -> u8 {
with_glean_value(|glean| glean.is_dirty_flag_set())
}
#[no_mangle]
pub extern "C" fn glean_handle_client_active() {
with_glean_value_mut(|glean| glean.handle_client_active());
}
#[no_mangle]
pub extern "C" fn glean_handle_client_inactive() {
with_glean_value_mut(|glean| glean.handle_client_inactive());
}
#[no_mangle]
pub extern "C" fn glean_test_clear_all_stores() {
with_glean_value(|glean| glean.test_clear_all_stores())
}
#[no_mangle]
pub extern "C" fn glean_destroy_glean() {
with_glean_value_mut(|glean| glean.destroy_db())
}
#[no_mangle]
pub extern "C" fn glean_is_first_run() -> u8 {
with_glean_value(|glean| glean.is_first_run())
}
#[no_mangle]
pub extern "C" fn glean_get_upload_task(result: *mut FfiPingUploadTask) {
with_glean_value(|glean| {
let ffi_task = FfiPingUploadTask::from(glean.get_upload_task());
unsafe {
std::ptr::write(result, ffi_task);
}
});
}
#[no_mangle]
pub unsafe extern "C" fn glean_process_ping_upload_response(
task: *mut FfiPingUploadTask,
status: u32,
) {
if task.is_null() {
return;
}
let task = std::ptr::replace(task, FfiPingUploadTask::Done);
with_glean(|glean| {
if let FfiPingUploadTask::Upload { document_id, .. } = task {
assert!(!document_id.is_null());
let document_id_str = CStr::from_ptr(document_id)
.to_str()
.map_err(|_| glean_core::Error::utf8_error())?;
glean.process_ping_upload_response(document_id_str, status.into());
};
Ok(())
});
}
#[no_mangle]
pub unsafe extern "C" fn glean_initialize_for_subprocess(cfg: *const FfiConfiguration) -> u8 {
assert!(!cfg.is_null());
handlemap_ext::handle_result(|| {
let glean_cfg = glean_core::Configuration::try_from(&*cfg)?;
let glean = Glean::new_for_subprocess(&glean_cfg, true)?;
glean_core::setup_glean(glean)?;
log::info!("Glean initialized for subprocess");
Ok(true)
})
}
#[no_mangle]
pub extern "C" fn glean_set_debug_view_tag(tag: FfiStr) -> u8 {
with_glean_mut(|glean| {
let tag = tag.to_string_fallible()?;
Ok(glean.set_debug_view_tag(&tag))
})
}
#[no_mangle]
pub extern "C" fn glean_set_log_pings(value: u8) {
with_glean_mut(|glean| Ok(glean.set_log_pings(value != 0)));
}
#[no_mangle]
pub extern "C" fn glean_set_source_tags(raw_tags: RawStringArray, tags_count: i32) -> u8 {
with_glean_mut(|glean| {
let tags = from_raw_string_array(raw_tags, tags_count)?;
Ok(glean.set_source_tags(tags))
})
}
#[no_mangle]
pub extern "C" fn glean_get_timestamp_ms() -> u64 {
glean_core::get_timestamp_ms()
}
define_string_destructor!(glean_str_free);