Skip to content

Commit a94bfa7

Browse files
authored
Sync 6.0/stage with master (#13)
1 parent fc00f5f commit a94bfa7

File tree

4 files changed

+101
-52
lines changed

4 files changed

+101
-52
lines changed

.github/workflows/test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
on: [push, pull_request]
2+
3+
jobs:
4+
test:
5+
runs-on: ubuntu-18.04
6+
steps:
7+
- uses: actions/checkout@v1
8+
- name: Build
9+
run: cargo build --verbose
10+
- name: Run tests
11+
run: cargo test --verbose

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ name = "netlink_example"
2828
path = "src/bin/testing/netlink.rs"
2929

3030
[profile.release]
31+
debug = true
3132
lto = true
3233
panic = "abort" # Save size. We don't need unwinding anyway.
3334
# TODO is there any way to remove the ability to display backtraces? This would

src/bin/testing/netlink.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// limitations under the License.
1515
//
1616

17-
use nix::sys::socket::{socket, AddressFamily, SockAddr, SockType, SockFlag, bind};
17+
use nix::sys::socket::{AddressFamily, SockAddr, SockType, bind};
1818
use nix::errno::Errno;
1919

2020
use std::fs::File;
@@ -35,7 +35,7 @@ fn main() {
3535

3636
let fd = Errno::result(fd).unwrap();
3737

38-
bind(fd, &SockAddr::new_netlink(0, 0));
38+
bind(fd, &SockAddr::new_netlink(0, 0)).unwrap();
3939

4040
// Signal parent process (the test process) that this process is ready to be observed by the
4141
// ptool being tested.

src/ptools.rs

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ use std::io::{BufRead, BufReader, Read};
3636
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
3737
use std::path::Path;
3838
use std::str::from_utf8;
39+
use std::io::ErrorKind;
40+
use std::process::exit;
3941

4042
// Issues blocking 0.1 release
4143
// - Everything marked with BLOCKER
@@ -172,7 +174,7 @@ impl ProcStat {
172174
// can't parse this file reliably. We can read the command from /proc/[pid]/comm,
173175
// so we know exactly what to expect, but that would be a pain.
174176
//
175-
fn read(pid: u64) -> Result<Self, Box<Error>> {
177+
fn read(pid: u64) -> Result<Self, Box<dyn Error>> {
176178
// /proc/[pid]/status contains lines of the form
177179
//
178180
// Name: bash
@@ -198,7 +200,7 @@ impl ProcStat {
198200
let key = substrs[0].to_string();
199201
let value = substrs[1].trim().to_string();
200202
Ok((key, value))
201-
}).collect::<Result<HashMap<String, String>, Box<Error>>>()?;
203+
}).collect::<Result<HashMap<String, String>, Box<dyn Error>>>()?;
202204

203205
Ok(ProcStat {
204206
pid: pid,
@@ -210,40 +212,46 @@ impl ProcStat {
210212
format!("/proc/{}/status", pid)
211213
}
212214

213-
fn get_field(&self, field: &str) -> Result<&str, Box<Error>> {
215+
fn get_field(&self, field: &str) -> Result<&str, Box<dyn Error>> {
214216
match self.fields.get(field) {
215217
Some(val) => Ok(val),
216218
None => Err(From::from(ParseError::in_file(
217219
"status",
218220
&format!(
219-
"Missing expected field '{}' file {}",
221+
"Missing expected field '{}' in file {}",
220222
field,
221223
ProcStat::status_file(self.pid)
222224
),
223225
))),
224226
}
225227
}
226228

227-
fn ppid(&self) -> Result<u64, Box<Error>> {
229+
fn ppid(&self) -> Result<u64, Box<dyn Error>> {
228230
Ok(self.get_field("PPid")?.parse()?)
229231
}
230232
}
231233

232-
fn print_tree(pid_of_interest: u64) -> Result<(), Box<Error>> {
234+
fn print_tree(pid_of_interest: u64) -> Result<(), Box<dyn Error>> {
233235
let mut child_map = HashMap::new(); // Map of pid to pids of children
234236
let mut parent_map = HashMap::new(); // Map of pid to pid of parent
235237

236238
// Loop over all the processes listed in /proc/, find the parent of each one, and build a map
237-
// from parent to children. There doesn't seem to be more efficient way of doing this reliably.
239+
// from parent to children. There doesn't seem to be a more efficient way of doing this
240+
// reliably.
238241
for entry in fs::read_dir("/proc")? {
239242
let entry = entry?;
240243
let filename = entry.file_name();
241244
let filename = filename.to_str().unwrap();
242245
if let Ok(pid) = filename.parse::<u64>() {
243246
let ppid = match ProcStat::read(pid) {
244-
Ok(proc_stat) => proc_stat.ppid()?, // TODO should we print error and continue?
245-
// TODO print error before continuing unless err is file not found, which could
246-
// happen if proc exited
247+
Ok(proc_stat) => match proc_stat.ppid() {
248+
Ok(ppid) => ppid,
249+
Err(e) => {
250+
eprintln!("{}", e.to_string());
251+
continue
252+
}
253+
},
254+
// Proc probably exited before we could read its status
247255
Err(_) => continue,
248256
};
249257
child_map.entry(ppid).or_insert(vec![]).push(pid);
@@ -254,6 +262,10 @@ fn print_tree(pid_of_interest: u64) -> Result<(), Box<Error>> {
254262
let indent_level = if pid_of_interest == 1 {
255263
0
256264
} else {
265+
if !parent_map.contains_key(&pid_of_interest) {
266+
eprintln!("No such pid {}", pid_of_interest);
267+
exit(1);
268+
}
257269
print_parents(&parent_map, pid_of_interest)
258270
};
259271
print_children(&child_map, pid_of_interest, indent_level);
@@ -263,18 +275,34 @@ fn print_tree(pid_of_interest: u64) -> Result<(), Box<Error>> {
263275

264276
// Print a summary of command line arguments on a single line.
265277
fn print_cmd_summary(pid: u64) {
266-
let file = File::open(format!("/proc/{}/cmdline", pid)).unwrap();
267-
for arg in BufReader::new(file).take(80).split('\0' as u8) {
268-
print!("{} ", from_utf8(&arg.unwrap()).unwrap());
278+
match File::open(format!("/proc/{}/cmdline", pid)) {
279+
Ok(file) => {
280+
for arg in BufReader::new(file).take(80).split('\0' as u8) {
281+
print!("{} ", from_utf8(&arg.unwrap()).unwrap());
282+
}
283+
print!("\n");
284+
}
285+
Err(ref e) if e.kind() == ErrorKind::NotFound => {
286+
println!("<exited>");
287+
}
288+
Err(e) => {
289+
println!("<error reading cmdline>");
290+
eprintln!("{}", e.to_string());
291+
}
269292
}
270-
print!("\n");
271293
}
272294

273295
// Returns the current indentation level
274296
fn print_parents(parent_map: &HashMap<u64, u64>, pid: u64) -> u64 {
275-
// TODO need to handle the case where the parent exited before we could read the parent's
276-
// parent.
277-
let ppid = *parent_map.get(&pid).unwrap();
297+
let ppid = match parent_map.get(&pid) {
298+
Some(ppid) => *ppid,
299+
// Some child process listed 'pid' as its parent, but 'pid' exited before we could read its
300+
// parent. The child of 'pid' will have been re-parented, and the new parent will be 'init'.
301+
// It's actually a bit more complicated (see find_new_reaper() in the kernel), and there is
302+
// one case we might want to handle better: when a child is re-parented to another thread in
303+
// the thread group.
304+
None => 1
305+
};
278306

279307
// We've reached the top of the process tree. Don't bother printing the parent if the parent
280308
// is pid 1. Typically pid 1 didn't really start the process in question.
@@ -321,7 +349,6 @@ enum PosixFileType {
321349
// form 'anon_inode:[eventpoll]' TODO better comment
322350
#[derive(PartialEq)]
323351
enum AnonFileType {
324-
Bpf,
325352
Epoll,
326353
Unknown(String),
327354
}
@@ -401,7 +428,6 @@ fn print_file_type(file_type: &FileType) -> String {
401428
FileType::Posix(PosixFileType::Fifo) => "S_IFIFO".into(),
402429
FileType::Posix(PosixFileType::Unknown(x)) => format!("UNKNOWN_TYPE(mode={})", x),
403430
FileType::Anon(AnonFileType::Epoll) => "anon_inode(epoll)".into(),
404-
FileType::Anon(AnonFileType::Bpf) => "anon_inode(bpf)".into(),
405431
FileType::Anon(AnonFileType::Unknown(s)) => format!("anon_inode({})", s),
406432
FileType::Unknown => "UNKNOWN_TYPE".into(),
407433
}
@@ -471,7 +497,13 @@ fn get_flags(pid: u64, fd: u64) -> u64 {
471497
fn print_file(pid: u64, fd: u64, sockets: &HashMap<u64, SockInfo>) {
472498
let link_path_str = format!("/proc/{}/fd/{}", pid, fd);
473499
let link_path = Path::new(&link_path_str);
474-
let stat_info = stat(link_path).unwrap();
500+
let stat_info = match stat(link_path) {
501+
Err(e) => {
502+
eprintln!("failed to stat {}: {}", &link_path_str, e);
503+
return;
504+
},
505+
Ok(stat_info) => stat_info,
506+
};
475507

476508
let file_type = file_type(stat_info.st_mode, &link_path);
477509

@@ -515,28 +547,14 @@ fn print_file(pid: u64, fd: u64, sockets: &HashMap<u64, SockInfo>) {
515547
}
516548
},
517549
_ => {
518-
let path = fs::read_link(link_path).unwrap();
519-
print!(" {}\n", path.to_str().unwrap());
550+
match fs::read_link(link_path) {
551+
Ok(path) => println!(" {}", path.to_string_lossy()),
552+
Err(e) => eprintln!("failed to readlink {}: {}", &link_path_str, e),
553+
}
520554
}
521555
}
522556
}
523557

524-
// Corresponds to definitions in include/net/tcp_states.h in the kernel
525-
enum TcpSockState {
526-
Established = 1,
527-
SynSent,
528-
SynRecv,
529-
FinWait1,
530-
FinWait2,
531-
TimeWait,
532-
Close,
533-
CloseWait,
534-
LastAck,
535-
Listen,
536-
Closing,
537-
NewSynRecv,
538-
}
539-
540558
#[derive(Debug)]
541559
struct SockInfo {
542560
family: AddressFamily,
@@ -682,7 +700,7 @@ fn parse_ipv4_sock_addr(s: &str) -> Result<SocketAddr, ParseError> {
682700
Ok(SocketAddr::new(IpAddr::V4(addr), port))
683701
}
684702

685-
fn fetch_sock_info(pid: u64) -> Result<HashMap<u64, SockInfo>, Box<Error>> {
703+
fn fetch_sock_info(pid: u64) -> Result<HashMap<u64, SockInfo>, Box<dyn Error>> {
686704
let file = File::open(format!("/proc/{}/net/unix", pid)).unwrap();
687705
let mut sockets = BufReader::new(file)
688706
.lines()
@@ -784,28 +802,41 @@ fn fetch_sock_info(pid: u64) -> Result<HashMap<u64, SockInfo>, Box<Error>> {
784802
* sockname: AF_INET6 :: port: 8341
785803
*/
786804

787-
fn print_files(pid: u64) {
805+
fn print_files(pid: u64) -> bool {
806+
807+
let proc_dir = format!("/proc/{}/", pid);
808+
if !Path::new(&proc_dir).exists() {
809+
eprintln!("No such directory {}", &proc_dir);
810+
return false;
811+
}
812+
788813
print_proc_summary(pid);
789814

790815
// TODO print current rlimit
791816

792-
// TODO BLOCKER handle permission errors by printing an error instead of just
793-
// not printing anything
794-
795817
let sockets = fetch_sock_info(pid).unwrap();
796818

797-
if let Ok(entries) = fs::read_dir(format!("/proc/{}/fd/", pid)) {
819+
let fd_dir = format!("/proc/{}/fd/", pid);
820+
let readdir_res = fs::read_dir(&fd_dir).and_then(|entries| {
798821
for entry in entries {
799-
let entry = entry.unwrap();
822+
let entry = entry?;
800823
let filename = entry.file_name();
801-
let filename = filename.to_str().unwrap();
802-
if let Ok(fd) = filename.parse::<u64>() {
824+
let filename = filename.to_string_lossy();
825+
if let Ok(fd) = (&filename).parse::<u64>() {
803826
print_file(pid, fd, &sockets);
804827
} else {
805-
eprint!("Unexpected file /proc/pid/fd/{} found", filename);
828+
eprint!("Unexpected file /proc/[pid]/fd/{} found", &filename);
806829
}
807-
}
830+
};
831+
Ok(())
832+
});
833+
834+
if let Err(e) = readdir_res {
835+
eprintln!("Unable to read {}: {}", &fd_dir, e);
836+
return false;
808837
}
838+
839+
return true;
809840
}
810841

811842
pub fn pargs_main() {
@@ -913,9 +944,14 @@ pub fn pfiles_main() {
913944
usage_err(program, opts);
914945
}
915946

947+
let mut error = false;
916948
for arg in &matches.free {
917949
let pid = arg.parse::<u64>().unwrap();
918-
print_files(pid);
950+
error = error || !print_files(pid);
951+
}
952+
953+
if error {
954+
exit(1);
919955
}
920956
}
921957

@@ -956,6 +992,7 @@ pub fn ptree_main() {
956992
}
957993
}
958994

995+
#[cfg(test)]
959996
mod test {
960997
use super::*;
961998
use std::net::SocketAddr;

0 commit comments

Comments
 (0)