1use std::{collections::HashSet, ffi::CStr, ptr::null_mut};
2
3use dynlink::{
4 compartment::CompartmentId,
5 context::{Context, LoadIds, NewCompartmentFlags},
6 engines::LoadCtx,
7 library::{AllowedGates, LibraryId, UnloadedLibrary},
8 DynlinkError,
9};
10use happylock::ThreadKey;
11use monitor_api::SharedCompConfig;
12use twizzler_rt_abi::{
13 core::{CtorSet, RuntimeInfo},
14 error::{GenericError, TwzError},
15 object::{MapFlags, ObjID},
16};
17
18use super::{
19 CompConfigObject, CompartmentMgr, RunComp, StackObject, COMP_DESTRUCTED, COMP_EXITED,
20 COMP_IS_BINARY, COMP_READY, COMP_STARTED,
21};
22use crate::mon::{
23 get_monitor,
24 space::{MapHandle, Space},
25 thread::DEFAULT_STACK_SIZE,
26 Monitor,
27};
28
29#[derive(Debug)]
31pub struct RunCompLoader {
32 loaded_extras: Vec<LoadInfo>,
33 root_comp: LoadInfo,
34}
35
36#[derive(Debug, Clone)]
38struct LoadInfo {
39 #[allow(dead_code)]
41 root_id: LibraryId,
42 #[allow(dead_code)]
44 rt_id: LibraryId,
45 sctx_id: ObjID,
47 name: String,
48 comp_id: CompartmentId,
49 ctor_info: Vec<CtorSet>,
51 entry: extern "C" fn(*const RuntimeInfo) -> !,
53 is_binary: bool,
54}
55
56impl LoadInfo {
57 fn new(
58 dynlink: &mut Context,
59 root_id: LibraryId,
60 rt_id: LibraryId,
61 sctx_id: ObjID,
62 is_binary: bool,
63 extras: &[LibraryId],
64 ) -> Result<Self, DynlinkError> {
65 let lib = dynlink.get_library(rt_id)?;
66 let extra_ctors: Vec<_> = extras
67 .iter()
68 .map(|extra| dynlink.build_ctors_list(*extra, Some(lib.compartment())))
69 .try_collect()?;
70 let mut root_ctors = dynlink.build_ctors_list(root_id, Some(lib.compartment()))?;
71 let mut ctor_info: Vec<_> = extra_ctors.iter().flatten().copied().collect();
72 ctor_info.append(&mut root_ctors);
73 Ok(Self {
74 root_id,
75 rt_id,
76 comp_id: lib.compartment(),
77 sctx_id,
78 name: dynlink.get_compartment(lib.compartment())?.name.clone(),
79 ctor_info,
80 entry: lib.get_entry_address()?,
81 is_binary,
82 })
83 }
84
85 fn build_runcomp(
86 &self,
87 handle: MapHandle,
88 stack_object: StackObject,
89 is_debugging: bool,
90 ) -> Result<RunComp, DynlinkError> {
91 let comp_config =
92 CompConfigObject::new(handle, SharedCompConfig::new(self.sctx_id, null_mut()));
93
94 let flags = if self.is_binary { COMP_IS_BINARY } else { 0 };
95 Ok(RunComp::new(
96 self.sctx_id,
97 self.sctx_id,
98 self.name.clone(),
99 self.comp_id,
100 vec![],
101 comp_config,
102 flags,
103 stack_object,
104 self.entry as usize,
105 &self.ctor_info,
106 is_debugging,
107 ))
108 }
109}
110
111impl Drop for RunCompLoader {
112 fn drop(&mut self) {
113 tracing::warn!("drop RunCompLoader: TODO");
114 }
115}
116
117const RUNTIME_NAME: &str = "libtwz_rt.so";
118
119impl RunCompLoader {
120 fn maybe_inject_runtime(
123 dynlink: &mut Context,
124 root_id: LibraryId,
125 comp_id: CompartmentId,
126 load_ctx: &mut LoadCtx,
127 ) -> Result<LibraryId, DynlinkError> {
128 if let Some(id) = dynlink.lookup_library(comp_id, RUNTIME_NAME) {
129 return Ok(id);
130 }
131
132 let rt_unlib = UnloadedLibrary::new(RUNTIME_NAME);
133 let loads = dynlink.load_library_in_compartment(
134 comp_id,
135 rt_unlib,
136 AllowedGates::Private,
137 load_ctx,
138 )?;
139 dynlink.add_manual_dependency(root_id, loads[0].lib);
140 Ok(loads[0].lib)
141 }
142
143 pub fn new(
146 dynlink: &mut Context,
147 comp_name: &str,
148 root_unlib: UnloadedLibrary,
149 extras: &[UnloadedLibrary],
150 new_comp_flags: NewCompartmentFlags,
151 mondebug: bool,
152 ) -> miette::Result<Self> {
153 struct UnloadOnDrop(Vec<LoadIds>);
154 impl Drop for UnloadOnDrop {
155 fn drop(&mut self) {
156 tracing::warn!("todo: drop");
157 }
158 }
159 let root_comp_id = dynlink.add_compartment(comp_name, new_comp_flags)?;
160 let allowed_gates = if new_comp_flags.contains(NewCompartmentFlags::EXPORT_GATES) {
161 AllowedGates::Public
162 } else {
163 AllowedGates::Private
164 };
165 let mut load_ctx = LoadCtx::default();
166
167 let extra_load_ids: Vec<_> = extras
168 .into_iter()
169 .map(|extra| {
170 if mondebug {
171 tracing::info!("loading ld preload library: {}", extra.name);
172 } else {
173 tracing::debug!("loading ld preload library: {}", extra.name);
174 }
175 dynlink.load_library_in_compartment(
176 root_comp_id,
177 extra.clone(),
178 AllowedGates::Private,
179 &mut load_ctx,
180 )
181 })
182 .try_collect()?;
183
184 let mut loads = UnloadOnDrop(dynlink.load_library_in_compartment(
185 root_comp_id,
186 root_unlib.clone(),
187 allowed_gates,
188 &mut load_ctx,
189 )?);
190
191 for extra in &extra_load_ids {
192 for extra in extra {
193 loads.0.push(extra.clone());
194 }
195 }
196
197 let mut cache = HashSet::new();
201 let extra_compartments = loads.0.iter().filter_map(|load| {
202 if load.comp != root_comp_id {
203 if cache.contains(&load.comp) {
206 return None;
207 }
208 cache.insert(load.comp);
209
210 let rt_id =
212 match Self::maybe_inject_runtime(dynlink, load.lib, load.comp, &mut load_ctx) {
213 Ok(id) => id,
214 Err(e) => return Some(Err(e)),
215 };
216 Some(LoadInfo::new(
217 dynlink,
218 load.lib,
219 rt_id,
220 *load_ctx.set.get(&load.comp).unwrap(),
221 false,
222 &[],
223 ))
224 } else {
225 None
226 }
227 });
228
229 let extra_compartments = DynlinkError::collect(
230 dynlink::DynlinkErrorKind::CompartmentLoadFail {
231 compartment: comp_name.to_string(),
232 },
233 extra_compartments,
234 )?;
235
236 let root_id = loads.0[0].lib;
237 let rt_id = Self::maybe_inject_runtime(dynlink, root_id, root_comp_id, &mut load_ctx)?;
238 let extra_lids = extra_load_ids
239 .iter()
240 .flatten()
241 .map(|x| x.lib)
242 .collect::<Vec<_>>();
243 for extra in &extra_lids {
244 dynlink.relocate_all(*extra)?;
245 }
246 dynlink.relocate_all(root_id)?;
247
248 let is_binary = dynlink.get_library(root_id)?.is_binary();
249 let root_comp = LoadInfo::new(
250 dynlink,
251 root_id,
252 rt_id,
253 *load_ctx.set.get(&root_comp_id).unwrap(),
254 is_binary,
255 extra_lids.as_slice(),
256 )?;
257
258 if mondebug {
259 let print_comp = |cmp: &LoadInfo| -> miette::Result<()> {
260 let dcmp = dynlink.get_compartment(cmp.comp_id)?;
261 tracing::info!("Loaded libraries for {}:", &dcmp.name);
262 for lid in dcmp.library_ids() {
263 let lib = dynlink.get_library(lid)?;
264 let mut flags = ["-", "-", "-"];
265 if lib.is_binary() {
266 flags[0] = "B";
267 } else {
268 flags[0] = "l";
269 }
270 if lib.id() == cmp.rt_id {
271 flags[1] = "r";
272 } else if lib.id() == cmp.root_id {
273 flags[1] = "R";
274 }
275 if lib.allows_gates() {
276 flags[2] = "g";
277 }
278 let flags = flags.join("");
279 tracing::info!("{:16x} {} {}", lib.base_addr(), flags, &lib.name);
280 if let Some(isg) = lib.iter_secgates() {
281 for gate in isg {
282 tracing::info!(
283 " GATE {:16x} {}",
284 gate.imp,
285 gate.name().to_string_lossy()
286 )
287 }
288 }
289 }
290 Ok(())
291 };
292 tracing::info!("Load info for {}", comp_name);
293 let _ = print_comp(&root_comp);
294 for cmp in &extra_compartments {
295 let _ = print_comp(cmp);
296 }
297 }
298
299 std::mem::forget(loads);
301 Ok(RunCompLoader {
302 loaded_extras: extra_compartments,
303 root_comp,
304 })
305 }
306
307 pub fn build_rcs(
308 self,
309 cmp: &mut CompartmentMgr,
310 dynlink: &mut Context,
311 _mondebug: bool,
312 is_debugging: bool,
313 ) -> miette::Result<ObjID> {
314 let make_new_handle = |id| {
315 Space::safe_create_and_map_runtime_object(
316 &get_monitor().space,
317 id,
318 MapFlags::READ | MapFlags::WRITE,
319 )
320 };
321
322 let root_rc = self.root_comp.build_runcomp(
323 make_new_handle(self.root_comp.sctx_id)?,
324 StackObject::new(make_new_handle(self.root_comp.sctx_id)?, DEFAULT_STACK_SIZE)?,
325 is_debugging,
326 )?;
327
328 let mut ids = vec![root_rc.instance];
329 let handles = self
331 .loaded_extras
332 .iter()
333 .map(|extra| {
334 Ok::<_, miette::Report>((
335 make_new_handle(extra.sctx_id)?,
336 StackObject::new(make_new_handle(extra.sctx_id)?, DEFAULT_STACK_SIZE)?,
337 ))
338 })
339 .try_collect::<Vec<_>>()?;
340 let mut extras = self
342 .loaded_extras
343 .iter()
344 .zip(handles)
345 .map(|extra| extra.0.build_runcomp(extra.1 .0, extra.1 .1, false))
346 .try_collect::<Vec<_>>()?;
347
348 for rc in extras.drain(..) {
349 ids.push(rc.instance);
350 cmp.insert(rc);
351 }
352 cmp.insert(root_rc);
353 std::mem::forget(self);
354
355 for id in &ids {
357 let Ok(comp) = cmp.get(*id) else { continue };
358 let mut deps = dynlink
359 .compartment_dependencies(comp.compartment_id)?
360 .iter()
361 .filter_map(|item| cmp.get_dynlinkid(*item).map(|rc| rc.instance).ok())
362 .collect();
363 cmp.get_mut(*id).unwrap().deps.append(&mut deps);
364
365 let Ok(comp) = cmp.get(*id) else { continue };
366 tracing::debug!("set comp {} deps to {:?}", comp.name, comp.deps);
367 }
368 Self::rec_inc_all_use_counts(cmp, ids[0], &HashSet::from_iter(ids.iter().cloned()));
369
370 Ok(ids[0])
371 }
372
373 fn rec_inc_all_use_counts(
374 cmgr: &mut CompartmentMgr,
375 start: ObjID,
376 created: &HashSet<ObjID>,
377 ) -> Option<()> {
378 debug_assert!(created.contains(&start));
379 let rc = cmgr.get(start).ok()?;
380 for dep in rc.deps.clone() {
381 if created.contains(&dep) {
382 Self::rec_inc_all_use_counts(cmgr, dep, created);
383 }
384 if let Ok(rc) = cmgr.get_mut(dep) {
385 rc.inc_use_count();
386 }
387 }
388
389 Some(())
390 }
391}
392
393impl Monitor {
394 pub(crate) fn start_compartment(
395 &self,
396 instance: ObjID,
397 args: &[&CStr],
398 env: &[&CStr],
399 mondebug: bool,
400 suspend_on_start: bool,
401 ) -> Result<(), TwzError> {
402 if mondebug {
403 tracing::info!("start compartment {}: {:?} {:?}", instance, args, env);
404 }
405 let deps = {
406 let cmp = self.comp_mgr.read(ThreadKey::get().unwrap());
407 let rc = cmp.get(instance)?;
408 tracing::debug!(
409 "starting compartment {} ({}) (binary = {})",
410 rc.name,
411 rc.instance,
412 rc.has_flag(COMP_IS_BINARY)
413 );
414 rc.deps.clone()
415 };
416 for dep in deps {
417 self.start_compartment(dep, &[], env, false, false)?;
418 }
419 let state = self.load_compartment_flags(instance);
421 if state & COMP_EXITED != 0 || state & COMP_DESTRUCTED != 0 {
422 tracing::error!(
423 "tried to start compartment ({:?}, {}) that has already exited (state: {:x})",
424 self.comp_name(instance),
425 instance,
426 state
427 );
428 return Err(GenericError::Internal.into());
429 }
430
431 loop {
432 let state = self.load_compartment_flags(instance);
434 if state & COMP_READY != 0 {
435 return Ok(());
436 }
437 if suspend_on_start {
438 if state & COMP_STARTED != 0 {
440 return Ok(());
441 }
442 }
443 let info = {
444 let (ref mut tmgr, ref mut cmp, ref mut dynlink, _, _) =
445 *self.locks.lock(ThreadKey::get().unwrap());
446 let rc = cmp.get_mut(instance)?;
447
448 rc.start_main_thread(
449 state,
450 &mut *tmgr,
451 &mut *dynlink,
452 args,
453 env,
454 suspend_on_start,
455 )
456 };
457 if info.is_none() {
458 return Err(GenericError::Internal.into());
459 }
460 self.wait_for_compartment_state_change(instance, state);
461 }
462 }
463}