@@ -12,6 +12,8 @@ use crate::svc::wait_for_service;
12
12
use crate :: zone:: { AddressRequest , IPADM , ZONE_PREFIX } ;
13
13
use camino:: { Utf8Path , Utf8PathBuf } ;
14
14
use ipnetwork:: IpNetwork ;
15
+ #[ cfg( target_os = "illumos" ) ]
16
+ use libc:: pid_t;
15
17
use omicron_common:: backoff;
16
18
use slog:: error;
17
19
use slog:: info;
@@ -156,10 +158,15 @@ pub enum GetZoneError {
156
158
// inside a non-global zone.
157
159
#[ cfg( target_os = "illumos" ) ]
158
160
mod zenter {
161
+ use libc:: ctid_t;
162
+ use libc:: pid_t;
159
163
use libc:: zoneid_t;
160
164
use std:: ffi:: c_int;
161
165
use std:: ffi:: c_uint;
162
- use std:: ffi:: CStr ;
166
+ use std:: ffi:: { CStr , CString } ;
167
+ use std:: fs:: File ;
168
+ use std:: os:: unix:: prelude:: FileExt ;
169
+ use std:: process;
163
170
164
171
#[ link( name = "contract" ) ]
165
172
extern "C" {
@@ -169,13 +176,68 @@ mod zenter {
169
176
fn ct_pr_tmpl_set_param ( fd : c_int , params : c_uint ) -> c_int ;
170
177
fn ct_tmpl_activate ( fd : c_int ) -> c_int ;
171
178
fn ct_tmpl_clear ( fd : c_int ) -> c_int ;
179
+ fn ct_ctl_abandon ( fd : c_int ) -> c_int ;
172
180
}
173
181
174
182
#[ link( name = "c" ) ]
175
183
extern "C" {
176
184
pub fn zone_enter ( zid : zoneid_t ) -> c_int ;
177
185
}
178
186
187
+ #[ derive( thiserror:: Error , Debug ) ]
188
+ pub enum AbandonContractError {
189
+ #[ error( "Error opening file {file}: {error}" ) ]
190
+ Open { file : String , error : std:: io:: Error } ,
191
+
192
+ #[ error( "Error abandoning contract {ctid}: {error}" ) ]
193
+ Abandon { ctid : ctid_t , error : std:: io:: Error } ,
194
+
195
+ #[ error( "Error closing file {file}: {error}" ) ]
196
+ Close { file : String , error : std:: io:: Error } ,
197
+ }
198
+
199
+ pub fn get_contract ( pid : pid_t ) -> std:: io:: Result < ctid_t > {
200
+ // The offset of "id_t pr_contract" in struct psinfo which is an
201
+ // interface documented in proc(5).
202
+ const CONTRACT_OFFSET : u64 = 280 ;
203
+
204
+ let path = format ! ( "/proc/{}/psinfo" , pid) ;
205
+
206
+ let file = File :: open ( path) ?;
207
+ let mut buffer = [ 0 ; 4 ] ;
208
+ file. read_exact_at ( & mut buffer, CONTRACT_OFFSET ) ?;
209
+ Ok ( ctid_t:: from_ne_bytes ( buffer) )
210
+ }
211
+
212
+ pub fn abandon_contract ( ctid : ctid_t ) -> Result < ( ) , AbandonContractError > {
213
+ let path = format ! ( "/proc/{}/contracts/{}/ctl" , process:: id( ) , ctid) ;
214
+
215
+ let cpath = CString :: new ( path. clone ( ) ) . unwrap ( ) ;
216
+ let fd = unsafe { libc:: open ( cpath. as_ptr ( ) , libc:: O_WRONLY ) } ;
217
+ if fd < 0 {
218
+ return Err ( AbandonContractError :: Open {
219
+ file : path,
220
+ error : std:: io:: Error :: last_os_error ( ) ,
221
+ } ) ;
222
+ }
223
+ let ret = unsafe { ct_ctl_abandon ( fd) } ;
224
+ if ret != 0 {
225
+ unsafe { libc:: close ( fd) } ;
226
+ return Err ( AbandonContractError :: Abandon {
227
+ ctid,
228
+ error : std:: io:: Error :: from_raw_os_error ( ret) ,
229
+ } ) ;
230
+ }
231
+ if unsafe { libc:: close ( fd) } != 0 {
232
+ return Err ( AbandonContractError :: Close {
233
+ file : path,
234
+ error : std:: io:: Error :: last_os_error ( ) ,
235
+ } ) ;
236
+ }
237
+
238
+ Ok ( ( ) )
239
+ }
240
+
179
241
// A Rust wrapper around the process contract template.
180
242
#[ derive( Debug ) ]
181
243
pub struct Template {
@@ -277,11 +339,11 @@ impl RunningZone {
277
339
// another, if the task is swapped out at an await point. That would leave
278
340
// the first thread's template in a modified state.
279
341
//
280
- // If we do need to make this method asynchronous, we will need to change the
281
- // internals to avoid changing the thread's contract. One possible approach
282
- // here would be to use `libscf` directly, rather than `exec`-ing `svccfg`
283
- // directly in a forked child. That would obviate the need to work on the
284
- // contract at all.
342
+ // If we do need to make this method asynchronous, we will need to change
343
+ // the internals to avoid changing the thread's contract. One possible
344
+ // approach here would be to use `libscf` directly, rather than `exec`-ing
345
+ // `svccfg` directly in a forked child. That would obviate the need to work
346
+ // on the contract at all.
285
347
#[ cfg( target_os = "illumos" ) ]
286
348
pub fn run_cmd < I , S > ( & self , args : I ) -> Result < String , RunCommandError >
287
349
where
@@ -319,13 +381,46 @@ impl RunningZone {
319
381
}
320
382
let command = command. args ( args) ;
321
383
322
- // Capture the result, and be sure to clear the template for this
323
- // process itself before returning.
324
- let res = crate :: execute ( command) . map_err ( |err| RunCommandError {
384
+ let child = crate :: spawn ( command) . map_err ( |err| RunCommandError {
325
385
zone : self . name ( ) . to_string ( ) ,
326
386
err,
387
+ } ) ?;
388
+
389
+ // Record the process contract now in use by the child; the contract
390
+ // just created from the template that we applied to this thread
391
+ // moments ago. We need to abandon it once the child is finished
392
+ // executing.
393
+ // unwrap() safety - child.id() returns u32 but pid_t is i32.
394
+ // PID_MAX is 999999 so this will not overflow.
395
+ let child_pid: pid_t = child. id ( ) . try_into ( ) . unwrap ( ) ;
396
+ let contract = zenter:: get_contract ( child_pid) ;
397
+
398
+ // Capture the result, and be sure to clear the template for this
399
+ // process itself before returning.
400
+ let res = crate :: run_child ( command, child) . map_err ( |err| {
401
+ RunCommandError { zone : self . name ( ) . to_string ( ) , err }
327
402
} ) ;
328
403
template. clear ( ) ;
404
+
405
+ // Now abandon the contract that was used for the child.
406
+ match contract {
407
+ Err ( e) => error ! (
408
+ self . inner. log,
409
+ "Could not retrieve contract for pid {}: {}" , child_pid, e
410
+ ) ,
411
+ Ok ( ctid) => {
412
+ if let Err ( e) = zenter:: abandon_contract ( ctid) {
413
+ error ! (
414
+ self . inner. log,
415
+ "Failed to abandon contract {} for pid {}: {}" ,
416
+ ctid,
417
+ child_pid,
418
+ e
419
+ ) ;
420
+ }
421
+ }
422
+ }
423
+
329
424
res. map ( |output| String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) )
330
425
}
331
426
@@ -342,7 +437,11 @@ impl RunningZone {
342
437
// that's actually run is irrelevant.
343
438
let mut command = std:: process:: Command :: new ( "echo" ) ;
344
439
let command = command. args ( args) ;
345
- crate :: execute ( command)
440
+ let child = crate :: spawn ( command) . map_err ( |err| RunCommandError {
441
+ zone : self . name ( ) . to_string ( ) ,
442
+ err,
443
+ } ) ?;
444
+ crate :: run_child ( command, child)
346
445
. map_err ( |err| RunCommandError {
347
446
zone : self . name ( ) . to_string ( ) ,
348
447
err,
0 commit comments