1
1
use crate :: fdentry:: FdEntry ;
2
- use crate :: sys:: dev_null;
3
2
use crate :: { wasi, Error , Result } ;
4
3
use std:: borrow:: Borrow ;
5
4
use std:: collections:: HashMap ;
6
5
use std:: env;
7
- use std:: ffi:: CString ;
6
+ use std:: ffi:: { CString , OsString } ;
8
7
use std:: fs:: File ;
9
8
use std:: path:: { Path , PathBuf } ;
10
9
10
+ enum PendingFdEntry {
11
+ Thunk ( fn ( ) -> Result < FdEntry > ) ,
12
+ File ( File ) ,
13
+ }
14
+
15
+ impl std:: fmt:: Debug for PendingFdEntry {
16
+ fn fmt ( & self , fmt : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
17
+ match self {
18
+ PendingFdEntry :: Thunk ( f) => write ! (
19
+ fmt,
20
+ "PendingFdEntry::Thunk({:p})" ,
21
+ f as * const fn ( ) -> Result <FdEntry >
22
+ ) ,
23
+ PendingFdEntry :: File ( f) => write ! ( fmt, "PendingFdEntry::File({:?})" , f) ,
24
+ }
25
+ }
26
+ }
27
+
28
+ #[ derive( Debug , Eq , Hash , PartialEq ) ]
29
+ enum PendingCString {
30
+ Bytes ( Vec < u8 > ) ,
31
+ OsString ( OsString ) ,
32
+ }
33
+
34
+ impl From < Vec < u8 > > for PendingCString {
35
+ fn from ( bytes : Vec < u8 > ) -> Self {
36
+ Self :: Bytes ( bytes)
37
+ }
38
+ }
39
+
40
+ impl From < OsString > for PendingCString {
41
+ fn from ( s : OsString ) -> Self {
42
+ Self :: OsString ( s)
43
+ }
44
+ }
45
+
46
+ impl PendingCString {
47
+ fn into_string ( self ) -> Result < String > {
48
+ match self {
49
+ PendingCString :: Bytes ( v) => String :: from_utf8 ( v) . map_err ( |_| Error :: EILSEQ ) ,
50
+ PendingCString :: OsString ( s) => s. into_string ( ) . map_err ( |_| Error :: EILSEQ ) ,
51
+ }
52
+ }
53
+
54
+ /// Create a `CString` containing valid UTF-8, or fail with `Error::EILSEQ`.
55
+ fn into_utf8_cstring ( self ) -> Result < CString > {
56
+ self . into_string ( )
57
+ . and_then ( |s| CString :: new ( s) . map_err ( |_| Error :: EILSEQ ) )
58
+ }
59
+ }
60
+
11
61
/// A builder allowing customizable construction of `WasiCtx` instances.
12
62
pub struct WasiCtxBuilder {
13
- fds : HashMap < wasi:: __wasi_fd_t , FdEntry > ,
63
+ fds : HashMap < wasi:: __wasi_fd_t , PendingFdEntry > ,
14
64
preopens : Vec < ( PathBuf , File ) > ,
15
- args : Vec < CString > ,
16
- env : HashMap < CString , CString > ,
65
+ args : Vec < PendingCString > ,
66
+ env : HashMap < PendingCString , PendingCString > ,
17
67
}
18
68
19
69
impl WasiCtxBuilder {
20
70
/// Builder for a new `WasiCtx`.
21
- pub fn new ( ) -> Result < Self > {
71
+ pub fn new ( ) -> Self {
22
72
let mut builder = Self {
23
73
fds : HashMap :: new ( ) ,
24
74
preopens : Vec :: new ( ) ,
25
75
args : vec ! [ ] ,
26
76
env : HashMap :: new ( ) ,
27
77
} ;
28
78
29
- builder. fds . insert ( 0 , FdEntry :: from ( dev_null ( ) ? ) ? ) ;
30
- builder. fds . insert ( 1 , FdEntry :: from ( dev_null ( ) ? ) ? ) ;
31
- builder. fds . insert ( 2 , FdEntry :: from ( dev_null ( ) ? ) ? ) ;
79
+ builder. fds . insert ( 0 , PendingFdEntry :: Thunk ( FdEntry :: null ) ) ;
80
+ builder. fds . insert ( 1 , PendingFdEntry :: Thunk ( FdEntry :: null ) ) ;
81
+ builder. fds . insert ( 2 , PendingFdEntry :: Thunk ( FdEntry :: null ) ) ;
32
82
33
- Ok ( builder)
83
+ builder
34
84
}
35
85
36
86
/// Add arguments to the command-line arguments list.
37
- pub fn args < S : AsRef < str > > ( mut self , args : impl Iterator < Item = S > ) -> Result < Self > {
38
- let args: Result < Vec < CString > > = args
39
- . map ( |arg| CString :: new ( arg. as_ref ( ) ) . map_err ( |_| Error :: ENOTCAPABLE ) )
87
+ ///
88
+ /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
89
+ /// with `Error::EILSEQ`.
90
+ pub fn args < S : AsRef < [ u8 ] > > ( mut self , args : impl IntoIterator < Item = S > ) -> Self {
91
+ self . args = args
92
+ . into_iter ( )
93
+ . map ( |arg| arg. as_ref ( ) . to_vec ( ) . into ( ) )
40
94
. collect ( ) ;
41
- self . args = args?;
42
- Ok ( self )
95
+ self
43
96
}
44
97
45
98
/// Add an argument to the command-line arguments list.
46
- pub fn arg ( mut self , arg : & str ) -> Result < Self > {
47
- self . args
48
- . push ( CString :: new ( arg) . map_err ( |_| Error :: ENOTCAPABLE ) ?) ;
49
- Ok ( self )
99
+ ///
100
+ /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
101
+ /// with `Error::EILSEQ`.
102
+ pub fn arg < S : AsRef < [ u8 ] > > ( mut self , arg : S ) -> Self {
103
+ self . args . push ( arg. as_ref ( ) . to_vec ( ) . into ( ) ) ;
104
+ self
50
105
}
51
106
52
107
/// Inherit the command-line arguments from the host process.
53
- pub fn inherit_args ( self ) -> Result < Self > {
54
- self . args ( env:: args ( ) )
108
+ ///
109
+ /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
110
+ /// fail with `Error::EILSEQ`.
111
+ pub fn inherit_args ( mut self ) -> Self {
112
+ self . args = env:: args_os ( ) . map ( PendingCString :: OsString ) . collect ( ) ;
113
+ self
55
114
}
56
115
57
116
/// Inherit the stdin, stdout, and stderr streams from the host process.
58
- pub fn inherit_stdio ( mut self ) -> Result < Self > {
59
- self . fds . insert ( 0 , FdEntry :: duplicate_stdin ( ) ?) ;
60
- self . fds . insert ( 1 , FdEntry :: duplicate_stdout ( ) ?) ;
61
- self . fds . insert ( 2 , FdEntry :: duplicate_stderr ( ) ?) ;
62
- Ok ( self )
117
+ pub fn inherit_stdio ( mut self ) -> Self {
118
+ self . fds
119
+ . insert ( 0 , PendingFdEntry :: Thunk ( FdEntry :: duplicate_stdin) ) ;
120
+ self . fds
121
+ . insert ( 1 , PendingFdEntry :: Thunk ( FdEntry :: duplicate_stdout) ) ;
122
+ self . fds
123
+ . insert ( 2 , PendingFdEntry :: Thunk ( FdEntry :: duplicate_stderr) ) ;
124
+ self
63
125
}
64
126
65
127
/// Inherit the environment variables from the host process.
66
- pub fn inherit_env ( self ) -> Result < Self > {
67
- self . envs ( std:: env:: vars ( ) )
128
+ ///
129
+ /// If any environment variables from the host process contain invalid Unicode (UTF-16 for
130
+ /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail with
131
+ /// `Error::EILSEQ`.
132
+ pub fn inherit_env ( mut self ) -> Self {
133
+ self . env = std:: env:: vars_os ( )
134
+ . map ( |( k, v) | ( k. into ( ) , v. into ( ) ) )
135
+ . collect ( ) ;
136
+ self
68
137
}
69
138
70
139
/// Add an entry to the environment.
71
- pub fn env < S : AsRef < str > > ( mut self , k : S , v : S ) -> Result < Self > {
72
- self . env . insert (
73
- CString :: new ( k. as_ref ( ) ) . map_err ( |_| Error :: ENOTCAPABLE ) ?,
74
- CString :: new ( v. as_ref ( ) ) . map_err ( |_| Error :: ENOTCAPABLE ) ?,
75
- ) ;
76
- Ok ( self )
140
+ ///
141
+ /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
142
+ /// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
143
+ pub fn env < S : AsRef < [ u8 ] > > ( mut self , k : S , v : S ) -> Self {
144
+ self . env
145
+ . insert ( k. as_ref ( ) . to_vec ( ) . into ( ) , v. as_ref ( ) . to_vec ( ) . into ( ) ) ;
146
+ self
77
147
}
78
148
79
149
/// Add entries to the environment.
80
- pub fn envs < S : AsRef < str > , T : Borrow < ( S , S ) > > (
150
+ ///
151
+ /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
152
+ /// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
153
+ pub fn envs < S : AsRef < [ u8 ] > , T : Borrow < ( S , S ) > > (
81
154
mut self ,
82
- envs : impl Iterator < Item = T > ,
83
- ) -> Result < Self > {
84
- let env: Result < HashMap < CString , CString > > = envs
155
+ envs : impl IntoIterator < Item = T > ,
156
+ ) -> Self {
157
+ self . env = envs
158
+ . into_iter ( )
85
159
. map ( |t| {
86
160
let ( k, v) = t. borrow ( ) ;
87
- let k = CString :: new ( k. as_ref ( ) ) . map_err ( |_| Error :: ENOTCAPABLE ) ;
88
- let v = CString :: new ( v. as_ref ( ) ) . map_err ( |_| Error :: ENOTCAPABLE ) ;
89
- match ( k, v) {
90
- ( Ok ( k) , Ok ( v) ) => Ok ( ( k, v) ) ,
91
- _ => Err ( Error :: ENOTCAPABLE ) ,
92
- }
161
+ ( k. as_ref ( ) . to_vec ( ) . into ( ) , v. as_ref ( ) . to_vec ( ) . into ( ) )
93
162
} )
94
163
. collect ( ) ;
95
- self . env = env?;
96
- Ok ( self )
164
+ self
97
165
}
98
166
99
167
/// Provide a File to use as stdin
100
- pub fn stdin ( mut self , file : File ) -> Result < Self > {
101
- self . fds . insert ( 0 , FdEntry :: from ( file) ? ) ;
102
- Ok ( self )
168
+ pub fn stdin ( mut self , file : File ) -> Self {
169
+ self . fds . insert ( 0 , PendingFdEntry :: File ( file) ) ;
170
+ self
103
171
}
104
172
105
173
/// Provide a File to use as stdout
106
- pub fn stdout ( mut self , file : File ) -> Result < Self > {
107
- self . fds . insert ( 1 , FdEntry :: from ( file) ? ) ;
108
- Ok ( self )
174
+ pub fn stdout ( mut self , file : File ) -> Self {
175
+ self . fds . insert ( 1 , PendingFdEntry :: File ( file) ) ;
176
+ self
109
177
}
110
178
111
179
/// Provide a File to use as stderr
112
- pub fn stderr ( mut self , file : File ) -> Result < Self > {
113
- self . fds . insert ( 2 , FdEntry :: from ( file) ? ) ;
114
- Ok ( self )
180
+ pub fn stderr ( mut self , file : File ) -> Self {
181
+ self . fds . insert ( 2 , PendingFdEntry :: File ( file) ) ;
182
+ self
115
183
}
116
184
117
185
/// Add a preopened directory.
@@ -121,42 +189,73 @@ impl WasiCtxBuilder {
121
189
}
122
190
123
191
/// Build a `WasiCtx`, consuming this `WasiCtxBuilder`.
124
- pub fn build ( mut self ) -> Result < WasiCtx > {
125
- // startup code starts looking at fd 3 for preopens
126
- let mut preopen_fd = 3 ;
192
+ ///
193
+ /// If any of the arguments or environment variables in this builder cannot be converted into
194
+ /// `CString`s, either due to NUL bytes or Unicode conversions, this returns `Error::EILSEQ`.
195
+ pub fn build ( self ) -> Result < WasiCtx > {
196
+ // Process arguments and environment variables into `CString`s, failing quickly if they
197
+ // contain any NUL bytes, or if conversion from `OsString` fails.
198
+ let args = self
199
+ . args
200
+ . into_iter ( )
201
+ . map ( |arg| arg. into_utf8_cstring ( ) )
202
+ . collect :: < Result < Vec < CString > > > ( ) ?;
203
+
204
+ let env = self
205
+ . env
206
+ . into_iter ( )
207
+ . map ( |( k, v) | {
208
+ k. into_string ( ) . and_then ( |mut pair| {
209
+ v. into_string ( ) . and_then ( |v| {
210
+ pair. push ( '=' ) ;
211
+ pair. push_str ( v. as_str ( ) ) ;
212
+ // We have valid UTF-8, but the keys and values have not yet been checked
213
+ // for NULs, so we do a final check here.
214
+ CString :: new ( pair) . map_err ( |_| Error :: EILSEQ )
215
+ } )
216
+ } )
217
+ } )
218
+ . collect :: < Result < Vec < CString > > > ( ) ?;
219
+
220
+ let mut fds: HashMap < wasi:: __wasi_fd_t , FdEntry > = HashMap :: new ( ) ;
221
+ // Populate the non-preopen fds.
222
+ for ( fd, pending) in self . fds {
223
+ log:: debug!( "WasiCtx inserting ({:?}, {:?})" , fd, pending) ;
224
+ match pending {
225
+ PendingFdEntry :: Thunk ( f) => {
226
+ fds. insert ( fd, f ( ) ?) ;
227
+ }
228
+ PendingFdEntry :: File ( f) => {
229
+ fds. insert ( fd, FdEntry :: from ( f) ?) ;
230
+ }
231
+ }
232
+ }
233
+ // Then add the preopen fds. Startup code in the guest starts looking at fd 3 for preopens,
234
+ // so we start from there. This variable is initially 2, though, because the loop
235
+ // immediately does the increment and check for overflow.
236
+ let mut preopen_fd: wasi:: __wasi_fd_t = 2 ;
127
237
for ( guest_path, dir) in self . preopens {
238
+ // We do the increment at the beginning of the loop body, so that we don't overflow
239
+ // unnecessarily if we have exactly the maximum number of file descriptors.
240
+ preopen_fd = preopen_fd. checked_add ( 1 ) . ok_or ( Error :: ENFILE ) ?;
241
+
128
242
if !dir. metadata ( ) ?. is_dir ( ) {
129
243
return Err ( Error :: EBADF ) ;
130
244
}
131
245
132
- while self . fds . contains_key ( & preopen_fd) {
246
+ // We don't currently allow setting file descriptors other than 0-2, but this will avoid
247
+ // collisions if we restore that functionality in the future.
248
+ while fds. contains_key ( & preopen_fd) {
133
249
preopen_fd = preopen_fd. checked_add ( 1 ) . ok_or ( Error :: ENFILE ) ?;
134
250
}
135
251
let mut fe = FdEntry :: from ( dir) ?;
136
252
fe. preopen_path = Some ( guest_path) ;
137
253
log:: debug!( "WasiCtx inserting ({:?}, {:?})" , preopen_fd, fe) ;
138
- self . fds . insert ( preopen_fd, fe) ;
139
- log:: debug!( "WasiCtx fds = {:?}" , self . fds) ;
140
- preopen_fd = preopen_fd. checked_add ( 1 ) . ok_or ( Error :: ENFILE ) ?;
254
+ fds. insert ( preopen_fd, fe) ;
255
+ log:: debug!( "WasiCtx fds = {:?}" , fds) ;
141
256
}
142
257
143
- let env = self
144
- . env
145
- . into_iter ( )
146
- . map ( |( k, v) | {
147
- let mut pair = k. into_bytes ( ) ;
148
- pair. push ( b'=' ) ;
149
- pair. extend_from_slice ( v. to_bytes_with_nul ( ) ) ;
150
- // constructing a new CString from existing CStrings is safe
151
- unsafe { CString :: from_vec_unchecked ( pair) }
152
- } )
153
- . collect ( ) ;
154
-
155
- Ok ( WasiCtx {
156
- fds : self . fds ,
157
- args : self . args ,
158
- env,
159
- } )
258
+ Ok ( WasiCtx { args, env, fds } )
160
259
}
161
260
}
162
261
@@ -175,12 +274,12 @@ impl WasiCtx {
175
274
/// - Environment variables are inherited from the host process.
176
275
///
177
276
/// To override these behaviors, use `WasiCtxBuilder`.
178
- pub fn new < S : AsRef < str > > ( args : impl Iterator < Item = S > ) -> Result < Self > {
277
+ pub fn new < S : AsRef < [ u8 ] > > ( args : impl IntoIterator < Item = S > ) -> Result < Self > {
179
278
WasiCtxBuilder :: new ( )
180
- . and_then ( |ctx| ctx . args ( args) )
181
- . and_then ( |ctx| ctx . inherit_stdio ( ) )
182
- . and_then ( |ctx| ctx . inherit_env ( ) )
183
- . and_then ( |ctx| ctx . build ( ) )
279
+ . args ( args)
280
+ . inherit_stdio ( )
281
+ . inherit_env ( )
282
+ . build ( )
184
283
}
185
284
186
285
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
@@ -206,7 +305,7 @@ impl WasiCtx {
206
305
/// The `FdEntry` will automatically get another free raw WASI `fd` assigned. Note that
207
306
/// the two subsequent free raw WASI `fd`s do not have to be stored contiguously.
208
307
pub ( crate ) fn insert_fd_entry ( & mut self , fe : FdEntry ) -> Result < wasi:: __wasi_fd_t > {
209
- // never insert where stdio handles usually are
308
+ // Never insert where stdio handles are expected to be.
210
309
let mut fd = 3 ;
211
310
while self . fds . contains_key ( & fd) {
212
311
if let Some ( next_fd) = fd. checked_add ( 1 ) {
0 commit comments