Skip to content

Commit 8406beb

Browse files
committed
Close non-stdio file handles when daemonizing
Otherwise, when the compiler wrapper spawns the sccache server, the server may end up with unintended file descriptors, which can lead to unexpected problems. This is particularly problematic if any of those file descriptors that accidentally end up in the server process is a pipe, as the pipe will only be closed when all the processes with that file descriptor close it or exit. This was causing sccache to hang ninja, as ninja uses the EOF of a pipe given to the subprocess to detect when that subprocess has exited: ninja-build/ninja#2444 (comment) This patch adds a dependency on the [close_fds](https://crates.io/crates/close_fds) crate, which automatically chooses an appropriate mechanism to close a range of file descriptors. On Linux 5.9+ that mechanism will be libc::close_range(). Fixes mozilla#2313
1 parent bf49d4c commit 8406beb

File tree

5 files changed

+31
-24
lines changed

5 files changed

+31
-24
lines changed

Cargo.lock

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ thirtyfour_sync = "0.27"
136136

137137
[target.'cfg(unix)'.dependencies]
138138
daemonize = "0.5"
139+
close_fds = "0.3.2"
139140

140141
[target.'cfg(not(target_os = "freebsd"))'.dependencies.libmount]
141142
optional = true

src/bin/sccache-dist/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ fn run(command: Command) -> Result<i32> {
209209
}
210210
};
211211

212-
daemonize()?;
212+
daemonize(None)?;
213213
let scheduler = Scheduler::new();
214214
let http_scheduler = dist::http::Scheduler::new(
215215
public_addr,

src/commands.rs

+4-20
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,6 @@ fn run_server_process(startup_timeout: Option<Duration>) -> Result<ServerStartup
126126
})
127127
}
128128

129-
#[cfg(not(windows))]
130-
fn redirect_stderr(f: File) {
131-
use libc::dup2;
132-
use std::os::unix::io::IntoRawFd;
133-
// Ignore errors here.
134-
unsafe {
135-
dup2(f.into_raw_fd(), 2);
136-
}
137-
}
138-
139129
#[cfg(windows)]
140130
fn redirect_stderr(f: File) {
141131
use std::os::windows::io::IntoRawHandle;
@@ -165,13 +155,6 @@ fn create_error_log() -> Result<File> {
165155
Ok(f)
166156
}
167157

168-
/// If `SCCACHE_ERROR_LOG` is set, redirect stderr to it.
169-
fn redirect_error_log(f: File) -> Result<()> {
170-
debug!("redirecting stderr into {:?}", f);
171-
redirect_stderr(f);
172-
Ok(())
173-
}
174-
175158
/// Re-execute the current executable as a background server.
176159
#[cfg(windows)]
177160
fn run_server_process(startup_timeout: Option<Duration>) -> Result<ServerStartup> {
@@ -654,14 +637,15 @@ pub fn run_command(cmd: Command) -> Result<i32> {
654637
}
655638
Command::InternalStartServer => {
656639
trace!("Command::InternalStartServer");
640+
// If `SCCACHE_ERROR_LOG` is set, redirect stderr to it.
657641
if env::var("SCCACHE_ERROR_LOG").is_ok() {
658642
let f = create_error_log()?;
643+
debug!("redirecting stderr into {:?}", f);
659644
// Can't report failure here, we're already daemonized.
660-
daemonize()?;
661-
redirect_error_log(f)?;
645+
daemonize(Some(f))?;
662646
} else {
663647
// We aren't asking for a log file
664-
daemonize()?;
648+
daemonize(None)?;
665649
}
666650
server::start_server(config, &get_addr())?;
667651
}

src/util.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ impl<'a> Hasher for HashToDigest<'a> {
841841

842842
/// Pipe `cmd`'s stdio to `/dev/null`, unless a specific env var is set.
843843
#[cfg(not(windows))]
844-
pub fn daemonize() -> Result<()> {
844+
pub fn daemonize(log_file: Option<File>) -> Result<()> {
845845
use crate::jobserver::discard_inherited_jobserver;
846846
use daemonize::Daemonize;
847847
use std::env;
@@ -850,7 +850,18 @@ pub fn daemonize() -> Result<()> {
850850
match env::var("SCCACHE_NO_DAEMON") {
851851
Ok(ref val) if val == "1" => {}
852852
_ => {
853-
Daemonize::new().start().context("failed to daemonize")?;
853+
Daemonize::new()
854+
.stderr(
855+
log_file
856+
.map(|f| f.into_parts().0.into())
857+
.unwrap_or_else(daemonize::Stdio::devnull),
858+
)
859+
.start()
860+
.context("failed to daemonize")?;
861+
// Be extra-zealous and clase all non-stdio file descriptors.
862+
unsafe {
863+
close_fds::close_open_fds(3, &[]);
864+
}
854865
}
855866
}
856867

@@ -928,7 +939,7 @@ pub fn daemonize() -> Result<()> {
928939

929940
/// This is a no-op on Windows.
930941
#[cfg(windows)]
931-
pub fn daemonize() -> Result<()> {
942+
pub fn daemonize(_log_file: Option<File>) -> Result<()> {
932943
Ok(())
933944
}
934945

0 commit comments

Comments
 (0)