1#[cfg(windows)]
13use {
14 crate::memory_dll::{free_library_memory, get_proc_address_memory, load_library_memory},
15 pyo3::exceptions::PySystemError,
16 std::ffi::{c_void, CString},
17};
18use {
19 crate::{
20 conversion::pyobject_to_pathbuf,
21 get_module_state,
22 path_entry_finder::OxidizedPathEntryFinder,
23 pkg_resources::register_pkg_resources_with_module,
24 python_resources::{
25 pyobject_to_resource, ModuleFlavor, OxidizedResource, PythonResourcesState,
26 },
27 resource_reader::OxidizedResourceReader,
28 OXIDIZED_IMPORTER_NAME_STR,
29 },
30 pyo3::{
31 exceptions::{PyImportError, PyValueError},
32 ffi as pyffi,
33 prelude::*,
34 types::{PyBytes, PyDict, PyList, PyString, PyTuple},
35 AsPyPointer, FromPyPointer, PyNativeType, PyTraverseError, PyVisit,
36 },
37 python_packaging::resource::BytecodeOptimizationLevel,
38 std::sync::Arc,
39};
40
41#[cfg(windows)]
42#[allow(non_camel_case_types)]
43type py_init_fn = extern "C" fn() -> *mut pyffi::PyObject;
44
45#[cfg(windows)]
61fn extension_module_shared_library_create_module(
62 resources_state: &PythonResourcesState<u8>,
63 py: Python,
64 sys_modules: &PyAny,
65 spec: &PyAny,
66 name_py: &PyAny,
67 name: &str,
68 library_data: &[u8],
69) -> PyResult<Py<PyAny>> {
70 let origin = PyString::new(py, "memory");
71
72 let existing_module =
73 unsafe { pyffi::_PyImport_FindExtensionObject(name_py.as_ptr(), origin.as_ptr()) };
74
75 if !existing_module.is_null() {
77 return Ok(unsafe { PyObject::from_owned_ptr(py, existing_module) });
78 }
79
80 if !unsafe { pyffi::PyErr_Occurred() }.is_null() {
82 return Err(PyErr::fetch(py));
83 }
84
85 let module = unsafe { load_library_memory(resources_state, library_data) };
89
90 if module.is_null() {
91 return Err(PyImportError::new_err((
92 "unable to load extension module library from memory",
93 name.to_owned(),
94 )));
95 }
96
97 load_dynamic_library(py, sys_modules, spec, name_py, name, module).map_err(|e| {
101 unsafe {
102 free_library_memory(module);
103 }
104 e
105 })
106}
107
108#[cfg(unix)]
109fn extension_module_shared_library_create_module(
110 _resources_state: &PythonResourcesState<u8>,
111 _py: Python,
112 _sys_modules: &PyAny,
113 _spec: &PyAny,
114 _name_py: &PyAny,
115 _name: &str,
116 _library_data: &[u8],
117) -> PyResult<Py<PyAny>> {
118 panic!("should only be called on Windows");
119}
120
121#[cfg(windows)]
123fn load_dynamic_library(
124 py: Python,
125 sys_modules: &PyAny,
126 spec: &PyAny,
127 name_py: &PyAny,
128 name: &str,
129 library_module: *const c_void,
130) -> PyResult<Py<PyAny>> {
131 let last_name_part = if name.contains('.') {
133 name.split('.').last().unwrap()
134 } else {
135 name
136 };
137
138 let name_cstring = CString::new(name).unwrap();
139 let init_fn_name = CString::new(format!("PyInit_{}", last_name_part)).unwrap();
140
141 let address = unsafe { get_proc_address_memory(library_module, &init_fn_name) };
142 if address.is_null() {
143 return Err(PyImportError::new_err((
144 format!(
145 "dynamic module does not define module export function ({})",
146 init_fn_name.to_str().unwrap()
147 ),
148 name.to_owned(),
149 )));
150 }
151
152 let init_fn: py_init_fn = unsafe { std::mem::transmute(address) };
153
154 let py_module = unsafe {
156 let old_context = pyffi::_Py_PackageContext;
157 pyffi::_Py_PackageContext = name_cstring.as_ptr();
158 let py_module = init_fn();
159 pyffi::_Py_PackageContext = old_context;
160 py_module
161 };
162
163 if py_module.is_null() && unsafe { pyffi::PyErr_Occurred().is_null() } {
170 return Err(PySystemError::new_err(format!(
171 "initialization of {} failed without raising an exception",
172 name
173 )));
174 }
175
176 if !unsafe { pyffi::PyErr_Occurred().is_null() } {
177 unsafe {
178 pyffi::PyErr_Clear();
179 }
180 return Err(PySystemError::new_err(format!(
181 "initialization of {} raised unreported exception",
182 name
183 )));
184 }
185
186 if unsafe { pyffi::Py_TYPE(py_module) }.is_null() {
187 return Err(PySystemError::new_err(format!(
188 "init function of {} returned uninitialized object",
189 name
190 )));
191 }
192
193 if unsafe { pyffi::PyObject_TypeCheck(py_module, &mut pyffi::PyModuleDef_Type) } != 0 {
198 let py_module = unsafe {
199 pyffi::PyModule_FromDefAndSpec(py_module as *mut pyffi::PyModuleDef, spec.as_ptr())
200 };
201
202 return if py_module.is_null() {
203 Err(PyErr::fetch(py))
204 } else {
205 Ok(unsafe { PyObject::from_owned_ptr(py, py_module) })
206 };
207 }
208
209 let py_module = unsafe { PyObject::from_owned_ptr(py, py_module) };
213
214 let mut module_def = unsafe { pyffi::PyModule_GetDef(py_module.as_ptr()) };
215 if module_def.is_null() {
216 return Err(PySystemError::new_err(format!(
217 "initialization of {} did not return an extension module",
218 name
219 )));
220 }
221
222 unsafe {
223 (*module_def).m_base.m_init = Some(init_fn);
224 }
225
226 let fixup_result = unsafe {
229 pyffi::_PyImport_FixupExtensionObject(
230 py_module.as_ptr(),
231 name_py.as_ptr(),
232 name_py.as_ptr(),
233 sys_modules.as_ptr(),
234 )
235 };
236
237 if fixup_result < 0 {
238 Err(PyErr::fetch(py))
239 } else {
240 Ok(py_module)
241 }
242}
243
244pub struct ImporterState {
246 pub(crate) imp_module: Py<PyModule>,
248 pub(crate) sys_module: Py<PyModule>,
250 pub(crate) io_module: Py<PyModule>,
252 pub(crate) marshal_loads: Py<PyAny>,
254 pub(crate) builtin_importer: Py<PyAny>,
256 pub(crate) frozen_importer: Py<PyAny>,
258 pub(crate) call_with_frames_removed: Py<PyAny>,
260 pub(crate) module_spec_type: Py<PyAny>,
262 pub(crate) decode_source: Py<PyAny>,
264 pub(crate) exec_fn: Py<PyAny>,
266 pub(crate) optimize_level: BytecodeOptimizationLevel,
268 pub(crate) multiprocessing_set_start_method: Option<String>,
272 pub(crate) pkg_resources_import_auto_register: bool,
274 pub(crate) resources_state: Py<PyAny>,
289}
290
291impl ImporterState {
292 fn new<'a>(
293 py: Python,
294 importer_module: &PyModule,
295 bootstrap_module: &PyModule,
296 resources_state: Box<PythonResourcesState<'a, u8>>,
297 ) -> Result<Self, PyErr> {
298 let decode_source = importer_module.getattr("decode_source")?.into_py(py);
299
300 let io_module = py.import("_io")?.into_py(py);
301 let marshal_module = py.import("marshal")?;
302
303 let imp_module = bootstrap_module.getattr("_imp")?;
304 let imp_module = imp_module.cast_as::<PyModule>()?.into_py(py);
305 let sys_module = bootstrap_module.getattr("sys")?;
306 let sys_module = sys_module.cast_as::<PyModule>()?;
307 let meta_path_object = sys_module.getattr("meta_path")?;
308
309 let meta_path = meta_path_object.cast_as::<PyList>()?;
316 if meta_path.len() < 2 {
317 return Err(PyValueError::new_err(
318 "sys.meta_path does not contain 2 values",
319 ));
320 }
321
322 let builtin_importer = meta_path.get_item(0)?.into_py(py);
323 let frozen_importer = meta_path.get_item(1)?.into_py(py);
324
325 let marshal_loads = marshal_module.getattr("loads")?.into_py(py);
326 let call_with_frames_removed = bootstrap_module
327 .getattr("_call_with_frames_removed")?
328 .into_py(py);
329 let module_spec_type = bootstrap_module.getattr("ModuleSpec")?.into_py(py);
330
331 let builtins_module =
332 unsafe { PyDict::from_borrowed_ptr_or_err(py, pyffi::PyEval_GetBuiltins()) }?;
333
334 let exec_fn = match builtins_module.get_item("exec") {
335 Some(v) => v,
336 None => {
337 return Err(PyValueError::new_err("could not obtain __builtins__.exec"));
338 }
339 }
340 .into_py(py);
341
342 let sys_flags = sys_module.getattr("flags")?;
343 let sys_module = sys_module.into_py(py);
344
345 let optimize_value = sys_flags.getattr("optimize")?;
346 let optimize_value = optimize_value.extract::<i64>()?;
347
348 let optimize_level = match optimize_value {
349 0 => Ok(BytecodeOptimizationLevel::Zero),
350 1 => Ok(BytecodeOptimizationLevel::One),
351 2 => Ok(BytecodeOptimizationLevel::Two),
352 _ => Err(PyValueError::new_err(
353 "unexpected value for sys.flags.optimize",
354 )),
355 }?;
356
357 let capsule = unsafe {
358 let ptr = pyffi::PyCapsule_New(
359 &*resources_state as *const PythonResourcesState<u8> as *mut _,
360 std::ptr::null(),
361 None,
362 );
363
364 if ptr.is_null() {
365 return Err(PyValueError::new_err(
366 "unable to convert PythonResourcesState to capsule",
367 ));
368 }
369
370 PyObject::from_owned_ptr(py, ptr)
371 };
372
373 Box::leak(resources_state);
376
377 Ok(ImporterState {
378 imp_module,
379 sys_module,
380 io_module,
381 marshal_loads,
382 builtin_importer,
383 frozen_importer,
384 call_with_frames_removed,
385 module_spec_type,
386 decode_source,
387 exec_fn,
388 optimize_level,
389 multiprocessing_set_start_method: None,
390 pkg_resources_import_auto_register: true,
392 resources_state: capsule,
393 })
394 }
395
396 fn gc_traverse(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
402 visit.call(&self.imp_module)?;
403 visit.call(&self.sys_module)?;
404 visit.call(&self.io_module)?;
405 visit.call(&self.marshal_loads)?;
406 visit.call(&self.builtin_importer)?;
407 visit.call(&self.frozen_importer)?;
408 visit.call(&self.call_with_frames_removed)?;
409 visit.call(&self.module_spec_type)?;
410 visit.call(&self.decode_source)?;
411 visit.call(&self.exec_fn)?;
412 visit.call(&self.resources_state)?;
413
414 Ok(())
415 }
416
417 #[inline]
419 pub fn get_resources_state<'a>(&self) -> &PythonResourcesState<'a, u8> {
420 let ptr =
421 unsafe { pyffi::PyCapsule_GetPointer(self.resources_state.as_ptr(), std::ptr::null()) };
422
423 if ptr.is_null() {
424 panic!("null pointer in resources state capsule");
425 }
426
427 unsafe { &*(ptr as *const PythonResourcesState<u8>) }
428 }
429
430 #[allow(clippy::mut_from_ref)]
435 pub fn get_resources_state_mut<'a>(&self) -> &mut PythonResourcesState<'a, u8> {
436 let ptr =
437 unsafe { pyffi::PyCapsule_GetPointer(self.resources_state.as_ptr(), std::ptr::null()) };
438
439 if ptr.is_null() {
440 panic!("null pointer in resources state capsule");
441 }
442
443 unsafe { &mut *(ptr as *mut PythonResourcesState<u8>) }
444 }
445
446 #[allow(unused)]
448 pub fn set_multiprocessing_set_start_method(&mut self, value: Option<String>) {
449 self.multiprocessing_set_start_method = value;
450 }
451}
452
453impl Drop for ImporterState {
454 fn drop(&mut self) {
455 let ptr =
456 unsafe { pyffi::PyCapsule_GetPointer(self.resources_state.as_ptr(), std::ptr::null()) };
457
458 if !ptr.is_null() {
459 unsafe {
460 drop(Box::from_raw(ptr as *mut PythonResourcesState<u8>));
461 }
462 }
463 }
464}
465
466#[pyclass(module = "oxidized_importer")]
472pub struct OxidizedFinder {
473 pub(crate) state: Arc<ImporterState>,
474}
475
476impl OxidizedFinder {
477 pub(crate) fn get_state(&self) -> Arc<ImporterState> {
478 self.state.clone()
479 }
480
481 pub fn new_from_module_and_resources<'a>(
483 py: Python,
484 m: &PyModule,
485 resources_state: Box<PythonResourcesState<'a, u8>>,
486 importer_state_callback: Option<impl FnOnce(&mut ImporterState)>,
487 ) -> PyResult<OxidizedFinder> {
488 let bootstrap_module = py.import("_frozen_importlib")?;
489
490 let mut importer_state = Arc::new(ImporterState::new(
491 py,
492 m,
493 bootstrap_module,
494 resources_state,
495 )?);
496
497 if let Some(cb) = importer_state_callback {
498 let state_ref = Arc::<ImporterState>::get_mut(&mut importer_state)
499 .expect("Arc::get_mut() should work");
500 cb(state_ref);
501 }
502
503 Ok(OxidizedFinder {
504 state: importer_state,
505 })
506 }
507}
508
509#[pymethods]
510impl OxidizedFinder {
511 fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
512 self.state.gc_traverse(visit)
513 }
514
515 #[args(target = "None")]
518 fn find_spec<'p>(
519 slf: &'p PyCell<Self>,
520 fullname: String,
521 path: &PyAny,
522 target: Option<&PyAny>,
523 ) -> PyResult<&'p PyAny> {
524 let py = slf.py();
525 let finder = slf.borrow();
526
527 let module = match finder
528 .state
529 .get_resources_state()
530 .resolve_importable_module(&fullname, finder.state.optimize_level)
531 {
532 Some(module) => module,
533 None => return Ok(py.None().into_ref(py)),
534 };
535
536 match module.flavor {
537 ModuleFlavor::Extension | ModuleFlavor::SourceBytecode => module.resolve_module_spec(
538 py,
539 finder.state.module_spec_type.clone_ref(py).into_ref(py),
540 slf,
541 finder.state.optimize_level,
542 ),
543 ModuleFlavor::Builtin => {
544 Ok(finder
547 .state
548 .builtin_importer
549 .call_method(py, "find_spec", (fullname,), None)?
550 .into_ref(py))
551 }
552 ModuleFlavor::Frozen => Ok(finder
553 .state
554 .frozen_importer
555 .call_method(py, "find_spec", (fullname, path, target), None)?
556 .into_ref(py)),
557 }
558 }
559
560 fn find_module<'p>(
561 slf: &'p PyCell<Self>,
562 fullname: &PyAny,
563 path: &PyAny,
564 ) -> PyResult<&'p PyAny> {
565 let find_spec = slf.getattr("find_spec")?;
566 let spec = find_spec.call((fullname, path), None)?;
567
568 if spec.is_none() {
569 Ok(slf.py().None().into_ref(slf.py()))
570 } else {
571 spec.getattr("loader")
572 }
573 }
574
575 fn invalidate_caches(&self) -> PyResult<()> {
576 Ok(())
577 }
578
579 fn create_module(slf: &PyCell<Self>, spec: &PyAny) -> PyResult<Py<PyAny>> {
584 let py = slf.py();
585 let finder = slf.borrow();
586 let state = &finder.state;
587
588 let name = spec.getattr("name")?;
589 let key = name.extract::<String>()?;
590
591 let module = match state
592 .get_resources_state()
593 .resolve_importable_module(&key, state.optimize_level)
594 {
595 Some(module) => module,
596 None => return Ok(py.None()),
597 };
598
599 if module.flavor == ModuleFlavor::Extension {
601 if let Some(library_data) = &module.in_memory_extension_module_shared_library() {
610 let sys_modules = state.sys_module.getattr(py, "modules")?;
611
612 extension_module_shared_library_create_module(
613 state.get_resources_state(),
614 py,
615 sys_modules.into_ref(py),
616 spec,
617 name,
618 &key,
619 library_data,
620 )
621 } else {
622 let create_dynamic = state.imp_module.getattr(py, "create_dynamic")?;
624
625 state
626 .call_with_frames_removed
627 .call(py, (&create_dynamic, spec), None)
628 }
629 } else {
630 Ok(py.None())
631 }
632 }
633
634 fn exec_module(slf: &PyCell<Self>, module: &PyAny) -> PyResult<Py<PyAny>> {
635 let py = slf.py();
636 let finder = slf.borrow();
637 let state = &finder.state;
638
639 let name = module.getattr("__name__")?;
640 let key = name.extract::<String>()?;
641
642 let mut entry = match state
643 .get_resources_state()
644 .resolve_importable_module(&key, state.optimize_level)
645 {
646 Some(entry) => entry,
647 None => {
648 return Ok(py.None());
651 }
652 };
653
654 if let Some(bytecode) = entry.resolve_bytecode(
655 py,
656 state.optimize_level,
657 state.decode_source.as_ref(py),
658 state.io_module.as_ref(py),
659 )? {
660 let code = state.marshal_loads.call(py, (bytecode,), None)?;
661 let dict = module.getattr("__dict__")?;
662
663 state
664 .call_with_frames_removed
665 .call(py, (&state.exec_fn, code, dict), None)
666 } else if entry.flavor == ModuleFlavor::Builtin {
667 state
668 .builtin_importer
669 .call_method(py, "exec_module", (module,), None)
670 } else if entry.flavor == ModuleFlavor::Frozen {
671 state
672 .frozen_importer
673 .call_method(py, "exec_module", (module,), None)
674 } else if entry.flavor == ModuleFlavor::Extension {
675 let exec_dynamic = state.imp_module.getattr(py, "exec_dynamic")?;
677
678 state
679 .call_with_frames_removed
680 .call(py, (&exec_dynamic, module), None)
681 } else {
682 Ok(py.None())
683 }?;
684
685 match key.as_str() {
687 "multiprocessing" => {
688 if let Some(method) = state.multiprocessing_set_start_method.as_ref() {
689 let kwargs = PyDict::new(py);
691 kwargs.set_item("force", true)?;
692 module.call_method("set_start_method", (method,), Some(kwargs))?;
693 }
694 }
695 "pkg_resources" => {
696 if state.pkg_resources_import_auto_register {
697 register_pkg_resources_with_module(py, module)?;
698 }
699 }
700 _ => {}
701 }
702
703 Ok(py.None())
704 }
705
706 fn get_data<'p>(slf: &'p PyCell<Self>, path: &str) -> PyResult<&'p PyAny> {
718 slf.borrow()
719 .state
720 .get_resources_state()
721 .resolve_resource_data_from_path(slf.py(), path)
722 }
723
724 fn get_code(slf: &PyCell<Self>, fullname: &str) -> PyResult<Py<PyAny>> {
729 let py = slf.py();
730 let finder = slf.borrow();
731 let state = &finder.state;
732
733 let key = fullname.to_string();
734
735 let mut module = match state
736 .get_resources_state()
737 .resolve_importable_module(&key, state.optimize_level)
738 {
739 Some(module) => module,
740 None => return Ok(py.None()),
741 };
742
743 if let Some(bytecode) = module.resolve_bytecode(
744 py,
745 state.optimize_level,
746 state.decode_source.as_ref(py),
747 state.io_module.as_ref(py),
748 )? {
749 state.marshal_loads.call(py, (bytecode,), None)
750 } else if module.flavor == ModuleFlavor::Frozen {
751 state
752 .imp_module
753 .call_method(py, "get_frozen_object", (fullname,), None)
754 } else {
755 Ok(py.None())
756 }
757 }
758
759 fn get_source(slf: &PyCell<Self>, fullname: &str) -> PyResult<Py<PyAny>> {
760 let py = slf.py();
761 let finder = slf.borrow();
762 let state = &finder.state;
763 let key = fullname.to_string();
764
765 let module = match state
766 .get_resources_state()
767 .resolve_importable_module(&key, state.optimize_level)
768 {
769 Some(module) => module,
770 None => return Ok(py.None()),
771 };
772
773 let source = module.resolve_source(
774 py,
775 state.decode_source.as_ref(py),
776 state.io_module.as_ref(py),
777 )?;
778
779 Ok(if let Some(source) = source {
780 source.into_py(py)
781 } else {
782 py.None()
783 })
784 }
785
786 fn get_filename<'p>(slf: &'p PyCell<Self>, fullname: &str) -> PyResult<&'p PyAny> {
795 let finder = slf.borrow();
796 let state = &finder.state;
797 let key = fullname.to_string();
798
799 let make_error =
800 |msg: &str| -> PyErr { PyImportError::new_err((msg.to_owned(), key.clone())) };
801
802 let module = state
803 .get_resources_state()
804 .resolve_importable_module(&key, state.optimize_level)
805 .ok_or_else(|| make_error("unknown module"))?;
806
807 module
808 .resolve_origin(slf.py())
809 .map_err(|_| make_error("unable to resolve origin"))?
810 .ok_or_else(|| make_error("no origin"))
811 }
812
813 fn get_resource_reader(slf: &PyCell<Self>, fullname: &str) -> PyResult<Py<PyAny>> {
820 let finder = slf.borrow();
821 let state = &finder.state;
822 let key = fullname.to_string();
823
824 let entry = match state
825 .get_resources_state()
826 .resolve_importable_module(&key, state.optimize_level)
827 {
828 Some(entry) => entry,
829 None => return Ok(slf.py().None()),
830 };
831
832 if entry.is_package {
834 Ok(PyCell::new(
835 slf.py(),
836 OxidizedResourceReader::new(state.clone(), key.to_string()),
837 )?
838 .into_py(slf.py()))
839 } else {
840 Ok(slf.py().None())
841 }
842 }
843
844 #[args(context = "None")]
861 fn find_distributions<'p>(
862 slf: &'p PyCell<Self>,
863 context: Option<&PyAny>,
864 ) -> PyResult<&'p PyAny> {
865 let py = slf.py();
866 let finder = slf.borrow();
867 let state = &finder.state;
868
869 let (path, name) = if let Some(context) = context {
870 let path = context.getattr("path")?;
873 let path = if path.is_none() { None } else { Some(path) };
874
875 let name = context.getattr("name")?;
876 let name = if name.is_none() { None } else { Some(name) };
877
878 (path, name)
879 } else {
880 (None, None)
882 };
883
884 crate::package_metadata::find_distributions(py, state.clone(), name, path)?
885 .call_method0("__iter__")
886 }
887
888 #[args(prefix = "None")]
892 fn iter_modules<'p>(slf: &'p PyCell<Self>, prefix: Option<&str>) -> PyResult<&'p PyList> {
893 let finder = slf.borrow();
894 let state = &finder.state;
895
896 let resources_state = state.get_resources_state();
897
898 let prefix = prefix.map(|prefix| prefix.to_string());
899
900 resources_state.pkgutil_modules_infos(slf.py(), None, prefix, state.optimize_level)
901 }
902
903 #[new]
907 #[args(relative_path_origin = "None")]
908 fn new(py: Python, relative_path_origin: Option<&PyAny>) -> PyResult<Self> {
909 let m = py.import(OXIDIZED_IMPORTER_NAME_STR)?;
914 let bootstrap_module = py.import("_frozen_importlib")?;
915
916 let mut resources_state =
917 Box::new(PythonResourcesState::new_from_env().map_err(PyValueError::new_err)?);
918
919 if let Some(py_origin) = relative_path_origin {
921 resources_state.set_origin(pyobject_to_pathbuf(py, py_origin)?);
922 }
923
924 Ok(OxidizedFinder {
925 state: Arc::new(ImporterState::new(
926 py,
927 m,
928 bootstrap_module,
929 resources_state,
930 )?),
931 })
932 }
933
934 #[getter]
935 fn multiprocessing_set_start_method(&self) -> PyResult<Option<String>> {
936 if let Some(v) = &self.state.multiprocessing_set_start_method {
937 Ok(Some(v.to_string()))
938 } else {
939 Ok(None)
940 }
941 }
942
943 #[getter]
944 fn origin<'p>(&self, py: Python<'p>) -> &'p PyAny {
945 self.state
946 .get_resources_state()
947 .origin()
948 .into_py(py)
949 .into_ref(py)
950 }
951
952 #[getter]
953 fn path_hook_base_str<'p>(&self, py: Python<'p>) -> &'p PyAny {
954 self.state
955 .get_resources_state()
956 .current_exe()
957 .into_py(py)
958 .into_ref(py)
959 }
960
961 #[getter]
962 fn pkg_resources_import_auto_register(&self) -> PyResult<bool> {
963 Ok(self.state.pkg_resources_import_auto_register)
964 }
965
966 fn path_hook(slf: &PyCell<Self>, path: &PyAny) -> PyResult<OxidizedPathEntryFinder> {
967 Self::path_hook_inner(slf, path).map_err(|inner| {
968 let err = PyImportError::new_err("error running OxidizedFinder.path_hook");
969
970 if let Err(err) = err.value(slf.py()).setattr("__suppress_context__", true) {
971 err
972 } else if let Err(err) = err
973 .value(slf.py())
974 .setattr("__cause__", inner.value(slf.py()))
975 {
976 err
977 } else {
978 err
979 }
980 })
981 }
982
983 fn index_bytes(&self, py: Python, data: &PyAny) -> PyResult<()> {
984 self.state
985 .get_resources_state_mut()
986 .index_pyobject(py, data)?;
987
988 Ok(())
989 }
990
991 fn index_file_memory_mapped(&self, py: Python, path: &PyAny) -> PyResult<()> {
992 let path = pyobject_to_pathbuf(py, path)?;
993
994 self.state
995 .get_resources_state_mut()
996 .index_path_memory_mapped(path)
997 .map_err(PyValueError::new_err)?;
998
999 Ok(())
1000 }
1001
1002 fn index_interpreter_builtins(&self) -> PyResult<()> {
1003 self.state
1004 .get_resources_state_mut()
1005 .index_interpreter_builtins()
1006 .map_err(PyValueError::new_err)?;
1007
1008 Ok(())
1009 }
1010
1011 fn index_interpreter_builtin_extension_modules(&self) -> PyResult<()> {
1012 self.state
1013 .get_resources_state_mut()
1014 .index_interpreter_builtin_extension_modules()
1015 .map_err(PyValueError::new_err)?;
1016
1017 Ok(())
1018 }
1019
1020 fn index_interpreter_frozen_modules(&self) -> PyResult<()> {
1021 self.state
1022 .get_resources_state_mut()
1023 .index_interpreter_frozen_modules()
1024 .map_err(PyValueError::new_err)?;
1025
1026 Ok(())
1027 }
1028
1029 fn indexed_resources<'p>(&self, py: Python<'p>) -> PyResult<&'p PyList> {
1030 let resources_state = self.state.get_resources_state();
1031
1032 resources_state.resources_as_py_list(py)
1033 }
1034
1035 fn add_resource(&self, resource: &OxidizedResource) -> PyResult<()> {
1036 let resources_state = self.state.get_resources_state_mut();
1037
1038 resources_state
1039 .add_resource(pyobject_to_resource(resource))
1040 .map_err(|_| PyValueError::new_err("unable to add resource to finder"))?;
1041
1042 Ok(())
1043 }
1044
1045 fn add_resources(&self, resources: &PyAny) -> PyResult<()> {
1046 let resources_state = self.state.get_resources_state_mut();
1047
1048 for resource in resources.iter()? {
1049 let resource_raw = resource?;
1050 let resource = resource_raw.cast_as::<PyCell<OxidizedResource>>()?;
1051
1052 resources_state
1053 .add_resource(pyobject_to_resource(&resource.borrow()))
1054 .map_err(|_| PyValueError::new_err("unable to add resource to finder"))?;
1055 }
1056
1057 Ok(())
1058 }
1059
1060 #[args(ignore_builtin = true, ignore_frozen = true)]
1061 fn serialize_indexed_resources<'p>(
1062 &self,
1063 py: Python<'p>,
1064 ignore_builtin: bool,
1065 ignore_frozen: bool,
1066 ) -> PyResult<&'p PyBytes> {
1067 let resources_state = self.state.get_resources_state();
1068
1069 let data = resources_state
1070 .serialize_resources(ignore_builtin, ignore_frozen)
1071 .map_err(|e| PyValueError::new_err(format!("error serializing: {}", e)))?;
1072
1073 Ok(PyBytes::new(py, &data))
1074 }
1075}
1076
1077impl OxidizedFinder {
1078 fn path_hook_inner(
1079 slf: &PyCell<Self>,
1080 path_original: &PyAny,
1081 ) -> PyResult<OxidizedPathEntryFinder> {
1082 let py = slf.py();
1083 let finder = slf.borrow();
1084
1085 let path = path_original.cast_as::<PyString>()?;
1103
1104 let path_hook_base = finder.path_hook_base_str(py).cast_as::<PyString>()?;
1105
1106 let target_package = if path.compare(path_hook_base)? == std::cmp::Ordering::Equal {
1107 None
1108 } else {
1109 let unix_prefix = path_hook_base.call_method("__add__", ("/",), None)?;
1111 let windows_prefix = path_hook_base.call_method("__add__", ("\\",), None)?;
1112
1113 let prefix = PyTuple::new(py, [unix_prefix, windows_prefix]);
1114
1115 if !path
1116 .call_method("startswith", (prefix,), None)?
1117 .extract::<bool>()?
1118 {
1119 return Err(PyValueError::new_err(format!(
1120 "{} is not prefixed by {}",
1121 path.to_string_lossy(),
1122 path_hook_base.to_string_lossy()
1123 )));
1124 }
1125
1126 let path_hook_base_bytes = path_hook_base
1140 .call_method("encode", ("utf-8", "replace"), None)?
1141 .extract::<Vec<u8>>()?;
1142 let path_bytes = path
1143 .call_method("encode", ("utf-8", "replace"), None)?
1144 .extract::<Vec<u8>>()?;
1145
1146 let path_suffix: &[u8] = &path_bytes[path_hook_base_bytes.len() + 1..];
1148 let original_package_path = String::from_utf8(path_suffix.to_vec()).map_err(|e| {
1149 PyValueError::new_err(format!(
1150 "error coercing package suffix to Rust string: {}",
1151 e
1152 ))
1153 })?;
1154
1155 let package_path = original_package_path.replace('\\', "/");
1156
1157 if package_path.starts_with('/') || package_path.ends_with('/') {
1159 return Err(PyValueError::new_err(
1160 format!("rejecting virtual sub-directory because package part contains leading or trailing directory separator: {}", original_package_path)));
1161 }
1162
1163 if package_path.contains("//") {
1165 return Err(PyValueError::new_err(format!("rejecting virtual sub-directory because it has consecutive directory separators: {}", original_package_path)));
1166 }
1167
1168 if package_path
1171 .split('/')
1172 .any(|s| s.starts_with('.') || s.ends_with('.') || s.contains(".."))
1173 {
1174 return Err(PyValueError::new_err(
1175 format!("rejecting virtual sub-directory because package part contains illegal dot characters: {}", original_package_path)
1176
1177 ));
1178 }
1179
1180 if package_path.is_empty() {
1181 None
1182 } else {
1183 Some(package_path.replace('/', "."))
1184 }
1185 };
1186
1187 Ok(OxidizedPathEntryFinder {
1188 finder: PyCell::new(
1189 py,
1190 OxidizedFinder {
1191 state: finder.state.clone(),
1192 },
1193 )?
1194 .into(),
1195 source_path: path.into_py(py),
1196 target_package,
1197 })
1198 }
1199}
1200
1201#[pyclass(module = "oxidized_importer")]
1205pub(crate) struct PyOxidizerTraversable {
1206 state: Arc<ImporterState>,
1207 path: String,
1208}
1209
1210#[pymethods]
1211impl PyOxidizerTraversable {
1212 fn iterdir(&self) -> PyResult<&PyAny> {
1214 unimplemented!()
1215 }
1216
1217 fn read_bytes(&self) -> PyResult<&PyAny> {
1219 unimplemented!()
1220 }
1221
1222 fn read_text(&self) -> PyResult<&PyAny> {
1224 unimplemented!()
1225 }
1226
1227 fn is_dir(&self) -> PyResult<bool> {
1229 if let Some(entry) = self
1234 .state
1235 .get_resources_state()
1236 .resolve_importable_module(&self.path, self.state.optimize_level)
1237 {
1238 if entry.is_package {
1239 return Ok(true);
1240 }
1241 }
1242
1243 Ok(false)
1244 }
1245
1246 fn is_file(&self) -> PyResult<&PyAny> {
1248 unimplemented!()
1249 }
1250
1251 #[allow(unused)]
1253 fn joinpath(&self, child: &PyAny) -> PyResult<&PyAny> {
1254 unimplemented!()
1255 }
1256
1257 #[allow(unused)]
1259 fn __truediv__(&self, child: &PyAny) -> PyResult<&PyAny> {
1260 unimplemented!()
1261 }
1262
1263 #[allow(unused)]
1269 #[args(py_args = "*", py_kwargs = "**")]
1270 fn open(&self, py_args: &PyTuple, py_kwargs: Option<&PyDict>) -> PyResult<&PyAny> {
1271 unimplemented!()
1272 }
1273}
1274
1275pub fn replace_meta_path_importers<'a, 'p>(
1283 py: Python<'p>,
1284 oxidized_importer: &PyModule,
1285 resources_state: Box<PythonResourcesState<'a, u8>>,
1286 importer_state_callback: Option<impl FnOnce(&mut ImporterState)>,
1287) -> PyResult<&'p PyCell<OxidizedFinder>> {
1288 let mut state = get_module_state(oxidized_importer)?;
1289
1290 let sys_module = py.import("sys")?;
1291
1292 let oxidized_finder = PyCell::new(
1297 py,
1298 OxidizedFinder::new_from_module_and_resources(
1299 py,
1300 oxidized_importer,
1301 resources_state,
1302 importer_state_callback,
1303 )?,
1304 )?;
1305
1306 let meta_path_object = sys_module.getattr("meta_path")?;
1307
1308 meta_path_object.call_method0("clear")?;
1309 meta_path_object.call_method("append", (oxidized_finder,), None)?;
1310
1311 state.initialized = true;
1312
1313 Ok(oxidized_finder)
1314}
1315
1316pub fn remove_external_importers(sys_module: &PyModule) -> PyResult<()> {
1321 let meta_path = sys_module.getattr("meta_path")?;
1322 let meta_path = meta_path.cast_as::<PyList>()?;
1323
1324 let mut oxidized_path_hooks = vec![];
1328 let mut index = 0;
1329 while index < meta_path.len() {
1330 let entry = meta_path.get_item(index as _)?;
1331
1332 if entry.get_type().to_string().contains("OxidizedFinder") {
1341 oxidized_path_hooks.push(entry.getattr("path_hook")?);
1342 index += 1;
1343 } else if entry
1344 .getattr("__module__")?
1345 .cast_as::<PyString>()?
1346 .to_string_lossy()
1347 == "_frozen_importlib"
1348 {
1349 index += 1;
1350 } else {
1351 meta_path.call_method1("pop", (index,))?;
1352 }
1353 }
1354
1355 let path_hooks = sys_module.getattr("path_hooks")?;
1356 let path_hooks = path_hooks.cast_as::<PyList>()?;
1357
1358 let mut index = 0;
1359 while index < path_hooks.len() {
1360 let entry = path_hooks.get_item(index as _)?;
1361
1362 let mut found = false;
1363 for candidate in oxidized_path_hooks.iter() {
1364 if candidate.eq(entry)? {
1365 found = true;
1366 break;
1367 }
1368 }
1369 if found {
1370 index += 1;
1371 } else {
1372 path_hooks.call_method1("pop", (index,))?;
1373 }
1374 }
1375
1376 Ok(())
1377}
1378
1379pub fn install_path_hook(finder: &PyAny, sys: &PyModule) -> PyResult<()> {
1386 let hook = finder.getattr("path_hook")?;
1387 let path_hooks = sys.getattr("path_hooks")?;
1388 path_hooks
1389 .call_method("insert", (0, hook), None)
1390 .map(|_| ())
1391}