1#![deny(rustdoc::broken_intra_doc_links)]
6
7use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::os::raw::c_char;
10use std::panic::UnwindSafe;
11use std::path::PathBuf;
12
13use ffi_support::{define_string_destructor, ConcurrentHandleMap, FfiStr, IntoFfi};
14
15#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
16use once_cell::sync::OnceCell;
17
18pub use glean_core::metrics::MemoryUnit;
19pub use glean_core::metrics::TimeUnit;
20pub use glean_core::upload::ffi_upload_result::*;
21use glean_core::Glean;
22pub use glean_core::Lifetime;
23
24mod macros;
25
26mod boolean;
27pub mod byte_buffer;
28mod counter;
29mod custom_distribution;
30mod datetime;
31mod event;
32mod ffi_string_ext;
33mod from_raw;
34mod handlemap_ext;
35mod jwe;
36mod labeled;
37mod memory_distribution;
38pub mod ping_type;
39mod quantity;
40mod string;
41mod string_list;
42mod timespan;
43mod timing_distribution;
44pub mod upload;
45mod url;
46mod uuid;
47
48#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
49mod fd_logger;
50
51#[cfg(unix)]
52#[macro_use]
53mod weak;
54
55use ffi_string_ext::FallibleToString;
56use from_raw::*;
57use handlemap_ext::HandleMapExtension;
58use ping_type::PING_TYPES;
59use upload::FfiPingUploadTask;
60
61pub(crate) fn with_glean<R, F>(callback: F) -> R::Value
69where
70 F: UnwindSafe + FnOnce(&Glean) -> Result<R, glean_core::Error>,
71 R: IntoFfi,
72{
73 let mut error = ffi_support::ExternError::success();
74 let res =
75 ffi_support::abort_on_panic::call_with_result(
76 &mut error,
77 || match glean_core::global_glean() {
78 Some(glean) => {
79 let glean = glean.lock().unwrap();
80 callback(&glean)
81 }
82 None => Err(glean_core::Error::not_initialized()),
83 },
84 );
85 handlemap_ext::log_if_error(error);
86 res
87}
88
89pub(crate) fn with_glean_mut<R, F>(callback: F) -> R::Value
97where
98 F: UnwindSafe + FnOnce(&mut Glean) -> Result<R, glean_core::Error>,
99 R: IntoFfi,
100{
101 let mut error = ffi_support::ExternError::success();
102 let res =
103 ffi_support::abort_on_panic::call_with_result(
104 &mut error,
105 || match glean_core::global_glean() {
106 Some(glean) => {
107 let mut glean = glean.lock().unwrap();
108 callback(&mut glean)
109 }
110 None => Err(glean_core::Error::not_initialized()),
111 },
112 );
113 handlemap_ext::log_if_error(error);
114 res
115}
116
117pub(crate) fn with_glean_value<R, F>(callback: F) -> R::Value
124where
125 F: UnwindSafe + FnOnce(&Glean) -> R,
126 R: IntoFfi,
127{
128 with_glean(|glean| Ok(callback(glean)))
129}
130
131pub(crate) fn with_glean_value_mut<R, F>(callback: F) -> R::Value
138where
139 F: UnwindSafe + FnOnce(&mut Glean) -> R,
140 R: IntoFfi,
141{
142 with_glean_mut(|glean| Ok(callback(glean)))
143}
144
145#[no_mangle]
148pub extern "C" fn glean_enable_logging() {
149 #[cfg(target_os = "android")]
150 {
151 let _ = std::panic::catch_unwind(|| {
152 let filter = android_logger::FilterBuilder::new()
153 .filter_module("glean_ffi", log::LevelFilter::Debug)
154 .filter_module("glean_core", log::LevelFilter::Debug)
155 .filter_module("glean", log::LevelFilter::Debug)
156 .build();
157 android_logger::init_once(
158 android_logger::Config::default()
159 .with_min_level(log::Level::Debug)
160 .with_filter(filter)
161 .with_tag("libglean_ffi"),
162 );
163 log::trace!("Android logging should be hooked up!")
164 });
165 }
166
167 #[cfg(target_os = "ios")]
169 {
170 #[cfg(debug_assertions)]
173 let level = log::LevelFilter::Debug;
174 #[cfg(not(debug_assertions))]
175 let level = log::LevelFilter::Info;
176
177 let logger = oslog::OsLogger::new("org.mozilla.glean").level_filter(level);
178
179 match logger.init() {
180 Ok(_) => log::trace!("os_log should be hooked up!"),
181 Err(_) => log::warn!("os_log was already initialized"),
185 };
186 }
187
188 #[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
192 {
193 match env_logger::try_init() {
194 Ok(_) => log::trace!("stdout logging should be hooked up!"),
195 Err(_) => log::warn!("stdout logging was already initialized"),
199 };
200 }
201}
202
203#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
204static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
205
206#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
217#[no_mangle]
218pub unsafe extern "C" fn glean_enable_logging_to_fd(fd: u64) {
219 if FD_LOGGER.set(fd_logger::FdLogger::new(fd)).is_ok() {
224 if log::set_logger(FD_LOGGER.get().unwrap()).is_ok() {
228 log::set_max_level(log::LevelFilter::Debug);
229 }
230 }
231}
232
233#[repr(C)]
238pub struct FfiConfiguration<'a> {
239 pub data_dir: FfiStr<'a>,
240 pub package_name: FfiStr<'a>,
241 pub language_binding_name: FfiStr<'a>,
242 pub upload_enabled: u8,
243 pub max_events: Option<&'a i32>,
244 pub delay_ping_lifetime_io: u8,
245}
246
247impl TryFrom<&FfiConfiguration<'_>> for glean_core::Configuration {
249 type Error = glean_core::Error;
250
251 fn try_from(cfg: &FfiConfiguration) -> Result<Self, Self::Error> {
252 let data_path = cfg.data_dir.to_string_fallible()?;
253 let data_path = PathBuf::from(data_path);
254 let application_id = cfg.package_name.to_string_fallible()?;
255 let language_binding_name = cfg.language_binding_name.to_string_fallible()?;
256 let upload_enabled = cfg.upload_enabled != 0;
257 let max_events = cfg.max_events.filter(|&&i| i >= 0).map(|m| *m as usize);
258 let delay_ping_lifetime_io = cfg.delay_ping_lifetime_io != 0;
259 let app_build = "Unknown".to_string();
260 let use_core_mps = false;
261
262 Ok(Self {
263 upload_enabled,
264 data_path,
265 application_id,
266 language_binding_name,
267 max_events,
268 delay_ping_lifetime_io,
269 app_build,
270 use_core_mps,
271 })
272 }
273}
274
275#[no_mangle]
279pub unsafe extern "C" fn glean_initialize(cfg: *const FfiConfiguration) -> u8 {
280 assert!(!cfg.is_null());
281
282 handlemap_ext::handle_result(|| {
283 let glean_cfg = glean_core::Configuration::try_from(&*cfg)?;
288 let glean = Glean::new(glean_cfg)?;
289 glean_core::setup_glean(glean)?;
290 log::info!("Glean initialized");
291 Ok(true)
292 })
293}
294
295#[no_mangle]
296pub extern "C" fn glean_on_ready_to_submit_pings() -> u8 {
297 with_glean_value(|glean| glean.on_ready_to_submit_pings())
298}
299
300#[no_mangle]
301pub extern "C" fn glean_is_upload_enabled() -> u8 {
302 with_glean_value(|glean| glean.is_upload_enabled())
303}
304
305#[no_mangle]
306pub extern "C" fn glean_set_upload_enabled(flag: u8) {
307 with_glean_value_mut(|glean| glean.set_upload_enabled(flag != 0));
308 }
311
312#[no_mangle]
313pub extern "C" fn glean_submit_ping_by_name(ping_name: FfiStr, reason: FfiStr) -> u8 {
314 with_glean(|glean| {
315 Ok(glean.submit_ping_by_name(&ping_name.to_string_fallible()?, reason.as_opt_str()))
316 })
317}
318
319#[no_mangle]
320pub extern "C" fn glean_ping_collect(ping_type_handle: u64, reason: FfiStr) -> *mut c_char {
321 with_glean_value(|glean| {
322 PING_TYPES.call_infallible(ping_type_handle, |ping_type| {
323 let ping_maker = glean_core::ping::PingMaker::new();
324 let data = ping_maker
325 .collect_string(glean, ping_type, reason.as_opt_str())
326 .unwrap_or_else(|| String::from(""));
327 log::info!("Ping({}): {}", ping_type.name.as_str(), data);
328 data
329 })
330 })
331}
332
333#[no_mangle]
334pub extern "C" fn glean_set_experiment_active(
335 experiment_id: FfiStr,
336 branch: FfiStr,
337 extra_keys: RawStringArray,
338 extra_values: RawStringArray,
339 extra_len: i32,
340) {
341 with_glean(|glean| {
342 let experiment_id = experiment_id.to_string_fallible()?;
343 let branch = branch.to_string_fallible()?;
344 let extra = from_raw_string_array_and_string_array(extra_keys, extra_values, extra_len)?;
345
346 glean.set_experiment_active(experiment_id, branch, extra);
347 Ok(())
348 })
349}
350
351#[no_mangle]
352pub extern "C" fn glean_set_experiment_inactive(experiment_id: FfiStr) {
353 with_glean(|glean| {
354 let experiment_id = experiment_id.to_string_fallible()?;
355 glean.set_experiment_inactive(experiment_id);
356 Ok(())
357 })
358}
359
360#[no_mangle]
361pub extern "C" fn glean_experiment_test_is_active(experiment_id: FfiStr) -> u8 {
362 with_glean(|glean| {
363 let experiment_id = experiment_id.to_string_fallible()?;
364 Ok(glean.test_is_experiment_active(experiment_id))
365 })
366}
367
368#[no_mangle]
369pub extern "C" fn glean_experiment_test_get_data(experiment_id: FfiStr) -> *mut c_char {
370 with_glean(|glean| {
371 let experiment_id = experiment_id.to_string_fallible()?;
372 Ok(glean.test_get_experiment_data_as_json(experiment_id))
373 })
374}
375
376#[no_mangle]
377pub extern "C" fn glean_clear_application_lifetime_metrics() {
378 with_glean_value(|glean| glean.clear_application_lifetime_metrics());
379}
380
381#[no_mangle]
388pub extern "C" fn glean_flush_rlb_dispatcher() {
389 #[cfg(unix)]
390 #[allow(non_upper_case_globals)]
391 {
392 weak!(fn rlb_flush_dispatcher() -> ());
393
394 if let Some(f) = rlb_flush_dispatcher.get() {
395 unsafe {
402 f();
403 }
404 } else {
405 log::info!("No RLB symbol found. Not trying to flush the RLB dispatcher.");
406 }
407 }
408}
409
410#[no_mangle]
411pub extern "C" fn glean_set_dirty_flag(flag: u8) {
412 with_glean_value_mut(|glean| glean.set_dirty_flag(flag != 0));
413}
414
415#[no_mangle]
416pub extern "C" fn glean_is_dirty_flag_set() -> u8 {
417 with_glean_value(|glean| glean.is_dirty_flag_set())
418}
419
420#[no_mangle]
421pub extern "C" fn glean_handle_client_active() {
422 with_glean_value_mut(|glean| glean.handle_client_active());
423}
424
425#[no_mangle]
426pub extern "C" fn glean_handle_client_inactive() {
427 with_glean_value_mut(|glean| glean.handle_client_inactive());
428}
429
430#[no_mangle]
431pub extern "C" fn glean_test_clear_all_stores() {
432 with_glean_value(|glean| glean.test_clear_all_stores())
433}
434
435#[no_mangle]
436pub extern "C" fn glean_destroy_glean() {
437 with_glean_value_mut(|glean| glean.destroy_db())
438}
439
440#[no_mangle]
441pub extern "C" fn glean_is_first_run() -> u8 {
442 with_glean_value(|glean| glean.is_first_run())
443}
444
445#[no_mangle]
454pub extern "C" fn glean_get_upload_task(result: *mut FfiPingUploadTask) {
455 with_glean_value(|glean| {
456 let ffi_task = FfiPingUploadTask::from(glean.get_upload_task());
457 unsafe {
458 std::ptr::write(result, ffi_task);
459 }
460 });
461}
462
463#[no_mangle]
474pub unsafe extern "C" fn glean_process_ping_upload_response(
475 task: *mut FfiPingUploadTask,
476 status: u32,
477) {
478 if task.is_null() {
483 return;
484 }
485
486 let task = std::ptr::replace(task, FfiPingUploadTask::Done);
490
491 with_glean(|glean| {
492 if let FfiPingUploadTask::Upload { document_id, .. } = task {
493 assert!(!document_id.is_null());
494 let document_id_str = CStr::from_ptr(document_id)
495 .to_str()
496 .map_err(|_| glean_core::Error::utf8_error())?;
497 glean.process_ping_upload_response(document_id_str, status.into());
498 };
499 Ok(())
500 });
501}
502
503#[no_mangle]
507pub unsafe extern "C" fn glean_initialize_for_subprocess(cfg: *const FfiConfiguration) -> u8 {
508 assert!(!cfg.is_null());
509
510 handlemap_ext::handle_result(|| {
511 let glean_cfg = glean_core::Configuration::try_from(&*cfg)?;
516 let glean = Glean::new_for_subprocess(&glean_cfg, true)?;
517 glean_core::setup_glean(glean)?;
518 log::info!("Glean initialized for subprocess");
519 Ok(true)
520 })
521}
522
523#[no_mangle]
524pub extern "C" fn glean_set_debug_view_tag(tag: FfiStr) -> u8 {
525 with_glean_mut(|glean| {
526 let tag = tag.to_string_fallible()?;
527 Ok(glean.set_debug_view_tag(&tag))
528 })
529}
530
531#[no_mangle]
532pub extern "C" fn glean_set_log_pings(value: u8) {
533 with_glean_mut(|glean| Ok(glean.set_log_pings(value != 0)));
534}
535
536#[no_mangle]
537pub extern "C" fn glean_set_source_tags(raw_tags: RawStringArray, tags_count: i32) -> u8 {
538 with_glean_mut(|glean| {
539 let tags = from_raw_string_array(raw_tags, tags_count)?;
540 Ok(glean.set_source_tags(tags))
541 })
542}
543
544#[no_mangle]
545pub extern "C" fn glean_get_timestamp_ms() -> u64 {
546 glean_core::get_timestamp_ms()
547}
548
549define_string_destructor!(glean_str_free);