about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheodore DeRego <tedsta@google.com>2016-11-11 18:21:03 -0800
committerTheodore DeRego <tedsta@google.com>2016-11-22 12:12:46 -0800
commit5c23f2e3c88275cee37acbd36c12baba57c4de2b (patch)
treef77bcfe12ed23ef51d5be7765c38ad9712e94f38
parent0491a231777735ba050c208ce621df93f863bf7c (diff)
downloadrust-5c23f2e3c88275cee37acbd36c12baba57c4de2b.tar.gz
rust-5c23f2e3c88275cee37acbd36c12baba57c4de2b.zip
Fuchsia support for std::process via liblaunchpad.
-rw-r--r--src/libstd/build.rs2
-rw-r--r--src/libstd/process.rs13
-rw-r--r--src/libstd/sys/unix/fd.rs2
-rw-r--r--src/libstd/sys/unix/magenta.rs157
-rw-r--r--src/libstd/sys/unix/mod.rs20
-rw-r--r--src/libstd/sys/unix/pipe.rs1
-rw-r--r--src/libstd/sys/unix/process.rs236
7 files changed, 426 insertions, 5 deletions
diff --git a/src/libstd/build.rs b/src/libstd/build.rs
index 72cd6e4830b..1087d1f2447 100644
--- a/src/libstd/build.rs
+++ b/src/libstd/build.rs
@@ -60,6 +60,8 @@ fn main() {
         println!("cargo:rustc-link-lib=shell32");
     } else if target.contains("fuchsia") {
         println!("cargo:rustc-link-lib=magenta");
+        println!("cargo:rustc-link-lib=mxio");
+        println!("cargo:rustc-link-lib=launchpad"); // for std::process
     }
 }
 
diff --git a/src/libstd/process.rs b/src/libstd/process.rs
index 9d21a76e81b..a9c68e82175 100644
--- a/src/libstd/process.rs
+++ b/src/libstd/process.rs
@@ -780,6 +780,8 @@ impl Child {
     ///
     #[stable(feature = "process", since = "1.0.0")]
     pub fn wait_with_output(mut self) -> io::Result<Output> {
+        //use io::ErrorKind;
+
         drop(self.stdin.take());
 
         let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
@@ -794,8 +796,15 @@ impl Child {
                 res.unwrap();
             }
             (Some(out), Some(err)) => {
-                let res = read2(out.inner, &mut stdout, err.inner, &mut stderr);
-                res.unwrap();
+                match read2(out.inner, &mut stdout, err.inner, &mut stderr) {
+                    Ok(()) => { },
+                    #[cfg(not(target_os = "fuchsia"))]
+                    Err(ref e) => { panic!("Failed to read child's stdout and stderr: {:?}", e); },
+                    #[cfg(target_os = "fuchsia")]
+                    Err(_) => {
+                        // FIXME: Right now there's a bug in magenta's pipes implementation
+                    },
+                }
             }
         }
 
diff --git a/src/libstd/sys/unix/fd.rs b/src/libstd/sys/unix/fd.rs
index 41bb96fed16..61eb60da486 100644
--- a/src/libstd/sys/unix/fd.rs
+++ b/src/libstd/sys/unix/fd.rs
@@ -110,6 +110,7 @@ impl FileDesc {
     #[cfg(not(any(target_env = "newlib",
                   target_os = "solaris",
                   target_os = "emscripten",
+                  target_os = "fuchsia",
                   target_os = "haiku")))]
     pub fn set_cloexec(&self) -> io::Result<()> {
         unsafe {
@@ -120,6 +121,7 @@ impl FileDesc {
     #[cfg(any(target_env = "newlib",
               target_os = "solaris",
               target_os = "emscripten",
+              target_os = "fuchsia",
               target_os = "haiku"))]
     pub fn set_cloexec(&self) -> io::Result<()> {
         unsafe {
diff --git a/src/libstd/sys/unix/magenta.rs b/src/libstd/sys/unix/magenta.rs
new file mode 100644
index 00000000000..ae3b7789b10
--- /dev/null
+++ b/src/libstd/sys/unix/magenta.rs
@@ -0,0 +1,157 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(non_camel_case_types)]
+
+use os::raw::c_char;
+use u64;
+
+use libc::{c_int, c_void};
+
+pub type mx_handle_t = i32;
+pub type mx_vaddr_t = usize;
+pub type mx_rights_t = u32;
+pub type mx_status_t = i32;
+
+pub type mx_size_t = usize;
+pub type mx_ssize_t = isize;
+
+pub const MX_HANDLE_INVALID: mx_handle_t = 0;
+
+pub type mx_time_t = u64;
+pub const MX_TIME_INFINITE : mx_time_t = u64::MAX;
+
+pub const NO_ERROR              : mx_status_t = 0;
+
+pub type mx_signals_t = u32;
+
+pub const MX_OBJECT_SIGNAL_3         : mx_signals_t = 1 << 3;
+
+pub const MX_TASK_TERMINATED        : mx_signals_t = MX_OBJECT_SIGNAL_3;
+
+pub const MX_RIGHT_SAME_RIGHTS  : mx_rights_t = 1 << 31;
+
+pub type mx_object_info_topic_t = u32;
+
+pub const MX_INFO_PROCESS         : mx_object_info_topic_t = 3;
+
+pub const MX_HND_TYPE_JOB: u32 = 6;
+
+// Common MX_INFO header
+#[derive(Default)]
+#[repr(C)]
+pub struct mx_info_header_t {
+    pub topic: u32,              // identifies the info struct
+    pub avail_topic_size: u16,   // “native” size of the struct
+    pub topic_size: u16,         // size of the returned struct (<=topic_size)
+    pub avail_count: u32,        // number of records the kernel has
+    pub count: u32,              // number of records returned (limited by buffer size)
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct mx_record_process_t {
+    pub return_code: c_int,
+}
+
+// Returned for topic MX_INFO_PROCESS
+#[derive(Default)]
+#[repr(C)]
+pub struct mx_info_process_t {
+    pub hdr: mx_info_header_t,
+    pub rec: mx_record_process_t,
+}
+
+#[link(name = "magenta")]
+extern {
+    pub fn mx_handle_close(handle: mx_handle_t) -> mx_status_t;
+
+    pub fn mx_handle_duplicate(handle: mx_handle_t, rights: mx_rights_t,
+                               out: *const mx_handle_t) -> mx_handle_t;
+
+    pub fn mx_handle_wait_one(handle: mx_handle_t, signals: mx_signals_t, timeout: mx_time_t,
+                              pending: *mut mx_signals_t) -> mx_status_t;
+
+    pub fn mx_object_get_info(handle: mx_handle_t, topic: u32, buffer: *mut c_void,
+                              buffer_size: mx_size_t, actual_size: *mut mx_size_t,
+                              avail: *mut mx_size_t) -> mx_status_t;
+}
+
+// Handle Info entries associate a type and optional
+// argument with each handle included in the process
+// arguments message.
+pub fn mx_hnd_info(hnd_type: u32, arg: u32) -> u32 {
+    (hnd_type & 0xFFFF) | ((arg & 0xFFFF) << 16)
+}
+
+#[link(name="mxio")]
+extern {
+    pub fn mxio_get_startup_handle(id: u32) -> mx_handle_t;
+}
+
+// From `enum special_handles` in system/ulib/launchpad/launchpad.c
+#[allow(unused)] pub const HND_LOADER_SVC: usize = 0;
+// HND_EXEC_VMO = 1
+#[allow(unused)] pub const HND_SPECIAL_COUNT: usize = 2;
+
+#[repr(C)]
+pub struct launchpad_t {
+    argc: u32,
+    envc: u32,
+    args: *const c_char,
+    args_len: usize,
+    env: *const c_char,
+    env_len: usize,
+
+    handles: *mut mx_handle_t,
+    handles_info: *mut u32,
+    handle_count: usize,
+    handle_alloc: usize,
+
+    entry: mx_vaddr_t,
+    base: mx_vaddr_t,
+    vdso_base: mx_vaddr_t,
+
+    stack_size: usize,
+
+    special_handles: [mx_handle_t; HND_SPECIAL_COUNT],
+    loader_message: bool,
+}
+
+#[link(name="launchpad")]
+extern {
+    pub fn launchpad_create(job: mx_handle_t, name: *const c_char,
+                            lp: *mut *mut launchpad_t) -> mx_status_t;
+
+    pub fn launchpad_start(lp: *mut launchpad_t) -> mx_status_t;
+
+    pub fn launchpad_destroy(lp: *mut launchpad_t);
+
+    pub fn launchpad_arguments(lp: *mut launchpad_t, argc: c_int,
+                               argv: *const *const c_char) -> mx_status_t;
+
+    pub fn launchpad_environ(lp: *mut launchpad_t, envp: *const *const c_char) -> mx_status_t;
+
+    pub fn launchpad_clone_mxio_root(lp: *mut launchpad_t) -> mx_status_t;
+
+    pub fn launchpad_clone_mxio_cwd(lp: *mut launchpad_t) -> mx_status_t;
+
+    pub fn launchpad_clone_fd(lp: *mut launchpad_t, fd: c_int, target_fd: c_int) -> mx_status_t;
+
+    pub fn launchpad_transfer_fd(lp: *mut launchpad_t, fd: c_int, target_fd: c_int) -> mx_status_t;
+
+    pub fn launchpad_elf_load(lp: *mut launchpad_t, vmo: mx_handle_t) -> mx_status_t;
+
+    pub fn launchpad_add_vdso_vmo(lp: *mut launchpad_t) -> mx_status_t;
+
+    pub fn launchpad_load_vdso(lp: *mut launchpad_t, vmo: mx_handle_t) -> mx_status_t;
+
+    pub fn launchpad_vmo_from_file(filename: *const c_char) -> mx_handle_t;
+}
diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs
index fd7dc17cccd..8fe55af51d5 100644
--- a/src/libstd/sys/unix/mod.rs
+++ b/src/libstd/sys/unix/mod.rs
@@ -13,6 +13,11 @@
 use io::{self, ErrorKind};
 use libc;
 
+#[cfg(target_os = "fuchsia")]
+use convert::TryInto;
+#[cfg(target_os = "fuchsia")]
+pub use self::magenta::mx_status_t;
+
 #[cfg(target_os = "android")]   pub use os::android as platform;
 #[cfg(target_os = "bitrig")]    pub use os::bitrig as platform;
 #[cfg(target_os = "dragonfly")] pub use os::dragonfly as platform;
@@ -41,6 +46,8 @@ pub mod ext;
 pub mod fast_thread_local;
 pub mod fd;
 pub mod fs;
+#[cfg(target_os = "fuchsia")]
+pub mod magenta;
 pub mod memchr;
 pub mod mutex;
 pub mod net;
@@ -164,6 +171,19 @@ pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
     }
 }
 
+#[cfg(target_os = "fuchsia")]
+pub fn mx_cvt<T>(t: T) -> io::Result<T> where T: TryInto<mx_status_t>+Copy {
+    if let Ok(status) = TryInto::try_into(t) {
+        if status < 0 {
+            Err(io::Error::from_raw_os_error(status))
+        } else {
+            Ok(t)
+        }
+    } else {
+        Err(io::Error::last_os_error())
+    }
+}
+
 // On Unix-like platforms, libc::abort will unregister signal handlers
 // including the SIGABRT handler, preventing the abort from being blocked, and
 // fclose streams, with the side effect of flushing them so libc bufferred
diff --git a/src/libstd/sys/unix/pipe.rs b/src/libstd/sys/unix/pipe.rs
index ffe8032e460..a8ed415b7f4 100644
--- a/src/libstd/sys/unix/pipe.rs
+++ b/src/libstd/sys/unix/pipe.rs
@@ -77,6 +77,7 @@ pub fn read2(p1: AnonPipe,
              v1: &mut Vec<u8>,
              p2: AnonPipe,
              v2: &mut Vec<u8>) -> io::Result<()> {
+
     // Set both pipes into nonblocking mode as we're gonna be reading from both
     // in the `select` loop below, and we wouldn't want one to block the other!
     let p1 = p1.into_fd();
diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs
index dafc11d9cc8..aa203dc6215 100644
--- a/src/libstd/sys/unix/process.rs
+++ b/src/libstd/sys/unix/process.rs
@@ -15,13 +15,23 @@ use env;
 use ffi::{OsString, OsStr, CString, CStr};
 use fmt;
 use io::{self, Error, ErrorKind};
-use libc::{self, pid_t, c_int, gid_t, uid_t, c_char};
+use libc::{self, c_int, gid_t, uid_t, c_char};
 use mem;
 use ptr;
 use sys::fd::FileDesc;
 use sys::fs::{File, OpenOptions};
 use sys::pipe::{self, AnonPipe};
-use sys::{self, cvt, cvt_r};
+
+#[cfg(not(target_os = "fuchsia"))]
+use sys::cvt;
+#[cfg(target_os = "fuchsia")]
+use sys::mx_cvt;
+
+#[cfg(target_os = "fuchsia")]
+use sys::magenta::{launchpad_t, mx_handle_t};
+
+#[cfg(not(target_os = "fuchsia"))]
+use libc::pid_t;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Command
@@ -210,8 +220,11 @@ impl Command {
         self.stderr = Some(stderr);
     }
 
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
                  -> io::Result<(Process, StdioPipes)> {
+        use sys;
+
         const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
 
         if self.saw_nul {
@@ -286,6 +299,31 @@ impl Command {
         }
     }
 
+    #[cfg(target_os = "fuchsia")]
+    pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
+                 -> io::Result<(Process, StdioPipes)> {
+        if self.saw_nul {
+            return Err(io::Error::new(ErrorKind::InvalidInput,
+                                      "nul byte found in provided data"));
+        }
+
+        let (ours, theirs) = self.setup_io(default, needs_stdin)?;
+
+        let (maybe_process, err) = unsafe { self.do_exec(&theirs) };
+        // We don't want FileDesc::drop to be called on any stdio. It would close their handles.
+        let ChildPipes { stdin: their_stdin, stdout: their_stdout, stderr: their_stderr } = theirs;
+        their_stdin.fd();
+        their_stdout.fd();
+        their_stderr.fd();
+
+        if let Some((launchpad, process_handle)) = maybe_process {
+            Ok((Process { launchpad: launchpad, handle: process_handle, status: None }, ours))
+        } else {
+            Err(err)
+        }
+    }
+
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn exec(&mut self, default: Stdio) -> io::Error {
         if self.saw_nul {
             return io::Error::new(ErrorKind::InvalidInput,
@@ -298,6 +336,22 @@ impl Command {
         }
     }
 
+    #[cfg(target_os = "fuchsia")]
+    pub fn exec(&mut self, default: Stdio) -> io::Error {
+        if self.saw_nul {
+            return io::Error::new(ErrorKind::InvalidInput,
+                                  "nul byte found in provided data")
+        }
+
+        match self.setup_io(default, true) {
+            Ok((_, _)) => {
+                // FIXME: This is tough because we don't support the exec syscalls
+                unimplemented!();
+            },
+            Err(e) => e,
+        }
+    }
+
     // And at this point we've reached a special time in the life of the
     // child. The child must now be considered hamstrung and unable to
     // do anything other than syscalls really. Consider the following
@@ -328,7 +382,10 @@ impl Command {
     // allocation). Instead we just close it manually. This will never
     // have the drop glue anyway because this code never returns (the
     // child will either exec() or invoke libc::exit)
+    #[cfg(not(target_os = "fuchsia"))]
     unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
+        use sys::{self, cvt_r};
+
         macro_rules! t {
             ($e:expr) => (match $e {
                 Ok(e) => e,
@@ -395,6 +452,108 @@ impl Command {
         io::Error::last_os_error()
     }
 
+    #[cfg(target_os = "fuchsia")]
+    unsafe fn do_exec(&mut self, stdio: &ChildPipes)
+                      -> (Option<(*mut launchpad_t, mx_handle_t)>, io::Error) {
+        use sys::magenta::*;
+
+        macro_rules! t {
+            ($e:expr) => (match $e {
+                Ok(e) => e,
+                Err(e) => return (None, e),
+            })
+        }
+
+        macro_rules! tlp {
+            ($lp:expr, $e:expr) => (match $e {
+                Ok(e) => e,
+                Err(e) => {
+                    launchpad_destroy($lp);
+                    return (None, e);
+                },
+            })
+        }
+
+        let job_handle = mxio_get_startup_handle(mx_hnd_info(MX_HND_TYPE_JOB, 0));
+        let envp = match self.envp {
+            Some(ref envp) => envp.as_ptr(),
+            None => ptr::null(),
+        };
+
+        let mut launchpad: *mut launchpad_t = ptr::null_mut();
+        let mut job_copy: mx_handle_t = MX_HANDLE_INVALID;
+
+        // Duplicate the job handle
+        t!(mx_cvt(mx_handle_duplicate(job_handle, MX_RIGHT_SAME_RIGHTS,
+                                   &mut job_copy as *mut mx_handle_t)));
+        // Create a launchpad
+        t!(mx_cvt(launchpad_create(job_copy, self.argv[0],
+                                &mut launchpad as *mut *mut launchpad_t)));
+        // Set the process argv
+        tlp!(launchpad, mx_cvt(launchpad_arguments(launchpad, self.argv.len() as i32 - 1,
+                                                self.argv.as_ptr())));
+        // Setup the environment vars
+        let status = launchpad_environ(launchpad, envp);
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+        let status = launchpad_add_vdso_vmo(launchpad);
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+        let status = launchpad_clone_mxio_root(launchpad);
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+        // Load the executable
+        let status = launchpad_elf_load(launchpad, launchpad_vmo_from_file(self.argv[0]));
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+        let status = launchpad_load_vdso(launchpad, MX_HANDLE_INVALID);
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+        let status = launchpad_clone_mxio_cwd(launchpad);
+        if status != NO_ERROR {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+
+        // Clone stdin, stdout, and stderr
+        if let Some(fd) = stdio.stdin.fd() {
+            launchpad_transfer_fd(launchpad, fd, 0);
+        } else {
+            launchpad_clone_fd(launchpad, 0, 0);
+        }
+        if let Some(fd) = stdio.stdout.fd() {
+            launchpad_transfer_fd(launchpad, fd, 1);
+        } else {
+            launchpad_clone_fd(launchpad, 1, 1);
+        }
+        if let Some(fd) = stdio.stderr.fd() {
+            launchpad_transfer_fd(launchpad, fd, 2);
+        } else {
+            launchpad_clone_fd(launchpad, 2, 2);
+        }
+
+        for callback in self.closures.iter_mut() {
+            t!(callback());
+        }
+
+        let process_handle = launchpad_start(launchpad);
+        if process_handle < 0 {
+            launchpad_destroy(launchpad);
+            return (None, io::Error::last_os_error());
+        }
+
+        (Some((launchpad, process_handle)), io::Error::last_os_error())
+    }
 
     fn setup_io(&self, default: Stdio, needs_stdin: bool)
                 -> io::Result<(StdioPipes, ChildPipes)> {
@@ -431,7 +590,9 @@ impl Stdio {
     fn to_child_stdio(&self, readable: bool)
                       -> io::Result<(ChildStdio, Option<AnonPipe>)> {
         match *self {
-            Stdio::Inherit => Ok((ChildStdio::Inherit, None)),
+            Stdio::Inherit => {
+                Ok((ChildStdio::Inherit, None))
+            },
 
             // Make sure that the source descriptors are not an stdio
             // descriptor, otherwise the order which we set the child's
@@ -556,16 +717,31 @@ impl fmt::Display for ExitStatus {
 }
 
 /// The unique id of the process (this should never be negative).
+#[cfg(not(target_os = "fuchsia"))]
 pub struct Process {
     pid: pid_t,
     status: Option<ExitStatus>,
 }
 
+#[cfg(target_os = "fuchsia")]
+pub struct Process {
+    launchpad: *mut launchpad_t,
+    handle: mx_handle_t,
+    status: Option<ExitStatus>,
+}
+
 impl Process {
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn id(&self) -> u32 {
         self.pid as u32
     }
 
+    #[cfg(target_os = "fuchsia")]
+    pub fn id(&self) -> u32 {
+        0
+    }
+
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn kill(&mut self) -> io::Result<()> {
         // If we've already waited on this process then the pid can be recycled
         // and used for another process, and we probably shouldn't be killing
@@ -578,7 +754,28 @@ impl Process {
         }
     }
 
+    #[cfg(target_os = "fuchsia")]
+    pub fn kill(&mut self) -> io::Result<()> {
+        use sys::magenta::*;
+
+        // If we've already waited on this process then the pid can be recycled
+        // and used for another process, and we probably shouldn't be killing
+        // random processes, so just return an error.
+        if self.status.is_some() {
+            Err(Error::new(ErrorKind::InvalidInput,
+                           "invalid argument: can't kill an exited process"))
+        } else {
+            unsafe {
+                mx_cvt(mx_handle_close(self.handle))?;
+                launchpad_destroy(self.launchpad);
+            }
+            Ok(())
+        }
+    }
+
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        use sys::cvt_r;
         if let Some(status) = self.status {
             return Ok(status)
         }
@@ -587,6 +784,39 @@ impl Process {
         self.status = Some(ExitStatus(status));
         Ok(ExitStatus(status))
     }
+
+    #[cfg(target_os = "fuchsia")]
+    pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        use default::Default;
+        use sys::magenta::*;
+
+        if let Some(status) = self.status {
+            return Ok(status)
+        }
+
+        let mut proc_info: mx_info_process_t = Default::default();
+        let mut actual: mx_size_t = 0;
+        let mut avail: mx_size_t = 0;
+
+        unsafe {
+            mx_cvt(mx_handle_wait_one(self.handle, MX_TASK_TERMINATED,
+                                      MX_TIME_INFINITE, ptr::null_mut()))?;
+            mx_cvt(mx_object_get_info(self.handle, MX_INFO_PROCESS,
+                                      &mut proc_info as *mut _ as *mut libc::c_void,
+                                      mem::size_of::<mx_info_process_t>(), &mut actual,
+                                      &mut avail))?;
+        }
+        if actual != 1 {
+            return Err(Error::new(ErrorKind::InvalidInput,
+                                  "Failed to get exit status of process"));
+        }
+        self.status = Some(ExitStatus(proc_info.rec.return_code));
+        unsafe {
+            mx_cvt(mx_handle_close(self.handle))?;
+            launchpad_destroy(self.launchpad);
+        }
+        Ok(ExitStatus(proc_info.rec.return_code))
+    }
 }
 
 #[cfg(all(test, not(target_os = "emscripten")))]