Skip to content

Commit 56e45fe

Browse files
Merge branch 'encode-argid' of github.com:Lind-Project/lind-wasm into encode-argid
2 parents 1eef516 + aeb904b commit 56e45fe

File tree

26 files changed

+642
-331
lines changed

26 files changed

+642
-331
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
passwd: files
22
group: files
3-
hosts: files
3+
hosts: files dns
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nameserver 8.8.8.8
2+
nameserver 8.8.4.4

skip_test_cases.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
file_tests/deterministic/popen.c
2-
process_tests/deterministic/exit_failure.c
2+
signal_tests/deterministic/kill.c
33
signal_tests/deterministic/signal.c
44
signal_tests/deterministic/signal_int_thread.c
55
signal_tests/deterministic/signal_longjmp.c

src/cage/src/cage.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,100 @@ pub use std::sync::Arc;
1414
use sysdefs::constants::lind_platform_const::MAX_CAGEID;
1515
use sysdefs::data::fs_struct::SigactionStruct;
1616

17+
/// Represents how a cage terminated, mirroring the two primary POSIX
18+
/// process termination modes.
19+
///
20+
/// A process may either:
21+
/// - exit normally via `exit()` with an exit code, or
22+
/// - be terminated by a signal.
23+
///
24+
/// This enum stores the termination information in a structured form
25+
/// before it is encoded into the traditional POSIX wait status returned
26+
/// by `waitpid`.
27+
///
28+
/// TODO: Currently, Lind-Wasm only supports normal exit and signal
29+
/// termination. Job-control states such as `Stopped` and `Continued`
30+
/// are not yet implemented.
31+
#[derive(Debug, Clone, Copy)]
32+
pub enum ExitStatus {
33+
/// Process exited normally with the given exit code.
34+
/// The exit code will later be truncated to 8 bits when encoded
35+
/// into a POSIX wait status.
36+
Exited(i32),
37+
/// Process was terminated by a signal.
38+
/// The boolean indicates whether a core dump occurred.
39+
Signaled(i32, bool), // (signal, core_dump)
40+
}
41+
42+
/// A zombie child process.
43+
///
44+
/// A zombie represents a child cage that has already terminated but whose
45+
/// termination status has not yet been collected by the parent via
46+
/// `waitpid` or a related wait syscall.
47+
///
48+
/// The runtime stores the cage identifier together with the termination
49+
/// status so the parent can later retrieve it.
1750
#[derive(Debug, Clone, Copy)]
1851
pub struct Zombie {
1952
pub cageid: u64,
20-
pub exit_code: i32,
53+
pub exit_code: ExitStatus,
54+
}
55+
56+
/// Encode a structured `ExitStatus` into the traditional POSIX
57+
/// `waitpid` status integer.
58+
///
59+
/// The encoding follows the standard Unix wait status layout:
60+
///
61+
/// Normal exit:
62+
/// status = (exit_code & 0xff) << 8
63+
///
64+
/// Signal termination:
65+
/// bits 0–6 : signal number
66+
/// bit 7 : core dump flag
67+
///
68+
/// Exit codes are truncated to 8 bits to match POSIX semantics.
69+
/// This ensures that `WIFEXITED`, `WEXITSTATUS`, and related libc
70+
/// macros behave correctly.
71+
pub fn encode_wait_status(st: ExitStatus) -> i32 {
72+
match st {
73+
ExitStatus::Exited(code) => ((code & 0xff) << 8),
74+
ExitStatus::Signaled(sig, core) => {
75+
let mut s = sig & 0x7f;
76+
if core {
77+
s |= 0x80;
78+
} // core dump flag in traditional encoding
79+
s
80+
}
81+
}
82+
}
83+
84+
/// Record the final termination status of a cage.
85+
///
86+
/// This function stores the exit status that will later be reported to the
87+
/// parent when the cage becomes a zombie (e.g., via `waitpid`). The status
88+
/// may represent either a normal exit (`Exited`) or signal-based termination
89+
/// (`Signaled`).
90+
///
91+
/// The recorded status is later consumed when inserting a `Zombie` entry
92+
/// into the parent's zombie list.
93+
///
94+
/// This function is currently used on signal-based termination to record
95+
/// the signal number.
96+
///
97+
/// # Panics
98+
///
99+
/// Panics if the specified cage does not exist in the cage table.
100+
pub fn cage_record_exit_status(cageid: u64, status: ExitStatus) {
101+
let cage = get_cage(cageid).unwrap_or_else(|| {
102+
panic!(
103+
"Attempted to record exit status for non-existent cage ID {}",
104+
cageid
105+
)
106+
});
107+
let mut final_status = cage.final_exit_status.write();
108+
if final_status.is_none() {
109+
*final_status = Some(status);
110+
}
21111
}
22112

23113
#[derive(Debug)]
@@ -73,6 +163,28 @@ pub struct Cage {
73163
pub child_num: AtomicU64,
74164
// vmmap represents the virtual memory mapping for this cage. More details on `memory::vmmap`
75165
pub vmmap: RwLock<Vmmap>,
166+
// final_exit_status stores the terminal status of the cage once a
167+
// termination condition has been determined.
168+
//
169+
// This field is used as a temporary cache for the cage's final exit
170+
// status (either `Exited(code)` or `Signaled(signo, core_dump)`).
171+
// The status is recorded when the cage enters a terminal state
172+
// (e.g., exit syscall or signal-triggered termination), but before
173+
// the cage is fully cleaned up.
174+
//
175+
// The recorded value is later consumed when inserting a `Zombie`
176+
// entry into the parent cage's `zombies` list, which is what the
177+
// parent observes through `wait()` / `waitpid()`.
178+
//
179+
// This field cannot be replaced by the `exit_code` stored in
180+
// `Zombie`. A `Zombie` object only exists in the parent's zombie
181+
// list and is created during the final cleanup phase of the exiting
182+
// cage. However, the cage's termination reason may need to be
183+
// determined earlier (for example during signal handling), before
184+
// the zombie entry is created. Therefore, the cage must temporarily
185+
// store its final termination status until the zombie entry is
186+
// generated.
187+
pub final_exit_status: RwLock<Option<ExitStatus>>,
76188
}
77189

78190
/// We achieve an O(1) complexity for our cage map implementation through the following three approaches:
@@ -225,6 +337,7 @@ mod tests {
225337
zombies: RwLock::new(vec![]),
226338
child_num: AtomicU64::new(0),
227339
vmmap: RwLock::new(crate::memory::vmmap::Vmmap::new()),
340+
final_exit_status: RwLock::new(None),
228341
};
229342

230343
add_cage(2, test_cage);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* lind_constants.h
3+
*
4+
* Named constants for the Lind syscall layer.
5+
*/
6+
7+
#ifndef _LIND_CONSTANTS_H
8+
#define _LIND_CONSTANTS_H
9+
10+
/* Define NOTUSED for unused arguments */
11+
#define NOTUSED 0xdeadbeefdeadbeefULL
12+
13+
/* Define flags for errno translation
14+
* See comments in lind_syscall/lind_syscall.c for details */
15+
#define TRANSLATE_ERRNO_ON 1
16+
#define TRANSLATE_ERRNO_OFF 0
17+
18+
/* Upper bound (exclusive) of valid errno values.
19+
* Return values in the range (-MAX_ERRNO, 0) are treated as -errno
20+
* by make_threei_call() when TRANSLATE_ERRNO_ON is active. */
21+
#define MAX_ERRNO 256
22+
23+
#endif /* _LIND_CONSTANTS_H */

src/glibc/lind_syscall/lind_syscall.c

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <stdint.h> // For uint64_t definition
33
#include "addr_translation.h"
44
#include "lind_syscall_num.h"
5+
#include "lind_constants.h"
56

67
// Entry point for wasmtime, lind_syscall is an imported function from wasmtime
78
int __lind_make_syscall_trampoline(unsigned int callnumber,
@@ -88,7 +89,7 @@ int make_threei_call (unsigned int callnumber,
8889
TRANSLATE_ARG_TO_HOST(arg6, arg6cageid));
8990

9091
// if translate_errno is not enabled, we do not do any further process to errno handling and directly return the result
91-
if(translate_errno == 0) return ret;
92+
if(translate_errno == TRANSLATE_ERRNO_OFF) return ret;
9293
// handle the errno
9394
// in rawposix, we use -errno as the return value to indicate the error
9495
// but this may cause some issues for mmap syscall, because mmap syscall
@@ -97,7 +98,7 @@ int make_threei_call (unsigned int callnumber,
9798
// multiple of pages (typically 4096) even when overflow, therefore we can distinguish
9899
// the errno and mmap result by simply checking if the return value is
99100
// within the valid errno range
100-
if(ret < 0 && ret > -256)
101+
if(ret < 0 && ret > -MAX_ERRNO)
101102
{
102103
errno = -ret;
103104
return -1;
@@ -124,16 +125,16 @@ int register_handler (int64_t targetcage,
124125
{
125126
return make_threei_call(
126127
REGISTER_HANDLER_SYSCALL,
127-
0, // callname is not used in the trampoline, set to 0
128+
NOTUSED, // callname is not used in the trampoline
128129
targetcage, // pass targetcage as self_cageid
129130
targetcage, // pass targetcage as target_cageid. Self_cageid and target_cageid are the same to adapt with regular make_syscall lookup logic in 3i
130131
targetcage,
131132
targetcallnum,
132-
0, // runtime_id currently not used, set to 0
133+
NOTUSED, // runtime_id currently not used
133134
this_grate_id, // handlefunccage is the grate id of the handler function, which is the same as this_grate_id
134135
in_grate_fn_ptr_u64,
135-
0, 0, 0, 0, 0, 0, 0,
136-
0 /* translate_errno=0: we want to return the raw result without errno translation */
136+
NOTUSED, NOTUSED, NOTUSED, NOTUSED, NOTUSED, NOTUSED, NOTUSED,
137+
TRANSLATE_ERRNO_OFF /* do not translate errno: return the raw result */
137138
);
138139
}
139140

@@ -152,15 +153,15 @@ int copy_data_between_cages(uint64_t thiscage, uint64_t targetcage, uint64_t src
152153
{
153154
return make_threei_call(
154155
COPY_DATA_BETWEEN_CAGES_SYSCALL,
155-
0, // callname is not used in the trampoline, set to 0
156+
NOTUSED, // callname is not used in the trampoline
156157
thiscage, // self_cageid
157158
thiscage, // target_cageid. Self_cageid and target_cageid are the same to adapt with regular make_syscall lookup logic in 3i
158159
TRANSLATE_UADDR_TO_HOST(srcaddr, srccage),
159160
TRANSLATE_UADDR_TO_HOST(destaddr, destcage),
160-
len, 0,
161-
copytype, 0,
162-
0, 0, 0, 0,
163-
0 /* translate_errno=0: we want to return the raw result without errno translation */
161+
len, NOTUSED,
162+
copytype, NOTUSED,
163+
NOTUSED, NOTUSED, NOTUSED, NOTUSED,
164+
TRANSLATE_ERRNO_OFF /* do not translate errno: return the raw result */
164165
);
165166
}
166167

@@ -174,16 +175,16 @@ int copy_handler_table_to_cage(uint64_t thiscage, uint64_t targetcage)
174175
{
175176
return make_threei_call(
176177
COPY_HANDLER_TABLE_TO_CAGE_SYSCALL,
177-
0, // callname is not used in the trampoline, set to 0
178+
NOTUSED, // callname is not used in the trampoline
178179
thiscage, // self_cageid
179180
thiscage, // target_cageid. Self_cageid and target_cageid are the same to adapt with regular make_syscall lookup logic in 3i
180181
thiscage,
181182
targetcage,
182-
0, 0,
183-
0, 0,
184-
0, 0,
185-
0, 0,
186-
0, 0,
187-
0 /* translate_errno=0: we want to return the raw result without errno translation */
183+
NOTUSED, NOTUSED,
184+
NOTUSED, NOTUSED,
185+
NOTUSED, NOTUSED,
186+
NOTUSED, NOTUSED,
187+
NOTUSED, NOTUSED,
188+
TRANSLATE_ERRNO_OFF /* do not translate errno: return the raw result */
188189
);
189190
}

src/glibc/sysdeps/unix/syscall-template.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@
33
#include <unistd.h>
44
#include <lind_syscall.h>
55
#include <addr_translation.h>
6-
7-
// Define NOTUSED for unused arguments
8-
#define NOTUSED 0xdeadbeefdeadbeefULL
9-
10-
// Define flags for errno translation
11-
// See comments in [`lind_syscall/lind_syscall.c`] for details
12-
#define TRANSLATE_ERRNO_ON 1
13-
#define TRANSLATE_ERRNO_OFF 0
6+
#include <lind_constants.h>
147

158
/*
169
* MAKE_LEGACY_SYSCALL:

src/lind-boot/src/lind_wasmtime/execute.rs

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@ use wasmtime_wasi_threads::WasiThreadsCtx;
4646
/// entrypoint. On successful completion it waits for all cages to exit before
4747
/// shutting down RawPOSIX, ensuring runtime-wide cleanup happens only after the
4848
/// last process terminates.
49-
pub fn execute_wasmtime(lindboot_cli: CliOptions) -> anyhow::Result<Vec<Val>> {
50-
// -- Initialize the Wasmtime execution environment --
51-
let wasm_file_path = Path::new(lindboot_cli.wasm_file());
52-
let args = lindboot_cli.args.clone();
53-
let wt_config = make_wasmtime_config(lindboot_cli.wasmtime_backtrace);
54-
let engine = Engine::new(&wt_config).context("failed to create execution engine")?;
55-
let host = HostCtx::default();
56-
let mut wstore = Store::new(&engine, host);
57-
49+
pub fn execute_wasmtime(lindboot_cli: CliOptions) -> anyhow::Result<i32> {
5850
// -- Initialize Lind + RawPOSIX + 3i runtime --
5951
// Initialize the Lind cage counter
6052
let lind_manager = Arc::new(LindCageManager::new(0));
@@ -75,42 +67,29 @@ pub fn execute_wasmtime(lindboot_cli: CliOptions) -> anyhow::Result<Vec<Val>> {
7567
panic!("[lind-boot] egister syscall handlers (clone/exec/exit) with 3i failed");
7668
}
7769

78-
// -- Load module and attach host APIs --
79-
let module = read_wasm_or_cwasm(&engine, wasm_file_path)?;
80-
let mut linker = Linker::new(&engine);
81-
82-
attach_api(
83-
&mut wstore,
84-
&mut linker,
85-
&module,
86-
lind_manager.clone(),
87-
lindboot_cli.clone(),
88-
None,
89-
)?;
90-
9170
// -- Run the first module in the first cage --
92-
let result = wasmtime_wasi::runtime::with_ambient_tokio_runtime(|| {
93-
load_main_module(
94-
&mut wstore,
95-
&mut linker,
96-
&module,
97-
CAGE_START_ID as u64,
98-
&args,
99-
)
100-
.with_context(|| format!("failed to run main module"))
101-
});
71+
let result = execute_with_lind(lindboot_cli, lind_manager.clone(), CAGE_START_ID as u64);
10272

10373
match result {
104-
Ok(ref _res) => {
74+
Ok(ref ret_vals) => {
10575
// we wait until all other cage exits
10676
lind_manager.wait();
77+
// Interpret the first return value of the Wasm entry point
78+
// as the process exit code. If the module does not explicitly
79+
// return an i32, we treat it as a successful exit (code = 0).
80+
let exit_code = match ret_vals.first() {
81+
Some(Val::I32(code)) => *code,
82+
_ => 0,
83+
};
84+
// Propagate the exit code to the main, which will translate it
85+
// into the host process exit status.
86+
Ok(exit_code)
10787
}
10888
Err(e) => {
89+
// Exit the process
10990
return Err(e);
11091
}
11192
}
112-
113-
result
11493
}
11594

11695
/// Executes a Wasm program *within an existing Lind runtime* as part of an `exec()` path.
@@ -149,7 +128,7 @@ pub fn execute_with_lind(
149128
&module,
150129
lind_manager.clone(),
151130
lind_boot.clone(),
152-
Some(cageid as i32),
131+
cageid as i32,
153132
)?;
154133

155134
// -- Run the module in the cage --
@@ -271,7 +250,7 @@ fn attach_api(
271250
module: &Module,
272251
lind_manager: Arc<LindCageManager>,
273252
lindboot_cli: CliOptions,
274-
cageid: Option<i32>,
253+
cageid: i32,
275254
) -> Result<()> {
276255
// Initialize argv/environ data and attach all Lind host functions
277256
// (syscall dispatch, debug, signals, and argv/environ) to the linker.

0 commit comments

Comments
 (0)