dotnetrawfilereader_sys/
runtime.rs1use std::fmt::Debug;
8use std::fs;
9use std::env;
10use std::io::{self, prelude::*};
11use std::path::{self, Path, PathBuf};
12use std::sync::{Arc, OnceLock, RwLock};
13
14use include_dir::{include_dir, Dir};
15use tempfile::{TempDir, Builder as TempDirBuilder};
16
17use netcorehost::{hostfxr::AssemblyDelegateLoader, nethost, pdcstring::PdCString};
18
19use crate::buffer::configure_allocator;
20
21static DOTNET_LIB_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/lib/");
22
23const TMP_NAME: &str = concat!("rawfilereader_libs_", env!("CARGO_PKG_VERSION"));
24const DEFAULT_VAR_NAME: &str = "DOTNET_RAWFILEREADER_BUNDLE_PATH";
25
26
27#[derive(Debug, thiserror::Error)]
29pub enum DotNetRuntimeCreationError {
30 #[error("Failed to create a directory on the file system to hold .NET DLLs")]
32 FailedToWriteDLLBundle(#[source] io::Error),
33 #[error("Failed to load hostfxr. Is there a .NET runtime available?")]
35 LoadHostfxrError(#[source] #[from] nethost::LoadHostfxrError),
36 #[error("Failed to load .NET host runtime. Is there a .NET runtime available?")]
38 HostingError(#[source] #[from] netcorehost::error::HostingError),
39 #[error(transparent)]
41 IOError(io::Error)
42}
43
44
45#[derive(Debug)]
47pub enum BundleStore {
48 TempDir(TempDir),
50 Path(PathBuf),
52}
53
54#[derive()]
61pub struct DotNetLibraryBundle {
62 dir: BundleStore,
64 assembly_loader: RwLock<Option<Arc<AssemblyDelegateLoader>>>,
66}
67
68impl Debug for DotNetLibraryBundle {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("DotNetLibraryBundle").field("dir", &self.dir).field("assembly_loader", &"?").finish()
71 }
72}
73
74impl Default for DotNetLibraryBundle {
75 fn default() -> Self {
76 match env::var(DEFAULT_VAR_NAME) {
77 Ok(val) => Self::new(Some(&val)).unwrap(),
78 Err(err) => {
79 match err {
80 env::VarError::NotPresent => Self::new(None).unwrap(),
81 env::VarError::NotUnicode(err) => {
82 eprintln!("Failed to decode `{DEFAULT_VAR_NAME}` {}", err.to_string_lossy());
83 Self::new(None).unwrap()
84 },
85 }
86 },
87 }
88 }
89}
90
91impl DotNetLibraryBundle {
92 pub fn new(dir: Option<&str>) -> io::Result<Self> {
95 let dir = if let Some(path) = dir {
96 let pathbuf = PathBuf::from(path);
97 if !pathbuf.exists() {
98 fs::create_dir_all(&pathbuf)?;
99 }
100 BundleStore::Path(pathbuf)
101 } else {
102 env::var(DEFAULT_VAR_NAME).map(|path| -> io::Result<BundleStore> {
103 let pathbuf = PathBuf::from(path);
104 if !pathbuf.exists() {
105 fs::create_dir_all(&pathbuf)?;
106 }
107 Ok(BundleStore::Path(pathbuf))
108 }).unwrap_or_else(|_| {
109 Ok(BundleStore::TempDir(TempDirBuilder::new().prefix(TMP_NAME).tempdir()?))
110 })?
111 };
112 Ok(Self {
113 dir,
114 assembly_loader: RwLock::new(None),
115 })
116 }
117
118 pub fn path(&self) -> &path::Path {
120 match &self.dir {
121 BundleStore::TempDir(d) => d.path(),
122 BundleStore::Path(d) => d.as_path(),
123 }
124 }
125
126 pub fn runtime(&self) -> Arc<AssemblyDelegateLoader> {
132 self.try_create_runtime().unwrap()
133 }
134
135 pub fn try_runtime(&self) -> Result<Arc<AssemblyDelegateLoader>, DotNetRuntimeCreationError> {
140 if let Ok(mut guard) = self.assembly_loader.write() {
141 if guard.is_none() {
142 *guard = Some(self.try_create_runtime()?);
143 }
144 }
145 let a = self
146 .assembly_loader
147 .read()
148 .map(|a| a.clone().unwrap())
149 .unwrap();
150 Ok(a)
151 }
152
153 pub fn write_bundle(&self) -> io::Result<()> {
155 let path = self.path();
156 let do_write = if !path.exists() {
157 fs::create_dir_all(path)?;
158 true
159 } else if path.join("checksum").exists(){
160 let checksum = fs::read(path.join("checksum"))?;
161 let lib_checksum = DOTNET_LIB_DIR.get_file("checksum").unwrap().contents();
162 checksum != lib_checksum
163 } else {
164 true
165 };
166
167 if !do_write {
168 return Ok(())
169 }
170
171 for entry in DOTNET_LIB_DIR.entries() {
172 if let Some(data_handle) = entry.as_file() {
173 let destintation = path.join(entry.path());
174 let mut outhandle = io::BufWriter::new(fs::File::create(destintation)?);
175 outhandle.write_all(data_handle.contents())?;
176 }
177 }
178
179 Ok(())
180 }
181
182 pub fn try_create_runtime(&self) -> Result<Arc<AssemblyDelegateLoader>, DotNetRuntimeCreationError> {
183 let hostfxr = nethost::load_hostfxr()?;
184 self.write_bundle().map_err(|e| {
185 match e.kind() {
186 io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound => DotNetRuntimeCreationError::FailedToWriteDLLBundle(e),
187 _ => DotNetRuntimeCreationError::IOError(e),
188 }
189 })?;
190
191 let runtime_path = self.path().join("librawfilereader.runtimeconfig.json");
192 let runtime_path_encoded: PdCString = runtime_path.to_string_lossy().parse().unwrap();
193
194 let context = hostfxr
195 .initialize_for_runtime_config(runtime_path_encoded)?;
196
197
198 let assembly_path = self.path().join("librawfilereader.dll");
199 let assembly_path_encoded: PdCString = assembly_path.to_string_lossy().parse().unwrap();
200
201 let delegate_loader = Arc::new(
202 context
203 .get_delegate_loader_for_assembly(assembly_path_encoded)?
204 );
205
206 configure_allocator(&delegate_loader);
207 Ok(delegate_loader)
208 }
209}
210
211static BUNDLE: OnceLock<DotNetLibraryBundle> = OnceLock::new();
212
213pub fn set_runtime_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
215 let path: &Path = path.as_ref();
216 if !path.exists() {
217 fs::DirBuilder::new().recursive(true).create(path)?;
218 }
219
220 let bundle = DotNetLibraryBundle::new(Some(path.to_str().unwrap())).unwrap();
221 BUNDLE.set(bundle).unwrap();
222 Ok(())
223}
224
225pub fn get_runtime() -> Arc<AssemblyDelegateLoader> {
229 try_get_runtime().unwrap()
230}
231
232
233pub fn try_get_runtime() -> Result<Arc<AssemblyDelegateLoader>, DotNetRuntimeCreationError> {
236 let bundle = BUNDLE.get_or_init(DotNetLibraryBundle::default);
237
238 bundle.try_runtime()
239}
240
241
242#[cfg(test)]
243mod test {
244 use super::*;
245
246 #[test]
247 fn test_bundle_writing() -> io::Result<()> {
248 let handle = DotNetLibraryBundle::new(None)?;
249 let _runtime = handle.runtime();
250 Ok(())
251 }
252}