picodata_plugin/internal/
mod.rs

1//! Picodata internal API.
2pub mod cas;
3pub(crate) mod ffi;
4pub mod types;
5
6use crate::internal::ffi::{
7    pico_ffi_authenticate, pico_ffi_cluster_uuid, pico_ffi_instance_info, pico_ffi_raft_info,
8    pico_ffi_rpc_version, pico_ffi_version,
9};
10use crate::internal::types::InstanceInfo;
11use abi_stable::derive_macro_reexports::RResult;
12use std::{env, fs, io, process};
13use tarantool::error::BoxError;
14
15/// Return picodata version.
16pub fn picodata_version() -> &'static str {
17    let ptr_and_len = unsafe { pico_ffi_version() };
18    // SAFETY: ptr points to static string
19    let slice = unsafe { std::slice::from_raw_parts(ptr_and_len.0, ptr_and_len.1) };
20    std::str::from_utf8(slice).expect("should be valid utf8")
21}
22
23/// Return picodata RPC API version.
24pub fn rpc_version() -> &'static str {
25    let ptr_and_len = unsafe { pico_ffi_rpc_version() };
26    // SAFETY: ptr points to static string
27    let slice = unsafe { std::slice::from_raw_parts(ptr_and_len.0, ptr_and_len.1) };
28    std::str::from_utf8(slice).expect("should be valid utf8")
29}
30
31/// Return UUID of the cluster the current instance belongs to.
32pub fn cluster_uuid() -> Result<String, BoxError> {
33    match unsafe { pico_ffi_cluster_uuid() } {
34        RResult::ROk(rstring) => Ok(rstring.into()),
35        RResult::RErr(_) => {
36            let error = BoxError::last();
37            Err(error)
38        }
39    }
40}
41
42/// Return information about current picodata instance.
43pub fn instance_info() -> Result<InstanceInfo, BoxError> {
44    match unsafe { pico_ffi_instance_info() } {
45        RResult::ROk(info) => Ok(info),
46        RResult::RErr(_) => {
47            let error = BoxError::last();
48            Err(error)
49        }
50    }
51}
52
53/// Return information about RAFT protocol state.
54pub fn raft_info() -> types::RaftInfo {
55    unsafe { pico_ffi_raft_info() }
56}
57
58/// # Description
59///
60/// Tries to authenticate a user with specified password.
61/// Authentication method is determined via accessing `_pico_user`
62/// system table using `admin` session.
63///
64/// # FFI
65///
66/// Uses [`pico_ffi_authenticate`].
67///
68/// # Errors
69///
70/// - User was not found in the list of available users.
71/// - Authentication method was not initialized for the user.
72/// - Username length is greater than `u32`.
73/// - Password is not correct for the specified user.
74///
75/// # Panics
76///
77/// - Global Raft node is not initialized.
78/// - Authentication data is not set for the specified user.
79/// - Session of `admin` user is closed.
80/// - User `admin` is not found.
81/// - User `admin` does not have enough permissions.
82/// - Internal error on accessing underlying Tarantool space of `_pico_user` system table.
83pub fn authenticate(username: &str, password: impl AsRef<[u8]>) -> Result<(), BoxError> {
84    match unsafe { pico_ffi_authenticate(username.into(), password.as_ref().into()) } {
85        0 => Ok(()),
86        _ => {
87            let error = BoxError::last();
88            Err(error)
89        }
90    }
91}
92
93/// Dump the backtrace to a file to make debugging easier.
94/// This is also used in integration tests.
95fn dump_backtrace(msg: &str) -> Result<(), io::Error> {
96    let should_dump = env::var("PICODATA_INTERNAL_BACKTRACE_DUMP")
97        .map(|v| !v.is_empty())
98        .unwrap_or(false);
99
100    if !should_dump {
101        return Ok(());
102    }
103
104    let name = format!("picodata-{}.backtrace", process::id());
105    let path = env::current_dir()?.join(&name);
106
107    fs::write(&name, msg)
108        .map(|_| tarantool::say_info!("dumped panic backtrace to `{}`", path.display()))
109        .inspect_err(|e| tarantool::say_info!("{}", e))?;
110
111    Ok(())
112}
113
114#[inline]
115pub fn set_panic_hook() {
116    // NOTE: this function is called ASAP when starting up the process.
117    // Even if `say` isn't properly initialized yet, we
118    // still should be able to print a simplified line to stderr.
119    std::panic::set_hook(Box::new(|info| {
120        let version = crate::internal::picodata_version();
121
122        // Capture a backtrace regardless of RUST_BACKTRACE and such.
123        let backtrace = std::backtrace::Backtrace::force_capture();
124        let message = format!(
125            "Picodata {version}\n\n{info}\n\nbacktrace:\n{backtrace}\naborting due to panic"
126        );
127
128        // Dump backtrace to logs and file if needed
129        tarantool::say_crit!("\n\n{message}");
130        dump_backtrace(&message)
131            .unwrap_or_else(|e| tarantool::say_info!("Failed to dump panic backtrace: {}", e));
132
133        std::process::abort();
134    }));
135}
136
137#[derive(thiserror::Error, Debug)]
138pub enum InternalError {
139    #[error("timeout")]
140    Timeout,
141    #[error("internal error: {0}")]
142    Any(BoxError),
143}