gemachain_program_runtime/
native_loader.rs1#[cfg(unix)]
3use libloading::os::unix::*;
4#[cfg(windows)]
5use libloading::os::windows::*;
6use log::*;
7use num_derive::{FromPrimitive, ToPrimitive};
8use serde::Serialize;
9use gemachain_sdk::{
10 account::ReadableAccount,
11 decode_error::DecodeError,
12 entrypoint_native::ProgramEntrypoint,
13 instruction::InstructionError,
14 keyed_account::keyed_account_at_index,
15 native_loader,
16 process_instruction::{InvokeContext, LoaderEntrypoint},
17 pubkey::Pubkey,
18};
19use std::{
20 collections::HashMap,
21 env,
22 path::{Path, PathBuf},
23 str,
24 sync::RwLock,
25};
26use thiserror::Error;
27
28#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
29pub enum NativeLoaderError {
30 #[error("Entrypoint name in the account data is not a valid UTF-8 string")]
31 InvalidAccountData = 0x0aaa_0001,
32 #[error("Entrypoint was not found in the module")]
33 EntrypointNotFound = 0x0aaa_0002,
34 #[error("Failed to load the module")]
35 FailedToLoad = 0x0aaa_0003,
36}
37impl<T> DecodeError<T> for NativeLoaderError {
38 fn type_of() -> &'static str {
39 "NativeLoaderError"
40 }
41}
42
43#[cfg(unix)]
45const PLATFORM_FILE_PREFIX: &str = "lib";
46#[cfg(windows)]
47const PLATFORM_FILE_PREFIX: &str = "";
48
49#[cfg(any(target_os = "macos", target_os = "ios"))]
51const PLATFORM_FILE_EXTENSION: &str = "dylib";
52#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
54const PLATFORM_FILE_EXTENSION: &str = "so";
55#[cfg(windows)]
57const PLATFORM_FILE_EXTENSION: &str = "dll";
58
59pub type ProgramSymbolCache = RwLock<HashMap<String, Symbol<ProgramEntrypoint>>>;
60pub type LoaderSymbolCache = RwLock<HashMap<String, Symbol<LoaderEntrypoint>>>;
61
62#[derive(Debug, Default)]
63pub struct NativeLoader {
64 program_symbol_cache: ProgramSymbolCache,
65 loader_symbol_cache: LoaderSymbolCache,
66}
67impl NativeLoader {
68 fn create_path(name: &str) -> Result<PathBuf, InstructionError> {
69 let current_exe = env::current_exe().map_err(|e| {
70 error!("create_path(\"{}\"): current exe not found: {:?}", name, e);
71 InstructionError::from(NativeLoaderError::EntrypointNotFound)
72 })?;
73 let current_exe_directory = PathBuf::from(current_exe.parent().ok_or_else(|| {
74 error!(
75 "create_path(\"{}\"): no parent directory of {:?}",
76 name, current_exe
77 );
78 InstructionError::from(NativeLoaderError::FailedToLoad)
79 })?);
80
81 let library_file_name = PathBuf::from(PLATFORM_FILE_PREFIX.to_string() + name)
82 .with_extension(PLATFORM_FILE_EXTENSION);
83
84 let file_path = current_exe_directory.join(&library_file_name);
87 if file_path.exists() {
88 Ok(file_path)
89 } else {
90 Ok(current_exe_directory.join("deps").join(library_file_name))
92 }
93 }
94
95 #[cfg(windows)]
96 fn library_open(path: &Path) -> Result<Library, libloading::Error> {
97 unsafe { Library::new(path) }
98 }
99
100 #[cfg(not(windows))]
101 fn library_open(path: &Path) -> Result<Library, libloading::Error> {
102 unsafe {
103 Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
105 }
106 }
107
108 fn get_entrypoint<T>(
109 name: &str,
110 cache: &RwLock<HashMap<String, Symbol<T>>>,
111 ) -> Result<Symbol<T>, InstructionError> {
112 let mut cache = cache.write().unwrap();
113 if let Some(entrypoint) = cache.get(name) {
114 Ok(entrypoint.clone())
115 } else {
116 match Self::library_open(&Self::create_path(name)?) {
117 Ok(library) => {
118 let result = unsafe { library.get::<T>(name.as_bytes()) };
119 match result {
120 Ok(entrypoint) => {
121 cache.insert(name.to_string(), entrypoint.clone());
122 Ok(entrypoint)
123 }
124 Err(e) => {
125 error!("Unable to find program entrypoint in {:?}: {:?})", name, e);
126 Err(NativeLoaderError::EntrypointNotFound.into())
127 }
128 }
129 }
130 Err(e) => {
131 error!("Failed to load: {:?}", e);
132 Err(NativeLoaderError::FailedToLoad.into())
133 }
134 }
135 }
136 }
137
138 pub fn process_instruction(
139 &self,
140 program_id: &Pubkey,
141 instruction_data: &[u8],
142 invoke_context: &mut dyn InvokeContext,
143 ) -> Result<(), InstructionError> {
144 let (program_id, name_vec) = {
145 let keyed_accounts = invoke_context.get_keyed_accounts()?;
146 let program = keyed_account_at_index(keyed_accounts, 0)?;
147 if native_loader::id() != *program_id {
148 error!("Program id mismatch");
149 return Err(InstructionError::IncorrectProgramId);
150 }
151 if program.owner()? != *program_id {
152 error!("Executable account now owned by loader");
153 return Err(InstructionError::IncorrectProgramId);
154 }
155 (
159 *program.unsigned_key(),
160 &program.try_account_ref()?.data().to_vec(),
161 )
162 };
163
164 let name = match str::from_utf8(name_vec) {
165 Ok(v) => v,
166 Err(e) => {
167 error!("Invalid UTF-8 sequence: {}", e);
168 return Err(NativeLoaderError::InvalidAccountData.into());
169 }
170 };
171 if name.is_empty() || name.starts_with('\0') {
172 error!("Empty name string");
173 return Err(NativeLoaderError::InvalidAccountData.into());
174 }
175 trace!("Call native {:?}", name);
176 invoke_context.remove_first_keyed_account()?;
177 if name.ends_with("loader_program") {
178 let entrypoint =
179 Self::get_entrypoint::<LoaderEntrypoint>(name, &self.loader_symbol_cache)?;
180 unsafe { entrypoint(&program_id, instruction_data, invoke_context) }
181 } else {
182 let entrypoint =
183 Self::get_entrypoint::<ProgramEntrypoint>(name, &self.program_symbol_cache)?;
184 unsafe {
185 entrypoint(
186 &program_id,
187 invoke_context.get_keyed_accounts()?,
188 instruction_data,
189 )
190 }
191 }
192 }
193}