1use std::borrow::Cow;
4use std::fs::File;
5use std::hash::Hash;
6use std::hash::Hasher;
7use std::io::BufReader;
8use std::io::Read;
9use std::path::Path;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13#[derive(Debug, thiserror::Error, deno_error::JsError)]
14pub enum LoadError {
15 #[class(generic)]
16 #[error("Failed to write native addon (Deno FFI/Node API) '{0}' to '{1}' because the file system was readonly. This is a limitation of native addons with deno compile.", executable_path.display(), real_path.display())]
17 ReadOnlyFilesystem {
18 real_path: PathBuf,
19 executable_path: PathBuf,
20 },
21 #[class(generic)]
22 #[error("Failed to write native addon (Deno FFI/Node API) '{0}' to '{1}'.", executable_path.display(), real_path.display())]
23 FailedWriting {
24 real_path: PathBuf,
25 executable_path: PathBuf,
26 #[source]
27 source: std::io::Error,
28 },
29}
30
31pub type DenoRtNativeAddonLoaderRc = Arc<dyn DenoRtNativeAddonLoader>;
32
33pub trait DenoRtNativeAddonLoader: Send + Sync {
38 fn load_if_in_vfs(&self, path: &Path) -> Option<Cow<'static, [u8]>>;
39
40 fn load_and_resolve_path<'a>(
41 &self,
42 path: &'a Path,
43 ) -> Result<Cow<'a, Path>, LoadError> {
44 match self.load_if_in_vfs(path) {
45 Some(bytes) => {
46 let exe_name = std::env::current_exe().ok();
47 let exe_name = exe_name
48 .as_ref()
49 .and_then(|p| p.file_stem())
50 .map(|s| s.to_string_lossy())
51 .unwrap_or("denort".into());
52 let real_path = resolve_temp_file_name(&exe_name, path, &bytes);
53 if let Err(err) = deno_path_util::fs::atomic_write_file(
54 &sys_traits::impls::RealSys,
55 &real_path,
56 &bytes,
57 0o644,
58 ) {
59 if err.kind() == std::io::ErrorKind::ReadOnlyFilesystem {
60 return Err(LoadError::ReadOnlyFilesystem {
61 real_path,
62 executable_path: path.to_path_buf(),
63 });
64 }
65
66 if !file_matches_bytes(&real_path, &bytes) {
69 return Err(LoadError::FailedWriting {
70 executable_path: path.to_path_buf(),
71 real_path,
72 source: err,
73 });
74 }
75 }
76 Ok(Cow::Owned(real_path))
77 }
78 None => Ok(Cow::Borrowed(path)),
79 }
80 }
81}
82
83fn file_matches_bytes(path: &Path, expected_bytes: &[u8]) -> bool {
84 let file = match File::open(path) {
85 Ok(f) => f,
86 Err(_) => return false,
87 };
88 let len_on_disk = match file.metadata() {
89 Ok(m) => m.len(),
90 Err(_) => return false,
91 };
92 if len_on_disk as usize != expected_bytes.len() {
93 return false; }
95
96 const CHUNK: usize = 8 * 1024;
98 let mut reader = BufReader::with_capacity(CHUNK, file);
99 let mut buf = [0u8; CHUNK];
100 let mut offset = 0;
101
102 loop {
103 match reader.read(&mut buf) {
104 Ok(0) => return offset == expected_bytes.len(),
105 Ok(n) => {
106 let next_offset = offset + n;
107 if next_offset > expected_bytes.len()
108 || buf[..n] != expected_bytes[offset..next_offset]
109 {
110 return false;
111 }
112 offset = next_offset;
113 }
114 Err(_) => return false,
115 }
116 }
117}
118
119fn resolve_temp_file_name(
120 current_exe_name: &str,
121 path: &Path,
122 bytes: &[u8],
123) -> PathBuf {
124 let path_hash = {
126 let mut hasher = twox_hash::XxHash64::default();
127 path.hash(&mut hasher);
128 hasher.finish()
129 };
130 let bytes_hash = {
131 let mut hasher = twox_hash::XxHash64::default();
132 bytes.hash(&mut hasher);
133 hasher.finish()
134 };
135 let mut file_name =
136 format!("{}{}{}", current_exe_name, path_hash, bytes_hash);
137 if let Some(ext) = path.extension() {
138 file_name.push('.');
139 file_name.push_str(&ext.to_string_lossy());
140 }
141 std::env::temp_dir().join(&file_name)
142}
143
144#[cfg(test)]
145mod test {
146 use super::*;
147
148 #[test]
149 fn test_file_matches_bytes() {
150 let tempdir = tempfile::TempDir::new().unwrap();
151 let path = tempdir.path().join("file.txt");
152 let mut bytes = vec![0u8; 17892];
153 for (i, byte) in bytes.iter_mut().enumerate() {
154 *byte = i as u8;
155 }
156 std::fs::write(&path, &bytes).unwrap();
157 assert!(file_matches_bytes(&path, &bytes));
158 bytes[17192] = 9;
159 assert!(!file_matches_bytes(&path, &bytes));
160 }
161
162 #[test]
163 fn test_resolve_temp_file_name() {
164 let file_path = PathBuf::from("/test/test.node");
165 let bytes: [u8; 3] = [1, 2, 3];
166 let temp_file = resolve_temp_file_name("exe_name", &file_path, &bytes);
167 assert_eq!(
168 temp_file,
169 std::env::temp_dir()
170 .join("exe_name1805603793990095570513255480333703631005.node")
171 );
172 }
173}