Skip to content

Commit 74fb798

Browse files
committed
libstd/sys/unix/process.rs: reap a zombie who didn't get through to exec(2).
After the library successfully called fork(2), the child does several setup works such as setting UID, GID and current directory before it calls exec(2). When those setup works failed, the child exits but the parent didn't call waitpid(2) and left it as a zombie. This patch also add several sanity checks. They shouldn't make any noticeable impact to runtime performance. The new test case run-pass/wait-forked-but-failed-child.rs calls the ps command to check if the new code can really reap a zombie. When I intentionally create many zombies with my test program ./spawn-failure, The output of "ps -A -o pid,sid,command" should look like this: PID SID COMMAND 1 1 /sbin/init 2 0 [kthreadd] 3 0 [ksoftirqd/0] ... 12562 9237 ./spawn-failure 12563 9237 [spawn-failure] <defunct> 12564 9237 [spawn-failure] <defunct> ... 12592 9237 [spawn-failure] <defunct> 12593 9237 ps -A -o pid,sid,command 12884 12884 /bin/zsh 12922 12922 /bin/zsh ... Filtering the output with the "SID" (session ID) column is a quick way to tell if a process (zombie) was spawned by my own test program. Then the number of "defunct" lines is the number of zombie children. Signed-off-by: NODA, Kai <nodakai@gmail.com>
1 parent 3c89031 commit 74fb798

File tree

2 files changed

+112
-10
lines changed

2 files changed

+112
-10
lines changed

src/libstd/sys/unix/process.rs

+33-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use self::Req::*;
1111

1212
use libc::{mod, pid_t, c_void, c_int};
1313
use c_str::CString;
14-
use io::{mod, IoResult, IoError};
14+
use io::{mod, IoResult, IoError, EndOfFile};
1515
use mem;
1616
use os;
1717
use ptr;
@@ -39,6 +39,8 @@ enum Req {
3939
NewChild(libc::pid_t, Sender<ProcessExit>, u64),
4040
}
4141

42+
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
43+
4244
impl Process {
4345
pub fn id(&self) -> pid_t {
4446
self.pid
@@ -106,18 +108,36 @@ impl Process {
106108
if pid < 0 {
107109
return Err(super::last_error())
108110
} else if pid > 0 {
111+
#[inline]
112+
fn combine(arr: &[u8]) -> i32 {
113+
let a = arr[0] as u32;
114+
let b = arr[1] as u32;
115+
let c = arr[2] as u32;
116+
let d = arr[3] as u32;
117+
118+
((a << 24) | (b << 16) | (c << 8) | (d << 0)) as i32
119+
}
120+
121+
let p = Process{ pid: pid };
109122
drop(output);
110-
let mut bytes = [0, ..4];
123+
let mut bytes = [0, ..8];
111124
return match input.read(&mut bytes) {
112-
Ok(4) => {
113-
let errno = (bytes[0] as i32 << 24) |
114-
(bytes[1] as i32 << 16) |
115-
(bytes[2] as i32 << 8) |
116-
(bytes[3] as i32 << 0);
125+
Ok(8) => {
126+
assert!(combine(CLOEXEC_MSG_FOOTER) == combine(bytes.slice(4, 8)),
127+
"Validation on the CLOEXEC pipe failed: {}", bytes);
128+
let errno = combine(bytes.slice(0, 4));
129+
assert!(p.wait(0).is_ok(), "wait(0) should either return Ok or panic");
117130
Err(super::decode_error(errno))
118131
}
119-
Err(..) => Ok(Process { pid: pid }),
120-
Ok(..) => panic!("short read on the cloexec pipe"),
132+
Err(ref e) if e.kind == EndOfFile => Ok(p),
133+
Err(e) => {
134+
assert!(p.wait(0).is_ok(), "wait(0) should either return Ok or panic");
135+
panic!("the CLOEXEC pipe failed: {}", e)
136+
},
137+
Ok(..) => { // pipe I/O up to PIPE_BUF bytes should be atomic
138+
assert!(p.wait(0).is_ok(), "wait(0) should either return Ok or panic");
139+
panic!("short read on the CLOEXEC pipe")
140+
}
121141
};
122142
}
123143

@@ -154,13 +174,16 @@ impl Process {
154174
let _ = libc::close(input.fd());
155175

156176
fn fail(output: &mut FileDesc) -> ! {
157-
let errno = sys::os::errno();
177+
let errno = sys::os::errno() as u32;
158178
let bytes = [
159179
(errno >> 24) as u8,
160180
(errno >> 16) as u8,
161181
(errno >> 8) as u8,
162182
(errno >> 0) as u8,
183+
CLOEXEC_MSG_FOOTER[0], CLOEXEC_MSG_FOOTER[1],
184+
CLOEXEC_MSG_FOOTER[2], CLOEXEC_MSG_FOOTER[3]
163185
];
186+
// pipe I/O up to PIPE_BUF bytes should be atomic
164187
assert!(output.write(&bytes).is_ok());
165188
unsafe { libc::_exit(1) }
166189
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
12+
extern crate libc;
13+
14+
use std::io::process::Command;
15+
use std::iter::IteratorExt;
16+
17+
use libc::funcs::posix88::unistd;
18+
19+
20+
// "ps -A -o pid,sid,command" with GNU ps should output something like this:
21+
// PID SID COMMAND
22+
// 1 1 /sbin/init
23+
// 2 0 [kthreadd]
24+
// 3 0 [ksoftirqd/0]
25+
// ...
26+
// 12562 9237 ./spawn-failure
27+
// 12563 9237 [spawn-failure] <defunct>
28+
// 12564 9237 [spawn-failure] <defunct>
29+
// ...
30+
// 12592 9237 [spawn-failure] <defunct>
31+
// 12593 9237 ps -A -o pid,sid,command
32+
// 12884 12884 /bin/zsh
33+
// 12922 12922 /bin/zsh
34+
// ...
35+
36+
#[cfg(unix)]
37+
fn find_zombies() {
38+
// http://man.freebsd.org/ps(1)
39+
// http://man7.org/linux/man-pages/man1/ps.1.html
40+
#[cfg(not(target_os = "macos"))]
41+
const FIELDS: &'static str = "pid,sid,command";
42+
43+
// https://developer.apple.com/library/mac/documentation/Darwin/
44+
// Reference/ManPages/man1/ps.1.html
45+
#[cfg(target_os = "macos")]
46+
const FIELDS: &'static str = "pid,sess,command";
47+
48+
let my_sid = unsafe { unistd::getsid(0) };
49+
50+
let ps_cmd_output = Command::new("ps").args(&["-A", "-o", FIELDS]).output().unwrap();
51+
let ps_output = String::from_utf8_lossy(ps_cmd_output.output.as_slice());
52+
53+
let found = ps_output.split('\n').enumerate().any(|(line_no, line)|
54+
0 < line_no && 0 < line.len() &&
55+
my_sid == from_str(line.split(' ').filter(|w| 0 < w.len()).nth(1)
56+
.expect("1st column should be Session ID")
57+
).expect("Session ID string into integer") &&
58+
line.contains("defunct") && {
59+
println!("Zombie child {}", line);
60+
true
61+
}
62+
);
63+
64+
assert!( ! found, "Found at least one zombie child");
65+
}
66+
67+
#[cfg(windows)]
68+
fn find_zombies() { }
69+
70+
fn main() {
71+
let too_long = format!("/NoSuchCommand{:0300}", 0u8);
72+
73+
for _ in range(0u32, 100) {
74+
let invalid = Command::new(too_long.as_slice()).spawn();
75+
assert!(invalid.is_err());
76+
}
77+
78+
find_zombies();
79+
}

0 commit comments

Comments
 (0)