Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions crates/openshell-bootstrap/src/runtime_apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,36 @@ impl AppleContainerRuntime {
Ok(())
}

/// Check if a gateway process is running by reading the PID file.
/// Check if a gateway process is running by probing the PID file lock.
pub async fn check_existing(&self, name: &str) -> Result<Option<ExistingGateway>> {
let pid_path = self.pid_path(name);
if !pid_path.exists() {
return Ok(None);
}

let pid_str = std::fs::read_to_string(&pid_path).unwrap_or_default();
let pid: i32 = match pid_str.trim().parse() {
Ok(p) => p,
Err(_) => return Ok(None),
// Use flock to determine if the gateway process is still holding the
// PID file lock. This avoids the TOCTOU race between reading the PID
// and checking with kill(pid, 0).
let running = match std::fs::File::open(&pid_path) {
Ok(file) => {
let fd = std::os::unix::io::AsRawFd::as_raw_fd(&file);
// LOCK_EX | LOCK_NB: try non-blocking exclusive lock.
// If we get it, no process holds it → not running.
let got_lock = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) } == 0;
if got_lock {
// Release the lock we just acquired.
unsafe { libc::flock(fd, libc::LOCK_UN) };
false
} else {
true
}
}
Err(_) => false,
};

// Check if process is alive.
let alive = unsafe { libc::kill(pid, 0) } == 0;

Ok(Some(ExistingGateway {
container_exists: true,
container_running: alive,
container_running: running,
storage_exists: self.data_dir(name).exists(),
container_image: Some("native".to_string()),
}))
Expand Down Expand Up @@ -187,6 +198,14 @@ impl AppleContainerRuntime {
.into_diagnostic()
.wrap_err("failed to write PID file")?;

// Hold an exclusive lock on the PID file for the process lifetime.
// check_existing() uses flock to determine if the gateway is running.
let pid_file = std::fs::File::open(&pid_path).into_diagnostic()?;
let fd = std::os::unix::io::AsRawFd::as_raw_fd(&pid_file);
unsafe { libc::flock(fd, libc::LOCK_EX) };
// pid_file is intentionally leaked to hold the lock until process exit.
std::mem::forget(pid_file);

Ok(())
}

Expand Down
6 changes: 6 additions & 0 deletions crates/openshell-core/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ impl ForwardSpec {
if port == 0 {
return Err(miette::miette!("port must be between 1 and 65535"));
}
if addr == "0.0.0.0" || addr == "::" {
eprintln!(
"warning: port forwarding binds to all interfaces ({}); use 127.0.0.1 to restrict to localhost",
addr
);
}
return Ok(Self {
bind_addr: addr.to_string(),
port,
Expand Down
2 changes: 2 additions & 0 deletions examples/private-ip-routing/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# SPDX-License-Identifier: Apache-2.0

FROM python:3.13-slim
RUN useradd -r -s /usr/sbin/nologin appuser
COPY server.py /app/server.py
EXPOSE 8080
USER appuser
CMD ["python", "/app/server.py"]
Loading