Skip to content

Commit

Permalink
fix dangling fd with stdio
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Prendes <[email protected]>
  • Loading branch information
jprendes committed Aug 31, 2023
1 parent 6232e5c commit d4b60ae
Show file tree
Hide file tree
Showing 18 changed files with 152 additions and 82 deletions.
1 change: 1 addition & 0 deletions .github/workflows/action-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- name: Validate docs
run: ./scripts/validate-docs.sh
- name: Run tests
timeout-minutes: 5
run: |
make test
- name: Package artifacts
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/action-test-k3s.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ jobs:
name: e2e k3s test on ${{ inputs.os }}
runs-on: ${{ inputs.os }}
steps:
- name: "check cgroup version"
run: "mount | grep cgroup"
- uses: actions/checkout@v3
- name: setup rust-wasm target
run: rustup target add wasm32-wasi
- name: Setup build env
run: ./scripts/setup-linux.sh
shell: bash
Expand All @@ -39,6 +35,7 @@ jobs:
name: test-image
path: dist
- name: run
timeout-minutes: 5
run: make test/k3s
- name: cleanup
if: always()
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/action-test-kind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ jobs:
name: e2e kind test on ${{ inputs.os }}
runs-on: ${{ inputs.os }}
steps:
- name: "check cgroup version"
run: "mount | grep cgroup"
- uses: actions/checkout@v3
- name: setup rust-wasm target
run: rustup target add wasm32-wasi
- name: Setup build env
run: ./scripts/setup-linux.sh
shell: bash
Expand All @@ -39,6 +35,7 @@ jobs:
name: test-image
path: dist
- name: run
timeout-minutes: 5
run: make test/k8s
# only runs when the previous step fails
- name: inspect failed pods
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/action-test-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Run smoke tests

on:
workflow_call:
inputs:
os:
required: true
type: string
runtime:
required: true
type: string

jobs:
smoke-test:
name: smoke test on ${{ inputs.os }}
runs-on: ${{ inputs.os }}
steps:
- uses: actions/checkout@v3
- name: Setup build env
run: ./scripts/setup-linux.sh
shell: bash
- name: Download artifacts
uses: actions/download-artifact@master
with:
name: containerd-shim-${{ inputs.runtime }}-${{ inputs.os }}
path: dist
- name: Unpack artifats
shell: bash
run: |
mkdir -p dist/bin
tar -xzf dist/containerd-shim-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz -C dist/bin
- name: Download test image
uses: actions/download-artifact@master
with:
name: test-image
path: dist
- name: run
timeout-minutes: 5
run: |
ls -alh dist
ls -alh dist/bin
make load
sudo cp -f dist/bin/* /usr/local/bin
sudo ctr run --rm --runtime=io.containerd.${{ inputs.runtime }}.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm echo 'hello'
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ jobs:
os: ${{ matrix.os }}
runtime: ${{ matrix.runtime }}

smoke-tests:
name: ${{ matrix.runtime }}
needs: [build-ubuntu, test-image]
strategy:
matrix:
# 20.04 uses cgroupv1, 22.04 uses cgroupv2
os: ["ubuntu-20.04", "ubuntu-22.04"]
runtime: ["wasmtime", "wasmedge"]
uses: ./.github/workflows/action-test-smoke.yml
with:
os: ${{ matrix.os }}
runtime: ${{ matrix.runtime }}

e2e-wasmtime:
name: ${{ matrix.runtime }}
needs: [build-ubuntu, test-image]
Expand Down
8 changes: 3 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,14 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
key: release-${{ needs.generate.outputs.crate }}
- name: "check cgroup version"
run: "mount | grep cgroup"
- uses: actions/checkout@v3
- name: Setup build env
run: ./scripts/setup-linux.sh
shell: bash
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
- name: Setup build env
run: ${GITHUB_WORKSPACE}/.github/scripts/build.sh
shell: bash
- name: Build
run: cargo build --verbose --package ${{ needs.generate.outputs.crate }}
- name: Test
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sha256 = "1.4.0"
# TODO: once lincontainer releases 0.2, switch to released version. The current commit is the tip of the tree from `youki` and a release candidate.
libcontainer = { git = "https://github.com/containers/youki", rev = "09e67372a892f22a89eeef62ff429c3cbcac6d41", default-features = false }
windows-sys = { version = "0.48" }
crossbeam = { version = "0.8.2", default-features = false }

[profile.release]
panic = "abort"
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ install:
)

dist:
$(MAKE) install PREFIX=$(PWD)/dist RUNTIMES=$(RUNTIMES) TARGET=$(TARGET)
$(MAKE) install PREFIX="$(PWD)/dist" RUNTIMES="$(RUNTIMES)" TARGET="$(TARGET)"

.PHONY: test-image
test-image: dist/img.tar
Expand Down
1 change: 1 addition & 0 deletions crates/containerd-shim-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ttrpc = { workspace = true }
chrono = { workspace = true }
log = { workspace = true }
libc = { workspace = true }
crossbeam = { workspace = true }

[target.'cfg(unix)'.dependencies]
clone3 = "0.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl LinuxContainerExecutor {

impl Executor for LinuxContainerExecutor {
fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {
self.stdio.redirect().map_err(|err| {
self.stdio.take().redirect().map_err(|err| {
log::error!("failed to redirect io: {}", err);
ExecutorError::Other(format!("failed to redirect io: {}", err))
})?;
Expand Down
94 changes: 58 additions & 36 deletions crates/containerd-shim-wasm/src/sandbox/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use std::fs::File;
use std::io::ErrorKind::NotFound;
use std::io::{Error, Result};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::Arc;

use crossbeam::atomic::AtomicCell;

use super::InstanceConfig;
use crate::sys::stdio::*;

#[derive(Default, Clone)]
Expand All @@ -14,51 +17,70 @@ pub struct Stdio {
}

impl Stdio {
pub fn redirect(&self) -> Result<()> {
pub fn redirect(self) -> Result<()> {
self.stdin.redirect()?;
self.stdout.redirect()?;
self.stderr.redirect()?;
Ok(())
}

pub fn take(&self) -> Self {
Self {
stdin: self.stdin.take(),
stdout: self.stdout.take(),
stderr: self.stderr.take(),
}
}

pub fn init_from_cfg(cfg: &InstanceConfig<impl Send + Sync + Clone>) -> Result<Self> {
Ok(Self {
stdin: cfg.get_stdin().try_into()?,
stdout: cfg.get_stdout().try_into()?,
stderr: cfg.get_stderr().try_into()?,
})
}
}

macro_rules! stdio_impl {
( $stdio_type:ident, $fd:expr ) => {
#[derive(Default, Clone)]
pub struct $stdio_type(Arc<Mutex<Option<File>>>);
#[derive(Clone, Default)]
pub struct StdioStream<const FD: StdioRawFd>(Arc<AtomicCell<Option<File>>>);

impl<P: AsRef<Path>> TryFrom<Option<P>> for $stdio_type {
type Error = std::io::Error;
fn try_from(path: Option<P>) -> Result<Self> {
path.and_then(|path| match path.as_ref() {
path if path.as_os_str().is_empty() => None,
path => Some(path.to_owned()),
})
.map(|path| match open_file(path) {
Err(err) if err.kind() == NotFound => Ok(None),
Ok(f) => Ok(Some(f)),
Err(err) => Err(err),
})
.transpose()
.map(|opt| Self(Arc::new(Mutex::new(opt.flatten()))))
impl<const FD: StdioRawFd> StdioStream<FD> {
pub fn redirect(self) -> Result<()> {
if let Some(f) = self.0.take() {
let f = try_into_fd(f)?;
let _ = unsafe { libc::dup(FD) };
if unsafe { libc::dup2(f.as_raw_fd(), FD) } == -1 {
return Err(Error::last_os_error());
}
}
Ok(())
}

impl $stdio_type {
pub fn redirect(&self) -> Result<()> {
if let Some(f) = self.0.try_lock().ok().and_then(|mut f| f.take()) {
let f = try_into_fd(f)?;
let _ = unsafe { libc::dup($fd) };
if unsafe { libc::dup2(f.as_raw_fd(), $fd) } == -1 {
return Err(Error::last_os_error());
}
}
Ok(())
}
}
};
pub fn take(&self) -> Self {
Self(Arc::new(AtomicCell::new(self.0.take())))
}
}

impl<P: AsRef<Path>, const FD: StdioRawFd> TryFrom<Option<P>> for StdioStream<FD> {
type Error = Error;
fn try_from(path: Option<P>) -> Result<Self> {
let file = path
.and_then(|path| match path.as_ref() {
path if path.as_os_str().is_empty() => None,
path => Some(path.to_owned()),
})
.map(|path| match open_file(path) {
Err(err) if err.kind() == NotFound => Ok(None),
Ok(f) => Ok(Some(f)),
Err(err) => Err(err),
})
.transpose()?
.flatten();

Ok(Self(Arc::new(AtomicCell::new(file))))
}
}

stdio_impl!(Stdin, STDIN_FILENO);
stdio_impl!(Stdout, STDOUT_FILENO);
stdio_impl!(Stderr, STDERR_FILENO);
pub type Stdin = StdioStream<STDIN_FILENO>;
pub type Stdout = StdioStream<STDOUT_FILENO>;
pub type Stderr = StdioStream<STDERR_FILENO>;
2 changes: 1 addition & 1 deletion crates/containerd-shim-wasm/src/sys/unix/stdio.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fs::{File, OpenOptions};
use std::io::Result;
pub use std::os::fd::AsRawFd as StdioAsRawFd;
use std::os::fd::OwnedFd;
pub use std::os::fd::{AsRawFd as StdioAsRawFd, RawFd as StdioRawFd};
use std::path::Path;

pub use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
Expand Down
8 changes: 4 additions & 4 deletions crates/containerd-shim-wasm/src/sys/windows/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use std::path::Path;
use libc::{c_int, close, intptr_t, open_osfhandle, O_APPEND};
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED;

type StdioRawFd = libc::c_int;
pub type StdioRawFd = libc::c_int;

pub static STDIN_FILENO: StdioRawFd = 0;
pub static STDOUT_FILENO: StdioRawFd = 1;
pub static STDERR_FILENO: StdioRawFd = 2;
pub const STDIN_FILENO: StdioRawFd = 0;
pub const STDOUT_FILENO: StdioRawFd = 1;
pub const STDERR_FILENO: StdioRawFd = 2;

struct StdioOwnedFd(c_int);

Expand Down
2 changes: 1 addition & 1 deletion crates/containerd-shim-wasmedge/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl WasmEdgeExecutor {
.register_module_from_file("main", module_name)
.map_err(|err| ExecutorError::Execution(err))?;

self.stdio.redirect()?;
self.stdio.take().redirect()?;

Ok(vm)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ impl LibcontainerInstance for Wasi {
)
.unwrap(),
exit_code: Arc::new((Mutex::new(None), Condvar::new())),
stdio: Stdio {
stdin: cfg.get_stdin().try_into().unwrap(),
stdout: cfg.get_stdout().try_into().unwrap(),
stderr: cfg.get_stderr().try_into().unwrap(),
},
stdio: Stdio::init_from_cfg(cfg).expect("failed to open stdio"),
bundle,
}
}
Expand All @@ -65,7 +61,7 @@ impl LibcontainerInstance for Wasi {

let err_others = |err| Error::Others(format!("failed to create container: {}", err));
let container = ContainerBuilder::new(self.id.clone(), SyscallType::Linux)
.with_executor(WasmEdgeExecutor::new(self.stdio.clone()))
.with_executor(WasmEdgeExecutor::new(self.stdio.take()))
.with_root_path(self.rootdir.clone())
.map_err(err_others)?
.as_init(&self.bundle)
Expand Down
Loading

0 comments on commit d4b60ae

Please sign in to comment.