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