From cf2ecd98041b5bca34d43b1a8ebb46dffe8ba491 Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 001/113] tests: cleanup: Avoid `.pop().unwrap()` in cross_process_sender_transfer() Since we assert the number of elements in the vector to be exactly one right before this, we can just index the element directly. --- src/platform/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 480d928f9..87c9dcbb8 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -691,7 +691,7 @@ fn cross_process_sender_transfer() { let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); - let sub_tx = received_channels.pop().unwrap().to_sender(); + let sub_tx = received_channels[0].to_sender(); let data: &[u8] = b"baz"; sub_tx.send(data, vec![], vec![]).unwrap(); From 4c6a09673d4e0112dfbe21c34a13b6e193aa67eb Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 002/113] tests: Add variants of cross-process test, using `spawn()` Since `fork()` isn't available on Windows, we need to do the tests using `spawn()` instead. The `spawn()` variants work both on Windows and on Unix platforms (including MacOS), making the `fork()` variants pretty redundant. However, it doesn't really hurt to keep them around, just in case. (It might help narrowing down any problems coming up, for example.) --- src/platform/test.rs | 117 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 87c9dcbb8..81cd99afc 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -10,16 +10,35 @@ use crate::platform::{self, OsIpcChannel, OsIpcReceiverSet}; use crate::platform::{OsIpcSharedMemory}; use std::collections::HashMap; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::env; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use libc; use crate::platform::{OsIpcSender, OsIpcOneShotServer}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use crate::test::{fork, Wait}; +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +fn get_channel_name_arg() -> Option { + for arg in env::args() { + let arg_str = "channel_name:"; + if arg.starts_with(arg_str) { + return Some(arg[arg_str.len()..].to_owned()); + } + } + None +} + #[test] fn simple() { let (tx, rx) = platform::channel().unwrap(); @@ -656,9 +675,51 @@ fn server_connect_first() { (data, vec![], vec![])); } +// Note! This test is actually used by the cross_process_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_server() +{ + let data: &[u8] = b"1234567"; + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + let tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + tx.send(data, vec![], vec![]).unwrap(); + unsafe { libc::exit(0); } +} + +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +fn cross_process_spawn() { + let (server, name) = OsIpcOneShotServer::new().unwrap(); + let data: &[u8] = b"1234567"; + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (_, received_data, received_channels, received_shared_memory_regions) = + server.accept().unwrap(); + child_pid.wait().expect("failed to wait on child"); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (data, vec![], vec![])); +} + #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -fn cross_process() { +fn cross_process_fork() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let data: &[u8] = b"1234567"; @@ -674,9 +735,61 @@ fn cross_process() { (data, vec![], vec![])); } +// Note! This test is actually used by the cross_process_sender_transfer_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_sender_transfer_server() +{ + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + let (sub_tx, sub_rx) = platform::channel().unwrap(); + let data: &[u8] = b"foo"; + super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + sub_rx.recv().unwrap(); + let data: &[u8] = b"bar"; + super_tx.send(data, vec![], vec![]).unwrap(); + unsafe { libc::exit(0); } +} + +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +fn cross_process_sender_transfer_spawn() { + let (server, name) = OsIpcOneShotServer::new().unwrap(); + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_sender_transfer_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); + assert_eq!(received_channels.len(), 1); + let sub_tx = received_channels[0].to_sender(); + let data: &[u8] = b"baz"; + sub_tx.send(data, vec![], vec![]).unwrap(); + + let data: &[u8] = b"bar"; + let (received_data, received_channels, received_shared_memory_regions) = + super_rx.recv().unwrap(); + child_pid.wait().expect("failed to wait on child"); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (data, vec![], vec![])); +} + #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -fn cross_process_sender_transfer() { +fn cross_process_sender_transfer_fork() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let child_pid = unsafe { fork(|| { From 03687e362a89ab74e4b410e337e082f0770776ab Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Thu, 15 Dec 2016 11:09:27 -0500 Subject: [PATCH 003/113] Add inception channel-in-channel multi-process transfer test --- src/platform/test.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/platform/test.rs b/src/platform/test.rs index 81cd99afc..421078545 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -1094,3 +1094,95 @@ mod sync_test { platform::OsIpcSender::test_not_sync(); } } + +// Note! This test is actually used by the +// cross_process_two_step_transfer_spawn() test below. Running it by +// itself is meaningless, but it passes if run this way. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_two_step_transfer_server() +{ + let cookie: &[u8] = b"cookie"; + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + // connect by name to our other process + let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + + // create a channel for real communication between the two processes + let (sub_tx, sub_rx) = platform::channel().unwrap(); + + // send the other process the tx side, so it can send us the channels + super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + + // get two_rx from the other process + let (_, mut received_channels, _) = sub_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let two_rx = received_channels[0].to_receiver(); + + // get one_rx from two_rx's buffer + let (_, mut received_channels, _) = two_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let one_rx = received_channels[0].to_receiver(); + + // get a cookie from one_rx + let (data, _, _) = one_rx.recv().unwrap(); + assert_eq!(&data[..], cookie); + + // finally, send a cookie back + super_tx.send(&data, vec![], vec![]).unwrap(); + + // terminate + unsafe { libc::exit(0); } +} + +// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg_attr(target_os = "macos", ignore)] +#[test] +fn cross_process_two_step_transfer_spawn() { + let cookie: &[u8] = b"cookie"; + + // create channel 1 + let (one_tx, one_rx) = platform::channel().unwrap(); + // put data in channel 1's pipe + one_tx.send(cookie, vec![], vec![]).unwrap(); + + // create channel 2 + let (two_tx, two_rx) = platform::channel().unwrap(); + // put channel 1's rx end in channel 2's pipe + two_tx.send(&[], vec![OsIpcChannel::Receiver(one_rx)], vec![]).unwrap(); + + // create a one-shot server, and spawn another process + let (server, name) = OsIpcOneShotServer::new().unwrap(); + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_two_step_transfer_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + // The other process will have sent us a transmit channel in received channels + let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); + assert_eq!(received_channels.len(), 1); + let sub_tx = received_channels[0].to_sender(); + + // Send the outer payload channel, so the server can use it to + // retrive the inner payload and the cookie + sub_tx.send(&[], vec![OsIpcChannel::Receiver(two_rx)], vec![]).unwrap(); + + // Then we wait for the cookie to make its way back to us + let (received_data, received_channels, received_shared_memory_regions) = + super_rx.recv().unwrap(); + let child_exit_code = child_pid.wait().expect("failed to wait on child"); + assert!(child_exit_code.success()); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (cookie, vec![], vec![])); +} From 1c9f7ab9e15b371d74af575a685fbcfefeb7aa15 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:25:22 +0200 Subject: [PATCH 004/113] tests: Introduce `which` parameter in `get_channel_name_arg()` helper In preparation for introducing spawn tests using more than one channel, we need a way to extract specific channel name args by distinctive labels. All existing `*_spawn()` tests are adapted to include the label in the passed command line argument; and all `*_server()` helpers are updated with the new argument in the `get_channel_name_arg()` calls. --- src/platform/test.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 421078545..6a43fd296 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -29,9 +29,9 @@ use crate::test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -fn get_channel_name_arg() -> Option { +pub fn get_channel_name_arg(which: &str) -> Option { for arg in env::args() { - let arg_str = "channel_name:"; + let arg_str = &*format!("channel_name-{}:", which); if arg.starts_with(arg_str) { return Some(arg[arg_str.len()..].to_owned()); } @@ -684,7 +684,7 @@ fn server_connect_first() { fn cross_process_server() { let data: &[u8] = b"1234567"; - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -703,7 +703,7 @@ fn cross_process_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -743,7 +743,7 @@ fn cross_process_fork() { #[ignore] fn cross_process_sender_transfer_server() { - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -766,7 +766,7 @@ fn cross_process_sender_transfer_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_sender_transfer_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -1104,7 +1104,7 @@ mod sync_test { fn cross_process_two_step_transfer_server() { let cookie: &[u8] = b"cookie"; - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -1162,7 +1162,7 @@ fn cross_process_two_step_transfer_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_two_step_transfer_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) From 1f22aa08faa21f37ba27751cdf843d6913056d7d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:38:56 +0200 Subject: [PATCH 005/113] tests: Move `get_channel_name_arg()` helper to `src/test.rs` In preparation for adding `_spawn()` test variants for the tests in `src/test.rs` as well, we need to move the helper there, so it can be used in both `src/test.rs` and `src/platform/test.rs` -- just like the `fork()` helpers. --- src/platform/test.rs | 10 +--------- src/test.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 6a43fd296..de26cf34a 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -29,15 +29,7 @@ use crate::test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -pub fn get_channel_name_arg(which: &str) -> Option { - for arg in env::args() { - let arg_str = &*format!("channel_name-{}:", which); - if arg.starts_with(arg_str) { - return Some(arg[arg_str.len()..].to_owned()); - } - } - None -} +use test::get_channel_name_arg; #[test] fn simple() { diff --git a/src/test.rs b/src/test.rs index 71e91b56d..225f9a59b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -26,6 +26,8 @@ use crossbeam_channel::{self, Sender}; use libc; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cell::RefCell; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::env; use std::iter; #[cfg(not(any( feature = "force-inprocess", @@ -95,6 +97,19 @@ impl Wait for libc::pid_t { } } +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +pub fn get_channel_name_arg(which: &str) -> Option { + for arg in env::args() { + let arg_str = &*format!("channel_name-{}:", which); + if arg.starts_with(arg_str) { + return Some(arg[arg_str.len()..].to_owned()); + } + } + None +} + type Person = (String, u32); #[test] From ad1b9ff29ac27afe38190333422f94973e95e107 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:41:53 +0200 Subject: [PATCH 006/113] tests: Introduce `cross_process_embedded_senders_spawn()` Just like the other cross-process tests, this one needs a variant using `spawn()` instead of `fork()`, so it can work on platforms that don't provide `fork()`. --- src/test.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 225f9a59b..261c1008e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -35,6 +35,13 @@ use std::iter; target_os = "android", target_os = "ios" )))] +use std::process::{Command, Stdio}; +#[cfg(not(any( + feature = "force-inprocess", + target_os = "windows", + target_os = "android", + target_os = "ios" +)))] use std::ptr; use std::sync::Arc; use std::thread; @@ -202,6 +209,74 @@ fn select() { } } +// Note! This test is actually used by the cross_process_embedded_senders_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any( + feature = "force-inprocess", + target_os = "windows", + target_os = "android", + target_os = "ios" +)))] +#[test] +#[ignore] +fn cross_process_embedded_senders_server() { + let person = ("Patrick Walton".to_owned(), 29); + + let server0_name = if let Some(name) = get_channel_name_arg("server0") { + name + } else { + return + }; + let server2_name = if let Some(name) = get_channel_name_arg("server2") { + name + } else { + return + }; + + let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); + let tx0 = IpcSender::connect(server0_name).unwrap(); + tx0.send(tx1).unwrap(); + rx1.recv().unwrap(); + let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); + tx2.send(person.clone()).unwrap(); + + unsafe { libc::exit(0); } +} + +#[cfg(not(any( + feature = "force-inprocess", + target_os = "windows", + target_os = "android", + target_os = "ios" +)))] +#[test] +fn cross_process_embedded_senders_spawn() { + let person = ("Patrick Walton".to_owned(), 29); + + let (server0, server0_name) = IpcOneShotServer::new().unwrap(); + let (server2, server2_name) = IpcOneShotServer::new().unwrap(); + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_embedded_senders_server") + .arg(format!("channel_name-server0:{}", server0_name)) + .arg(format!("channel_name-server2:{}", server2_name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); + tx1.send(person.clone()).unwrap(); + let (_, received_person): (_, Person) = server2.accept().unwrap(); + + child_pid.wait().expect("failed to wait on child"); + + assert_eq!(received_person, person); +} + #[cfg(not(any( feature = "force-inprocess", target_os = "windows", @@ -209,7 +284,7 @@ fn select() { target_os = "ios" )))] #[test] -fn cross_process_embedded_senders() { +fn cross_process_embedded_senders_fork() { let person = ("Patrick Walton".to_owned(), 29); let (server0, server0_name) = IpcOneShotServer::new().unwrap(); let (server2, server2_name) = IpcOneShotServer::new().unwrap(); From e4c83d1ce52cb0ca1ec3ff528e45124295f0a9db Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 21:26:49 +0200 Subject: [PATCH 007/113] tests: cleanup: Streamline structure of `_server()` helpers Change the way parameter unwrapping / conditional execution is handled, so the active code in the `*_server()` functions is more in line with the corresponding `*_fork()` variants. This should faciliate keeping the fork/spawn variants in sync. --- src/platform/test.rs | 80 +++++++++++++++++++++----------------------- src/test.rs | 31 +++++++---------- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index de26cf34a..58329900a 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -677,13 +677,12 @@ fn cross_process_server() { let data: &[u8] = b"1234567"; let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } + if let Some(channel_name) = channel_name { + let tx = OsIpcSender::connect(channel_name).unwrap(); + tx.send(data, vec![], vec![]).unwrap(); - let tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); - tx.send(data, vec![], vec![]).unwrap(); - unsafe { libc::exit(0); } + unsafe { libc::exit(0); } + } } #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] @@ -736,18 +735,17 @@ fn cross_process_fork() { fn cross_process_sender_transfer_server() { let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } + if let Some(channel_name) = channel_name { + let super_tx = OsIpcSender::connect(channel_name).unwrap(); + let (sub_tx, sub_rx) = platform::channel().unwrap(); + let data: &[u8] = b"foo"; + super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + sub_rx.recv().unwrap(); + let data: &[u8] = b"bar"; + super_tx.send(data, vec![], vec![]).unwrap(); - let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); - let (sub_tx, sub_rx) = platform::channel().unwrap(); - let data: &[u8] = b"foo"; - super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); - sub_rx.recv().unwrap(); - let data: &[u8] = b"bar"; - super_tx.send(data, vec![], vec![]).unwrap(); - unsafe { libc::exit(0); } + unsafe { libc::exit(0); } + } } #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] @@ -1097,38 +1095,36 @@ fn cross_process_two_step_transfer_server() { let cookie: &[u8] = b"cookie"; let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } - - // connect by name to our other process - let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + if let Some(channel_name) = channel_name { + // connect by name to our other process + let super_tx = OsIpcSender::connect(channel_name).unwrap(); - // create a channel for real communication between the two processes - let (sub_tx, sub_rx) = platform::channel().unwrap(); + // create a channel for real communication between the two processes + let (sub_tx, sub_rx) = platform::channel().unwrap(); - // send the other process the tx side, so it can send us the channels - super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + // send the other process the tx side, so it can send us the channels + super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); - // get two_rx from the other process - let (_, mut received_channels, _) = sub_rx.recv().unwrap(); - assert_eq!(received_channels.len(), 1); - let two_rx = received_channels[0].to_receiver(); + // get two_rx from the other process + let (_, mut received_channels, _) = sub_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let two_rx = received_channels[0].to_receiver(); - // get one_rx from two_rx's buffer - let (_, mut received_channels, _) = two_rx.recv().unwrap(); - assert_eq!(received_channels.len(), 1); - let one_rx = received_channels[0].to_receiver(); + // get one_rx from two_rx's buffer + let (_, mut received_channels, _) = two_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let one_rx = received_channels[0].to_receiver(); - // get a cookie from one_rx - let (data, _, _) = one_rx.recv().unwrap(); - assert_eq!(&data[..], cookie); + // get a cookie from one_rx + let (data, _, _) = one_rx.recv().unwrap(); + assert_eq!(&data[..], cookie); - // finally, send a cookie back - super_tx.send(&data, vec![], vec![]).unwrap(); + // finally, send a cookie back + super_tx.send(&data, vec![], vec![]).unwrap(); - // terminate - unsafe { libc::exit(0); } + // terminate + unsafe { libc::exit(0); } + } } // TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! diff --git a/src/test.rs b/src/test.rs index 261c1008e..a1c012e69 100644 --- a/src/test.rs +++ b/src/test.rs @@ -223,25 +223,18 @@ fn select() { fn cross_process_embedded_senders_server() { let person = ("Patrick Walton".to_owned(), 29); - let server0_name = if let Some(name) = get_channel_name_arg("server0") { - name - } else { - return - }; - let server2_name = if let Some(name) = get_channel_name_arg("server2") { - name - } else { - return - }; - - let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); - let tx0 = IpcSender::connect(server0_name).unwrap(); - tx0.send(tx1).unwrap(); - rx1.recv().unwrap(); - let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); - tx2.send(person.clone()).unwrap(); - - unsafe { libc::exit(0); } + let server0_name = get_channel_name_arg("server0"); + let server2_name = get_channel_name_arg("server2"); + if let (Some(server0_name), Some(server2_name)) = (server0_name, server2_name) { + let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); + let tx0 = IpcSender::connect(server0_name).unwrap(); + tx0.send(tx1).unwrap(); + rx1.recv().unwrap(); + let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); + tx2.send(person.clone()).unwrap(); + + unsafe { libc::exit(0); } + } } #[cfg(not(any( From 50fe7c4b5e6a371ab90f535c2a1f92a9a3c48ede Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 26 Jul 2017 21:17:19 +0200 Subject: [PATCH 008/113] tests: cleanup: Introduce a common `spawn_server()` helper function --- src/platform/test.rs | 38 ++++++-------------------------------- src/test.rs | 30 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 58329900a..f98a0955a 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -10,13 +10,9 @@ use crate::platform::{self, OsIpcChannel, OsIpcReceiverSet}; use crate::platform::{OsIpcSharedMemory}; use std::collections::HashMap; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use std::env; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc; @@ -29,7 +25,7 @@ use crate::test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use test::get_channel_name_arg; +use test::{get_channel_name_arg, spawn_server}; #[test] fn simple() { @@ -691,15 +687,7 @@ fn cross_process_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let data: &[u8] = b"1234567"; - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_server", &[("server", &*name)]); let (_, received_data, received_channels, received_shared_memory_regions) = server.accept().unwrap(); @@ -753,15 +741,8 @@ fn cross_process_sender_transfer_server() fn cross_process_sender_transfer_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_sender_transfer_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_sender_transfer_server", + &[("server", &*name)]); let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); @@ -1147,15 +1128,8 @@ fn cross_process_two_step_transfer_spawn() { // create a one-shot server, and spawn another process let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_two_step_transfer_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_two_step_transfer_server", + &[("server", &*name)]); // The other process will have sent us a transmit channel in received channels let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); diff --git a/src/test.rs b/src/test.rs index a1c012e69..1d85b93a8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -35,7 +35,7 @@ use std::iter; target_os = "android", target_os = "ios" )))] -use std::process::{Command, Stdio}; +use std::process::{self, Command, Stdio}; #[cfg(not(any( feature = "force-inprocess", target_os = "windows", @@ -117,6 +117,22 @@ pub fn get_channel_name_arg(which: &str) -> Option { None } +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { + Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg(test_name) + .args(server_args.iter() + .map(|&(ref name, ref val)| format!("channel_name-{}:{}", name, val))) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process") +} + type Person = (String, u32); #[test] @@ -250,16 +266,8 @@ fn cross_process_embedded_senders_spawn() { let (server0, server0_name) = IpcOneShotServer::new().unwrap(); let (server2, server2_name) = IpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_embedded_senders_server") - .arg(format!("channel_name-server0:{}", server0_name)) - .arg(format!("channel_name-server2:{}", server2_name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_embedded_senders_server", + &[("server0", &*server0_name), ("server2", &*server2_name)]); let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); tx1.send(person.clone()).unwrap(); From d52be622a0b26abec59502f6ba393477a4f6728e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 26 Jul 2017 21:37:13 +0200 Subject: [PATCH 009/113] tests: cleanup: Avoid need for separate `_server()` pseudo-tests Integrate the server functionality right into the main `_spawn()` test functions: running either the server or the client portions as necessary. This further aligns the structure of these tests with the corresponding `_fork()` variants. It also avoids the need for the `#[ignore]`/`--ignored` hack, since all the test functions now always run -- either as normal tests (client parts); or as pseudo-tests, when self-spawned in server mode. --- src/platform/test.rs | 53 +++++++++----------------------------------- src/test.rs | 32 +------------------------- 2 files changed, 12 insertions(+), 73 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index f98a0955a..d1ce8b395 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -663,15 +663,11 @@ fn server_connect_first() { (data, vec![], vec![])); } -// Note! This test is actually used by the cross_process_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -#[ignore] -fn cross_process_server() -{ +fn cross_process_spawn() { let data: &[u8] = b"1234567"; + let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { let tx = OsIpcSender::connect(channel_name).unwrap(); @@ -679,15 +675,9 @@ fn cross_process_server() unsafe { libc::exit(0); } } -} -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[test] -fn cross_process_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - let data: &[u8] = b"1234567"; - - let mut child_pid = spawn_server("cross_process_server", &[("server", &*name)]); + let mut child_pid = spawn_server("cross_process_spawn", &[("server", &*name)]); let (_, received_data, received_channels, received_shared_memory_regions) = server.accept().unwrap(); @@ -714,14 +704,9 @@ fn cross_process_fork() { (data, vec![], vec![])); } -// Note! This test is actually used by the cross_process_sender_transfer_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -#[ignore] -fn cross_process_sender_transfer_server() -{ +fn cross_process_sender_transfer_spawn() { let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { let super_tx = OsIpcSender::connect(channel_name).unwrap(); @@ -734,15 +719,9 @@ fn cross_process_sender_transfer_server() unsafe { libc::exit(0); } } -} -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[test] -fn cross_process_sender_transfer_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - - let mut child_pid = spawn_server("cross_process_sender_transfer_server", - &[("server", &*name)]); + let mut child_pid = spawn_server("cross_process_sender_transfer_spawn", &[("server", &*name)]); let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); @@ -1066,15 +1045,14 @@ mod sync_test { } } -// Note! This test is actually used by the -// cross_process_two_step_transfer_spawn() test below. Running it by -// itself is meaningless, but it passes if run this way. +// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg_attr(target_os = "macos", ignore)] #[test] -#[ignore] -fn cross_process_two_step_transfer_server() -{ +fn cross_process_two_step_transfer_spawn() { let cookie: &[u8] = b"cookie"; + let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { // connect by name to our other process @@ -1106,15 +1084,6 @@ fn cross_process_two_step_transfer_server() // terminate unsafe { libc::exit(0); } } -} - -// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! -// Needs investigation. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[cfg_attr(target_os = "macos", ignore)] -#[test] -fn cross_process_two_step_transfer_spawn() { - let cookie: &[u8] = b"cookie"; // create channel 1 let (one_tx, one_rx) = platform::channel().unwrap(); @@ -1128,7 +1097,7 @@ fn cross_process_two_step_transfer_spawn() { // create a one-shot server, and spawn another process let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = spawn_server("cross_process_two_step_transfer_server", + let mut child_pid = spawn_server("cross_process_two_step_transfer_spawn", &[("server", &*name)]); // The other process will have sent us a transmit channel in received channels diff --git a/src/test.rs b/src/test.rs index 1d85b93a8..c2c45424a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -122,7 +122,6 @@ pub fn get_channel_name_arg(which: &str) -> Option { #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { Command::new(env::current_exe().unwrap()) - .arg("--ignored") .arg(test_name) .args(server_args.iter() .map(|&(ref name, ref val)| format!("channel_name-{}:{}", name, val))) @@ -225,9 +224,6 @@ fn select() { } } -// Note! This test is actually used by the cross_process_embedded_senders_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any( feature = "force-inprocess", target_os = "windows", @@ -235,8 +231,7 @@ fn select() { target_os = "ios" )))] #[test] -#[ignore] -fn cross_process_embedded_senders_server() { +fn cross_process_embedded_senders_spawn() { let person = ("Patrick Walton".to_owned(), 29); let server0_name = get_channel_name_arg("server0"); @@ -253,31 +248,6 @@ fn cross_process_embedded_senders_server() { } } -#[cfg(not(any( - feature = "force-inprocess", - target_os = "windows", - target_os = "android", - target_os = "ios" -)))] -#[test] -fn cross_process_embedded_senders_spawn() { - let person = ("Patrick Walton".to_owned(), 29); - - let (server0, server0_name) = IpcOneShotServer::new().unwrap(); - let (server2, server2_name) = IpcOneShotServer::new().unwrap(); - - let mut child_pid = spawn_server("cross_process_embedded_senders_server", - &[("server0", &*server0_name), ("server2", &*server2_name)]); - - let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); - tx1.send(person.clone()).unwrap(); - let (_, received_person): (_, Person) = server2.accept().unwrap(); - - child_pid.wait().expect("failed to wait on child"); - - assert_eq!(received_person, person); -} - #[cfg(not(any( feature = "force-inprocess", target_os = "windows", From 3347fc19fa6c0dd15f589b2d7163d1fb3fd62c8c Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 010/113] Implement ipc-channel on Windows This implementation uses named pipes on Windows, with auto-generated uuid names. It takes advantage of DuplicateHandle to clone handles to target processes when sending handles cross-process. Shared memory is implemented using anonymous file mappings. select() and friends are implemented using IO Completion Ports. --- Cargo.toml | 10 + src/lib.rs | 5 + src/platform/mod.rs | 11 +- src/platform/test.rs | 30 +- src/platform/windows/mod.rs | 1485 +++++++++++++++++++++++++++++++++++ src/test.rs | 22 +- 6 files changed, 1541 insertions(+), 22 deletions(-) create mode 100644 src/platform/windows/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 3184b107f..dd6f39252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,13 @@ edition = "2018" force-inprocess = [] memfd = ["sc"] unstable = [] +<<<<<<< HEAD async = ["futures-preview", "futures-test-preview"] +win32-trace = [] +======= +async = ["futures"] +win32-trace = [] +>>>>>>> Implement ipc-channel on Windows [dependencies] bincode = "1" @@ -32,3 +38,7 @@ sc = { version = "0.2.2", optional = true } [dev-dependencies] crossbeam = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = "0.2" +kernel32-sys = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 78d362f61..162f62baf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,11 @@ extern crate futures_test; #[cfg(feature = "async")] pub mod asynch; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +extern crate winapi; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +extern crate kernel32; + pub mod ipc; pub mod platform; pub mod router; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 4ba32d111..928a31245 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -25,9 +25,16 @@ mod os { pub use super::macos::*; } -#[cfg(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"))] +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +mod windows; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +mod os { + pub use super::windows::*; +} + +#[cfg(any(feature = "force-inprocess", target_os = "android", target_os = "ios"))] mod inprocess; -#[cfg(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"))] +#[cfg(any(feature = "force-inprocess", target_os = "android", target_os = "ios"))] mod os { pub use super::inprocess::*; } diff --git a/src/platform/test.rs b/src/platform/test.rs index d1ce8b395..835cb1079 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -14,17 +14,22 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use libc; use crate::platform::{OsIpcSender, OsIpcOneShotServer}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +<<<<<<< HEAD use crate::test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] +======= +use test::{fork, Wait}; +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] +>>>>>>> Implement ipc-channel on Windows use test::{get_channel_name_arg, spawn_server}; #[test] @@ -216,7 +221,8 @@ fn with_n_fds(n: usize, size: usize) { // These tests only apply to platforms that need fragmentation. #[cfg(all(not(feature = "force-inprocess"), any(target_os = "linux", - target_os = "freebsd")))] + target_os = "freebsd", + target_os = "windows")))] mod fragment_tests { use crate::platform; use super::with_n_fds; @@ -663,7 +669,7 @@ fn server_connect_first() { (data, vec![], vec![])); } -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] #[test] fn cross_process_spawn() { let data: &[u8] = b"1234567"; @@ -704,7 +710,7 @@ fn cross_process_fork() { (data, vec![], vec![])); } -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] #[test] fn cross_process_sender_transfer_spawn() { let channel_name = get_channel_name_arg("server"); @@ -1045,10 +1051,16 @@ mod sync_test { } } -// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! -// Needs investigation. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[cfg_attr(target_os = "macos", ignore)] +// This test panics on Windows, because the other process will panic +// when it detects that it receives handles that are intended for another +// process. It's marked as ignore/known-fail on Windows for this reason. +// +// TODO -- this fails on OSX as well with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. It may be a similar underlying issue, just done by +// the kernel instead of explicitly (ports in a message that's already +// buffered are intended for only one process). +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] #[test] fn cross_process_two_step_transfer_spawn() { let cookie: &[u8] = b"cookie"; diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs new file mode 100644 index 000000000..573390749 --- /dev/null +++ b/src/platform/windows/mod.rs @@ -0,0 +1,1485 @@ +// Copyright 2016 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use serde; +use bincode; +use kernel32; +use libc::intptr_t; +use std::cell::{Cell, RefCell}; +use std::cmp::PartialEq; +use std::default::Default; +use std::env; +use std::ffi::CString; +use std::io::{Error, ErrorKind}; +use std::marker::{Send, Sync, PhantomData}; +use std::mem; +use std::ops::{Deref, DerefMut, RangeFrom}; +use std::ptr; +use std::slice; +use uuid::Uuid; +use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; +use winapi; + +lazy_static! { + static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; + static ref CURRENT_PROCESS_HANDLE: intptr_t = unsafe { kernel32::GetCurrentProcess() as intptr_t }; + + static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; +} + +// some debug bump macros to better track what's going on in case of errors +macro_rules! win32_trace { ($($rest:tt)*) => { + if cfg!(feature = "win32-trace") { + if *DEBUG_TRACE_ENABLED { println!($($rest)*); } + } +} } + +// When we create the pipe, how big of a write buffer do we specify? +// This is reserved in the nonpaged pool. The fragment size is the +// max we can write to the pipe without fragmentation, and the +// buffer size is what we tell the pipe it is, so we have room +// for out of band data etc. +const MAX_FRAGMENT_SIZE: usize = 64 * 1024; + +// Size of the pipe's write buffer, with excess room for the header. +const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024; + +#[allow(non_snake_case)] +fn GetLastError() -> u32 { + unsafe { + kernel32::GetLastError() + } +} + +pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { + let pipe_id = make_pipe_id(); + let pipe_name = make_pipe_name(&pipe_id); + + let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); + let sender = try!(OsIpcSender::connect_named(&pipe_name)); + + Ok((sender, receiver)) +} + +// Holds data len and out-of-band data len +struct MessageHeader(u32, u32); + +impl MessageHeader { + fn size() -> usize { + mem::size_of::() + } + + fn total_message_bytes_needed(&self) -> usize { + MessageHeader::size() + self.0 as usize + self.1 as usize + } +} + +struct Message<'data> { + data_len: usize, + oob_len: usize, + bytes: &'data [u8], +} + +impl<'data> Message<'data> { + fn from_bytes(bytes: &'data [u8]) -> Option { + if bytes.len() < MessageHeader::size() { + return None; + } + + unsafe { + let ref header = *(bytes.as_ptr() as *const MessageHeader); + if bytes.len() < header.total_message_bytes_needed() { + return None; + } + + Some(Message { + data_len: header.0 as usize, + oob_len: header.1 as usize, + bytes: &bytes[0..header.total_message_bytes_needed()], + }) + } + } + + fn data(&self) -> &[u8] { + &self.bytes[MessageHeader::size()..(MessageHeader::size() + self.data_len)] + } + + fn oob_bytes(&self) -> &[u8] { + &self.bytes[(MessageHeader::size() + self.data_len)..] + } + + fn oob_data(&self) -> Option { + if self.oob_len > 0 { + + let oob = bincode::deserialize::(self.oob_bytes()) + .expect("Failed to deserialize OOB data"); + if oob.target_process_id != *CURRENT_PROCESS_ID { + panic!("Windows IPC channel received handles intended for pid {}, but this is pid {}. \ + This likely happened because a receiver was transferred while it had outstanding data \ + that contained a channel or shared memory in its pipe. \ + This isn't supported in the Windows implementation.", + oob.target_process_id, *CURRENT_PROCESS_ID); + } + Some(oob) + } else { + None + } + } + + fn size(&self) -> usize { + MessageHeader::size() + self.data_len + self.oob_len + } +} + +// If we have any channel handles or shmem segments, then we'll send an +// OutOfBandMessage after the data message. +// +// This includes the receiver's process ID, which the receiver checks to +// make sure that the message was originally sent to it, and was not sitting +// in another channel's buffer when that channel got transferred to another +// process. On Windows, we duplicate handles on the sender side to a specific +// reciever. If the wrong receiver gets it, those handles are not valid. +// +// TODO(vlad): We could attempt to recover from the above situation by +// duplicating from the intended target process to ourselves (the receiver). +// That would only work if the intended process a) still exists; b) can be +// opened by the receiver with handle dup privileges. Another approach +// could be to use a separate dedicated process intended purely for handle +// passing, though that process would need to be global to any processes +// amongst which you want to share channels or connect one-shot servers to. +// There may be a system process that we could use for this purpose, but +// I haven't foundone -- and in the system process case, we'd need to ensure +// that we don't leak the handles (e.g. dup a handle to the system process, +// and then everything dies -- we don't want those resources to be leaked). +#[derive(Debug)] +struct OutOfBandMessage { + target_process_id: u32, + channel_handles: Vec, + shmem_handles: Vec<(intptr_t, u64)>, // handle and size + big_data_receiver_handle: Option<(intptr_t, u64)>, // handle and size +} + +impl OutOfBandMessage { + fn new(target_id: u32) -> OutOfBandMessage { + OutOfBandMessage { + target_process_id: target_id, + channel_handles: vec![], + shmem_handles: vec![], + big_data_receiver_handle: None, + } + } + + fn needs_to_be_sent(&self) -> bool { + !self.channel_handles.is_empty() || + !self.shmem_handles.is_empty() || + self.big_data_receiver_handle.is_some() + } +} + +impl serde::Serialize for OutOfBandMessage { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer + { + ((self.target_process_id, + &self.channel_handles, + &self.shmem_handles, + &self.big_data_receiver_handle)).serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for OutOfBandMessage { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> + { + let (target_process_id, channel_handles, shmem_handles, big_data_receiver_handle) = + try!(serde::Deserialize::deserialize(deserializer)); + Ok(OutOfBandMessage { + target_process_id: target_process_id, + channel_handles: channel_handles, + shmem_handles: shmem_handles, + big_data_receiver_handle: big_data_receiver_handle + }) + } +} + +fn make_pipe_id() -> Uuid { + Uuid::new_v4() +} + +fn make_pipe_name(pipe_id: &Uuid) -> CString { + CString::new(format!("\\\\.\\pipe\\rust-ipc-{}", pipe_id.to_string())).unwrap() +} + +// Duplicate a given handle from this process to the target one, passing the +// given flags to DuplicateHandle. +// +// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is +// also the pseudohandle for the current process). +unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) + -> Result +{ + if handle == INVALID_HANDLE_VALUE { + return Ok(INVALID_HANDLE_VALUE); + } + + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let ok = kernel32::DuplicateHandle(*CURRENT_PROCESS_HANDLE as HANDLE, handle, + other_process, &mut new_handle, + 0, winapi::FALSE, flags); + if ok == winapi::FALSE { + Err(WinError::last("DuplicateHandle")) + } else { + Ok(new_handle) + } +} + +// duplicate a handle in the current process +fn dup_handle(handle: &WinHandle) -> Result { + dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) +} + +// duplicate a handle to the target process +fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { + unsafe { + let h = try!(dup_handle_to_process_with_flags( + **handle, **other_process, winapi::DUPLICATE_SAME_ACCESS)); + Ok(WinHandle::new(h)) + } +} + +// duplicate a handle to the target process, closing the source handle +fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { + unsafe { + let h = try!(dup_handle_to_process_with_flags( + handle.take(), **other_process, + winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); + Ok(WinHandle::new(h)) + } +} + +#[derive(Debug)] +struct WinHandle { + h: HANDLE +} + +unsafe impl Send for WinHandle { } +unsafe impl Sync for WinHandle { } + +impl Drop for WinHandle { + fn drop(&mut self) { + unsafe { + kernel32::CloseHandle(self.h); + } + } +} + +impl Default for WinHandle { + fn default() -> WinHandle { + WinHandle { h: INVALID_HANDLE_VALUE } + } +} + +impl Deref for WinHandle { + type Target = HANDLE; + + #[inline] + fn deref(&self) -> &HANDLE { + &self.h + } +} + +impl PartialEq for WinHandle { + fn eq(&self, other: &WinHandle) -> bool { + // FIXME this is not correct! We need to compare the object + // the handles refer to. On Windows 10, we have: + // unsafe { kernel32::CompareObjectHandles(self.h, other.h) == winapi::TRUE } + // But that + self.h == other.h + } +} + +impl WinHandle { + fn new(h: HANDLE) -> WinHandle { + WinHandle { h: h } + } + + fn invalid() -> WinHandle { + WinHandle { h: INVALID_HANDLE_VALUE } + } + + fn is_valid(&self) -> bool { + self.h != INVALID_HANDLE_VALUE + } + + fn take(&mut self) -> HANDLE { + mem::replace(&mut self.h, INVALID_HANDLE_VALUE) + } +} + +enum GetMessageResult { + NoMessage, + Message(Vec, Vec, Vec), +} + +// MessageReader implements blocking/nonblocking reads of messages +// from the handle +#[derive(Debug)] +struct MessageReader { + // The pipe read handle + handle: WinHandle, + + // The OVERLAPPED struct for async IO on this receiver; we'll only + // ever have one in flight + ov: Box, + + // A read buffer for any pending reads + read_buf: Vec, + + // If we have already issued an async read + read_in_progress: bool, + + // If we received a BROKEN_PIPE or other error + // indicating that the remote end has closed the pipe + closed: bool, + + // If this is part of a Set, then this is the ID that is used to identify + // this reader. If this is None, then this isn't part of a set. + set_id: Option, +} + +impl MessageReader { + fn new(handle: WinHandle) -> MessageReader { + MessageReader { + handle: handle, + ov: Box::new(unsafe { mem::zeroed::() }), + read_buf: Vec::new(), + read_in_progress: false, + closed: false, + set_id: None, + } + } + + fn cancel_io(&mut self) { + unsafe { + if self.read_in_progress { + kernel32::CancelIoEx(*self.handle, self.ov.deref_mut()); + self.read_in_progress = false; + } + } + } + + // Called when we receive an IO Completion Packet for this handle. + fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + win32_trace!("[$ {:?}] notify_completion", self.handle); + + // mark a read as no longer in progress even before we check errors + self.read_in_progress = false; + + if err == winapi::ERROR_BROKEN_PIPE { + assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); + self.closed = true; + return Ok(()); + } + + let nbytes = self.ov.InternalHigh as u32; + let offset = self.ov.Offset; + + assert!(offset == 0); + + // if the remote end closed... + if err != winapi::ERROR_SUCCESS { + // This should never happen + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } + + unsafe { + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); + } + + Ok(()) + } + + // kick off an asynchronous read + fn start_read(&mut self) -> Result<(),WinError> { + if self.read_in_progress || self.closed { + return Ok(()); + } + + win32_trace!("[$ {:?}] start_read", self.handle); + + let buf_len = self.read_buf.len(); + let mut buf_cap = self.read_buf.capacity(); + let mut bytes_read: u32 = 0; + + if buf_len == buf_cap { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + buf_cap = self.read_buf.capacity(); + } + + // issue the read to the buffer, at the current length offset + unsafe { + *self.ov.deref_mut() = mem::zeroed(); + let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; + let max_read_bytes = buf_cap - buf_len; + let ok = kernel32::ReadFile(*self.handle, + buf_ptr.offset(buf_len as isize), + max_read_bytes as u32, + &mut bytes_read, + self.ov.deref_mut()); + + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + if ok == winapi::FALSE { + let err = GetLastError(); + if err == winapi::ERROR_BROKEN_PIPE { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + } + + if err == winapi::ERROR_IO_PENDING { + self.read_in_progress = true; + return Ok(()); + } + + Err(WinError::from_system(err, "ReadFile")) + } else { + self.read_in_progress = true; + Ok(()) + } + } + } + + // This is split between get_message and get_message_inner, so that + // this function can handle removing bytes from the buffer, since + // get_message_inner borrows the buffer. + fn get_message(&mut self) -> Result { + let drain_bytes; + let result; + if let Some(message) = Message::from_bytes(&self.read_buf) { + let mut channels: Vec = vec![]; + let mut shmems: Vec = vec![]; + let mut big_data = None; + + if let Some(oob) = message.oob_data() { + win32_trace!("[$ {:?}] msg with total {} bytes, {} channels, {} shmems, big data handle {:?}", + self.handle, message.data_len, oob.channel_handles.len(), oob.shmem_handles.len(), + oob.big_data_receiver_handle); + + unsafe { + for handle in oob.channel_handles.iter() { + channels.push(OsOpaqueIpcChannel::new(*handle as HANDLE)); + } + + for sh in oob.shmem_handles.iter() { + shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); + } + + if oob.big_data_receiver_handle.is_some() { + let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); + let receiver = OsIpcReceiver::from_handle(handle as HANDLE); + big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); + } + } + } + + let buf_data = big_data.unwrap_or_else(|| message.data().to_vec()); + + win32_trace!("[$ {:?}] get_message success -> {} bytes, {} channels, {} shmems", + self.handle, buf_data.len(), channels.len(), shmems.len()); + drain_bytes = Some(message.size()); + result = GetMessageResult::Message(buf_data, channels, shmems); + } else { + drain_bytes = None; + result = GetMessageResult::NoMessage; + } + + if let Some(size) = drain_bytes { + // If the only valid bytes in the buffer are what we just + // consumed, then just set the vector's length to 0. This + // avoids reallocations as in the drain() case, and is + // a significant speedup. + if self.read_buf.len() == size { + self.read_buf.clear(); + } else { + self.read_buf.drain(0..size); + } + } + + Ok(result) + } + + fn add_to_iocp(&mut self, iocp: HANDLE, set_id: u64) -> Result<(),WinError> { + unsafe { + assert!(self.set_id.is_none()); + + let ret = kernel32::CreateIoCompletionPort(*self.handle, + iocp, + *self.handle as winapi::ULONG_PTR, + 0); + if ret.is_null() { + return Err(WinError::last("CreateIoCompletionPort")); + } + + self.set_id = Some(set_id); + + // Make sure that the reader has a read in flight, + // otherwise a later select() will hang. + try!(self.start_read()); + + Ok(()) + } + } + + // This is a specialized read when the buffser size is known ahead of time, + // and without our typical message framing. It's only valid to call this + // as the one and only call after creating a MessageReader. + fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { + assert!(self.read_buf.len() == 0); + + // We use with_capacity() to allocate an uninitialized buffer, + // since we're going to read into it and don't need to + // zero it. + let mut buf = Vec::with_capacity(size); + while buf.len() < size { + // Because our handle is asynchronous, we have to do a two-part read -- + // first issue the operation, then wait for its completion. + unsafe { + let ov = self.ov.deref_mut(); + *ov = mem::zeroed(); + + let buf_len = buf.len(); + let dest_ptr = buf.as_mut_ptr().offset(buf_len as isize) as LPVOID; + + let bytes_left = (size - buf_len) as u32; + let mut bytes_read: u32 = 0; + + let ok = kernel32::ReadFile(*self.handle, + dest_ptr, + bytes_left, + &mut bytes_read, + ov); + if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { + return Err(WinError::last("ReadFile")); + } + + if ov.Internal as i32 == winapi::STATUS_PENDING { + let ok = kernel32::GetOverlappedResult(*self.handle, ov, &mut bytes_read, winapi::TRUE); + if ok == winapi::FALSE { + return Err(WinError::last("GetOverlappedResult")); + } + } else { + bytes_read = ov.InternalHigh as u32; + } + + let new_len = buf_len + bytes_read as usize; + buf.set_len(new_len); + } + } + + Ok(buf) + } +} + +#[derive(Debug)] +pub struct OsIpcReceiver { + // A MessageReader that implements most of the work of this + // MessageReader + reader: RefCell, +} + +unsafe impl Send for OsIpcReceiver { } + +impl PartialEq for OsIpcReceiver { + fn eq(&self, other: &OsIpcReceiver) -> bool { + self.reader.borrow().handle == other.reader.borrow().handle + } +} + +impl OsIpcReceiver { + unsafe fn from_handle(handle: HANDLE) -> OsIpcReceiver { + OsIpcReceiver { + reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + } + } + + fn new_named(pipe_name: &CString) -> Result { + unsafe { + // create the pipe server + let handle = + kernel32::CreateNamedPipeA(pipe_name.as_ptr(), + winapi::PIPE_ACCESS_INBOUND | winapi::FILE_FLAG_OVERLAPPED, + winapi::PIPE_TYPE_BYTE | winapi::PIPE_READMODE_BYTE | winapi::PIPE_REJECT_REMOTE_CLIENTS, + // 1 max instance of this pipe + 1, + // out/in buffer sizes + 0, PIPE_BUFFER_SIZE as u32, + 0, // default timeout for WaitNamedPipe (0 == 50ms as default) + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateNamedPipeA")); + } + + Ok(OsIpcReceiver { + reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + }) + } + } + + fn prepare_for_transfer(&mut self) -> Result { + let mut reader = self.reader.borrow_mut(); + // cancel any outstanding IO request + reader.cancel_io(); + // this is only okay if we have nothing in the read buf + Ok(reader.read_buf.is_empty()) + } + + pub fn consume(&self) -> OsIpcReceiver { + let mut handle = dup_handle(&self.reader.borrow().handle).unwrap(); + unsafe { OsIpcReceiver::from_handle(handle.take()) } + } + + fn receive_message(&self, mut block: bool) + -> Result<(Vec, Vec, Vec),WinError> { + // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then + // the implementation in select() is used. It does much the same thing, but across multiple + // channels. + + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. + unsafe { + let mut reader = self.reader.borrow_mut(); + assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + + loop { + // First, try to fetch a message, in case we have one pending + // in the reader's receive buffer + match try!(reader.get_message()) { + GetMessageResult::Message(data, channels, shmems) => + return Ok((data, channels, shmems)), + GetMessageResult::NoMessage => + {}, + } + + // If the pipe was already closed, we're done -- we've + // already drained all incoming bytes + if reader.closed { + return Err(WinError::ChannelClosed); + } + + // Then, issue a read if we don't have one already in flight. + // We must not issue a read if we have complete unconsumed + // messages, because getting a message modifies the read_buf. + try!(reader.start_read()); + + // If the last read flagged us closed we're done; we've already + // drained all incoming bytes earlier in the loop. + if reader.closed { + return Err(WinError::ChannelClosed); + } + + // Then, get the overlapped result, blocking if we need to. + let mut nbytes: u32 = 0; + let mut err = winapi::ERROR_SUCCESS; + let ok = kernel32::GetOverlappedResult(*reader.handle, reader.ov.deref_mut(), &mut nbytes, + if block { winapi::TRUE } else { winapi::FALSE }); + if ok == winapi::FALSE { + err = GetLastError(); + if !block && err == winapi::ERROR_IO_INCOMPLETE { + // Nonblocking read, no message, read's in flight, we're + // done. An error is expected in this case. + return Err(WinError::NoData); + } + // We pass err through to notify_completion so + // that it can handle other errors. + } + + // Notify that the read completed, which will update the + // read pointers + try!(reader.notify_completion(err)); + + // If we're not blocking, pretend that we are blocking, since we got part of + // a message already. Keep reading until we get a complete message. + block = true; + } + } + } + + pub fn recv(&self) + -> Result<(Vec, Vec, Vec),WinError> { + win32_trace!("recv"); + self.receive_message(true) + } + + pub fn try_recv(&self) + -> Result<(Vec, Vec, Vec),WinError> { + win32_trace!("try_recv"); + self.receive_message(false) + } + + // Do a pipe connect. Only used for one-shot servers + fn accept(&mut self) -> Result<(),WinError> { + unsafe { + let reader_borrow = self.reader.borrow(); + let handle = *reader_borrow.handle; + let mut ov = Box::new(mem::zeroed::()); + let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); + + // we should always get FALSE with async IO + assert!(ok == winapi::FALSE); + let err = GetLastError(); + + match err { + // did we successfully connect? (it's reported as an error [ok==false]) + winapi::ERROR_PIPE_CONNECTED => { + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle); + Ok(()) + }, + + // This is a weird one -- if we create a named pipe (like we do + // in new(), the client connects, sends data, then drops its handle, + // a Connect here will get ERROR_NO_DATA -- but there may be data in + // the pipe that we'll be able to read. So we need to go do some reads + // like normal and wait until ReadFile gives us ERROR_NO_DATA. + winapi::ERROR_NO_DATA => { + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle); + Ok(()) + }, + + // was it an actual error? + err if err != winapi::ERROR_IO_PENDING => { + win32_trace!("[$ {:?}] accept error -> {}", handle, err); + Err(WinError::last("ConnectNamedPipe")) + }, + + // the connect is pending; wait for it to complete + _ /* winapi::ERROR_IO_PENDING */ => { + let mut nbytes: u32 = 0; + let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + if ok == winapi::FALSE { + return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); + } + Ok(()) + }, + } + } + } + + // Does a single explicitly-sized recv from the handle, consuming + // the receiver in the process. This is used for receiving data + // from the out-of-band big data buffer. + fn recv_raw(self, size: usize) -> Result, WinError> { + self.reader.borrow_mut().read_raw_sized(size) + } +} + +#[derive(Debug, PartialEq)] +pub struct OsIpcSender { + // The client hande itself + handle: WinHandle, + // Make sure this is `!Sync`, to match `mpsc::Sender`; and to discourage sharing references. + // + // (Rather, senders should just be cloned, as they are shared internally anyway -- + // another layer of sharing only adds unnecessary overhead...) + nosync_marker: PhantomData>, +} + +unsafe impl Send for OsIpcSender { } + +impl Clone for OsIpcSender { + fn clone(&self) -> OsIpcSender { + unsafe { + let mut handle = dup_handle(&self.handle).unwrap(); + OsIpcSender::from_handle(handle.take()) + } + } +} + +// Write_msg, unlike write_buf, requires that bytes be sent +// in one operation. +fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { + if bytes.len() == 0 { + return Ok(()); + } + + let mut size: u32 = 0; + unsafe { + if kernel32::WriteFile(handle, + bytes.as_ptr() as LPVOID, + bytes.len() as u32, + &mut size, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + + if size != bytes.len() as u32 { + panic!("Windows IPC write_msg expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", size, bytes.len()); + } + + Ok(()) +} + +fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { + let total = bytes.len(); + if total == 0 { + return Ok(()); + } + + let mut written = 0; + while written < total { + let mut sz: u32 = 0; + unsafe { + let bytes_to_write = &bytes[written..]; + if kernel32::WriteFile(handle, + bytes_to_write.as_ptr() as LPVOID, + bytes_to_write.len() as u32, + &mut sz, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + written += sz as usize; + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle, sz, written, bytes.len(), GetLastError()); + } + + Ok(()) +} + +impl OsIpcSender { + pub fn connect(name: String) -> Result { + let pipe_name = make_pipe_name(&Uuid::parse_str(&name).unwrap()); + OsIpcSender::connect_named(&pipe_name) + } + + pub fn get_max_fragment_size() -> usize { + MAX_FRAGMENT_SIZE + } + + unsafe fn from_handle(handle: HANDLE) -> OsIpcSender { + OsIpcSender { + handle: WinHandle::new(handle), + nosync_marker: PhantomData, + } + } + + // Connect to a pipe server + fn connect_named(pipe_name: &CString) -> Result { + unsafe { + let handle = + kernel32::CreateFileA(pipe_name.as_ptr(), + winapi::GENERIC_WRITE, + 0, + ptr::null_mut(), // lpSecurityAttributes + winapi::OPEN_EXISTING, + winapi::FILE_ATTRIBUTE_NORMAL, + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateFileA")); + } + + win32_trace!("[c {:?}] connect_to_server success", handle); + + Ok(OsIpcSender::from_handle(handle)) + } + } + + fn get_pipe_server_process_id(&self) -> Result { + unsafe { + let mut server_pid: winapi::ULONG = 0; + if kernel32::GetNamedPipeServerProcessId(*self.handle, &mut server_pid) == winapi::FALSE { + return Err(WinError::last("GetNamedPipeServerProcessId")); + } + Ok(server_pid) + } + } + + fn get_pipe_server_process_handle_and_pid(&self) -> Result<(WinHandle, winapi::ULONG),WinError> { + unsafe { + let server_pid = try!(self.get_pipe_server_process_id()); + if server_pid == *CURRENT_PROCESS_ID { + return Ok((WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE), server_pid)); + } + + let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, + winapi::FALSE, + server_pid as winapi::DWORD); + if raw_handle.is_null() { + return Err(WinError::last("OpenProcess")); + } + + Ok((WinHandle::new(raw_handle), server_pid)) + } + } + + fn needs_fragmentation(data_len: usize, oob: &OutOfBandMessage) -> bool { + let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; + + // make sure we don't have too much oob data to begin with + assert!((oob_size as usize) < (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + + let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); + data_len >= bytes_left_for_data + } + + // An internal-use-only send method that sends just raw data, with + // no header. + fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { + win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, + try!(self.get_pipe_server_process_id())); + + write_buf(*self.handle, data) + } + + pub fn send(&self, + data: &[u8], + ports: Vec, + shared_memory_regions: Vec) + -> Result<(),WinError> + { + // We limit the max size we can send here; we can fix this + // just by upping the header to be 2x u64 if we really want + // to. + assert!(data.len() < u32::max_value() as usize); + + let (server_h, server_pid) = if !shared_memory_regions.is_empty() || !ports.is_empty() { + try!(self.get_pipe_server_process_handle_and_pid()) + } else { + (WinHandle::invalid(), 0) + }; + + let mut oob = OutOfBandMessage::new(server_pid); + + for ref shmem in shared_memory_regions { + // shmem.handle, shmem.length + let mut remote_handle = try!(dup_handle_to_process(&shmem.handle, &server_h)); + oob.shmem_handles.push((remote_handle.take() as intptr_t, shmem.length as u64)); + } + + for port in ports { + match port { + OsIpcChannel::Sender(mut s) => { + let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); + oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + }, + OsIpcChannel::Receiver(mut r) => { + if try!(r.prepare_for_transfer()) == false { + panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); + } + + let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.borrow_mut().handle, &server_h)); + oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + }, + } + } + + // Do we need to fragment? + let big_data_sender: Option = + if OsIpcSender::needs_fragmentation(data.len(), &oob) { + // We need to create a channel for the big data + let (sender, receiver) = try!(channel()); + + let (server_h, server_pid) = if server_h.is_valid() { + (server_h, server_pid) + } else { + try!(self.get_pipe_server_process_handle_and_pid()) + }; + + // Put the receiver in the OOB data + let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.borrow_mut().handle, &server_h)); + oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); + oob.target_process_id = server_pid; + + Some(sender) + } else { + None + }; + + // If we need to send OOB data, serialize it + let mut oob_data: Vec = vec![]; + if oob.needs_to_be_sent() { + oob_data = bincode::serialize(&oob).unwrap(); + } + + unsafe { + let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; + let full_in_band_len = MessageHeader::size() + in_band_data_len + oob_data.len(); + assert!(full_in_band_len <= PIPE_BUFFER_SIZE); + + let mut full_message = Vec::::with_capacity(full_in_band_len); + full_message.set_len(full_in_band_len); + + let header = full_message.as_mut_ptr() as *mut MessageHeader; + *header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + + if big_data_sender.is_none() { + &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); + &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); + try!(write_msg(*self.handle, &full_message)); + } else { + &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); + try!(write_msg(*self.handle, &full_message)); + try!(big_data_sender.unwrap().send_raw(data)); + } + } + + Ok(()) + } +} + +pub enum OsIpcSelectionResult { + DataReceived(u64, Vec, Vec, Vec), + ChannelClosed(u64), +} + +pub struct OsIpcReceiverSet { + // Our incrementor, for unique handle IDs + incrementor: RangeFrom, + + // the IOCP that we select on + iocp: WinHandle, + + // The set of receivers, stored as MessageReaders + readers: Vec, +} + +impl OsIpcReceiverSet { + pub fn new() -> Result { + unsafe { + let iocp = kernel32::CreateIoCompletionPort(INVALID_HANDLE_VALUE, + ptr::null_mut(), + 0 as winapi::ULONG_PTR, + 0); + if iocp.is_null() { + return Err(WinError::last("CreateIoCompletionPort")); + } + + Ok(OsIpcReceiverSet { + incrementor: 0.., + iocp: WinHandle::new(iocp), + readers: vec![], + }) + } + } + + pub fn add(&mut self, receiver: OsIpcReceiver) -> Result { + // consume the receiver, and take the reader out + let mut reader = receiver.reader.into_inner(); + + let set_id = self.incrementor.next().unwrap(); + try!(reader.add_to_iocp(*self.iocp, set_id)); + + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + + self.readers.push(reader); + + Ok(set_id) + } + + pub fn select(&mut self) -> Result,WinError> { + assert!(!self.readers.is_empty(), "selecting with no objects?"); + win32_trace!("[# {:?}] select() with {} receivers", *self.iocp, self.readers.len()); + + // the ultimate results + let mut selection_results = vec![]; + + // Make a quick first-run check for any closed receivers. + // This will only happen if we have a receiver that + // gets added to the Set after it was closed (the + // router_drops_callbacks_on_cloned_sender_shutdown test + // causes this.) + self.readers.retain(|ref r| { + if r.closed { + selection_results.push(OsIpcSelectionResult::ChannelClosed(r.set_id.unwrap())); + false + } else { + true + } + }); + + // if we had prematurely closed elements, just process them first + if !selection_results.is_empty() { + return Ok(selection_results); + } + + // Do this in a loop, because we may need to dequeue multiple packets to + // read a complete message. + loop { + let mut nbytes: u32 = 0; + let mut reader_index: Option = None; + let mut io_err = winapi::ERROR_SUCCESS; + + unsafe { + let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; + let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); + // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! + let ok = kernel32::GetQueuedCompletionStatus(*self.iocp, + &mut nbytes, + &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, + &mut ov_ptr, + winapi::INFINITE); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", *self.iocp, ok, nbytes, completion_key); + if ok == winapi::FALSE { + // If the OVERLAPPED result is NULL, then the + // function call itself failed or timed out. + // Otherwise, the async IO operation failed, and + // we want to hand io_err to notify_completion below. + if ov_ptr.is_null() { + return Err(WinError::last("GetQueuedCompletionStatus")); + } + + io_err = GetLastError(); + } + + assert!(!ov_ptr.is_null()); + assert!(completion_key != INVALID_HANDLE_VALUE); + + // Find the matching receiver + for (index, ref mut reader) in self.readers.iter_mut().enumerate() { + if completion_key != *reader.handle { + continue; + } + + reader_index = Some(index); + break; + } + } + + if reader_index.is_none() { + panic!("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + } + + let mut remove_index = None; + + // We need a scope here for the mutable borrow of self.readers; + // we need to (maybe) remove an element from it below. + { + let reader_index = reader_index.unwrap(); + let mut reader = &mut self.readers[reader_index]; + + win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); + + // tell it about the completed IO op + try!(reader.notify_completion(io_err)); + + // then drain as many messages as we can + loop { + match try!(reader.get_message()) { + GetMessageResult::Message(data, channels, shmems) => { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + }, + GetMessageResult::NoMessage => { + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + break; + }, + } + } + + // We may have already been closed, or the read resulted in us being closed. + // If so, add that to the result and remove the reader from our list. + if reader.closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + remove_index = Some(reader_index); + } else { + try!(reader.start_read()); + } + } + + if remove_index.is_some() { + self.readers.swap_remove(remove_index.unwrap()); + } + + // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; + // otherwise we're done. + if !selection_results.is_empty() { + break; + } + } + + win32_trace!("select() -> {} results", selection_results.len()); + Ok(selection_results) + } +} + +impl OsIpcSelectionResult { + pub fn unwrap(self) -> (u64, Vec, Vec, Vec) { + match self { + OsIpcSelectionResult::DataReceived(id, data, channels, shared_memory_regions) => { + (id, data, channels, shared_memory_regions) + } + OsIpcSelectionResult::ChannelClosed(id) => { + panic!("OsIpcSelectionResult::unwrap(): receiver ID {} was closed!", id) + } + } + } +} + +#[derive(Debug)] +pub struct OsIpcSharedMemory { + handle: WinHandle, + ptr: *mut u8, + length: usize, +} + +unsafe impl Send for OsIpcSharedMemory {} +unsafe impl Sync for OsIpcSharedMemory {} + +impl Drop for OsIpcSharedMemory { + fn drop(&mut self) { + unsafe { + kernel32::UnmapViewOfFile(self.ptr as LPVOID); + } + } +} + +impl Clone for OsIpcSharedMemory { + fn clone(&self) -> OsIpcSharedMemory { + unsafe { + let mut handle = dup_handle(&self.handle).unwrap(); + OsIpcSharedMemory::from_handle(handle.take(), self.length).unwrap() + } + } +} + +impl PartialEq for OsIpcSharedMemory { + fn eq(&self, other: &OsIpcSharedMemory) -> bool { + self.handle == other.handle + } +} + +impl Deref for OsIpcSharedMemory { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + assert!(!self.ptr.is_null() && self.handle.is_valid()); + unsafe { + slice::from_raw_parts(self.ptr, self.length) + } + } +} + +impl OsIpcSharedMemory { + #[allow(exceeding_bitshifts)] + fn new(length: usize) -> Result { + unsafe { + assert!(length < u32::max_value() as usize); + let (lhigh, llow) = (0 as u32, (length & 0xffffffffusize) as u32); + let handle = + kernel32::CreateFileMappingA(INVALID_HANDLE_VALUE, + ptr::null_mut(), + winapi::PAGE_READWRITE | winapi::SEC_COMMIT, + lhigh, llow, + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateFileMapping")); + } + + OsIpcSharedMemory::from_handle(handle, length) + } + } + + // There is no easy way to query the size of the mapping -- you + // can use NtQuerySection, but that's an undocumented NT kernel + // API. Instead we'll just always pass the length along. + // + // This function takes ownership of the handle, and will close it + // when finished. + unsafe fn from_handle(handle_raw: HANDLE, length: usize) -> Result { + // turn this into a WinHandle, because that will + // take care of closing it + let handle = WinHandle::new(handle_raw); + let address = kernel32::MapViewOfFile(handle_raw, + winapi::FILE_MAP_ALL_ACCESS, + 0, 0, 0); + if address.is_null() { + return Err(WinError::last("MapViewOfFile")); + } + + Ok(OsIpcSharedMemory { + handle: handle, + ptr: address as *mut u8, + length: length + }) + } + + pub fn from_byte(byte: u8, length: usize) -> OsIpcSharedMemory { + unsafe { + // panic if we can't create it + let mem = OsIpcSharedMemory::new(length).unwrap(); + for element in slice::from_raw_parts_mut(mem.ptr, mem.length) { + *element = byte; + } + mem + } + } + + pub fn from_bytes(bytes: &[u8]) -> OsIpcSharedMemory { + unsafe { + // panic if we can't create it + let mem = OsIpcSharedMemory::new(bytes.len()).unwrap(); + ptr::copy_nonoverlapping(bytes.as_ptr(), mem.ptr, bytes.len()); + mem + } + } +} + +pub struct OsIpcOneShotServer { + receiver: OsIpcReceiver, +} + +impl OsIpcOneShotServer { + pub fn new() -> Result<(OsIpcOneShotServer, String),WinError> { + let pipe_id = make_pipe_id(); + let pipe_name = make_pipe_name(&pipe_id); + let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); + Ok(( + OsIpcOneShotServer { + receiver: receiver, + }, + pipe_id.to_string() + )) + } + + pub fn accept(self) -> Result<(OsIpcReceiver, + Vec, + Vec, + Vec),WinError> { + let mut receiver = self.receiver; + try!(receiver.accept()); + let (data, channels, shmems) = try!(receiver.recv()); + Ok((receiver, data, channels, shmems)) + } +} + +pub enum OsIpcChannel { + Sender(OsIpcSender), + Receiver(OsIpcReceiver), +} + +#[derive(PartialEq, Debug)] +pub struct OsOpaqueIpcChannel { + handle: HANDLE, +} + +impl OsOpaqueIpcChannel { + fn new(handle: HANDLE) -> OsOpaqueIpcChannel { + OsOpaqueIpcChannel { + handle: handle, + } + } + + pub fn to_receiver(&mut self) -> OsIpcReceiver { + unsafe { OsIpcReceiver::from_handle(self.handle) } + } + + pub fn to_sender(&mut self) -> OsIpcSender { + unsafe { OsIpcSender::from_handle(self.handle) } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum WinError { + WindowsResult(u32), + ChannelClosed, + NoData, +} + +impl WinError { + pub fn error_string(errnum: u32) -> String { + // This value is calculated from the macro + // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) + let lang_id = 0x0800 as winapi::DWORD; + let mut buf = [0 as winapi::WCHAR; 2048]; + + unsafe { + let res = kernel32::FormatMessageW(winapi::FORMAT_MESSAGE_FROM_SYSTEM | + winapi::FORMAT_MESSAGE_IGNORE_INSERTS, + ptr::null_mut(), + errnum as winapi::DWORD, + lang_id, + buf.as_mut_ptr(), + buf.len() as winapi::DWORD, + ptr::null_mut()) as usize; + if res == 0 { + // Sometimes FormatMessageW can fail e.g. system doesn't like lang_id, + let fm_err = kernel32::GetLastError(); + return format!("OS Error {} (FormatMessageW() returned error {})", + errnum, fm_err); + } + + match String::from_utf16(&buf[..res]) { + Ok(msg) => { + // Trim trailing CRLF inserted by FormatMessageW + msg.trim().to_string() + }, + Err(..) => format!("OS Error {} (FormatMessageW() returned \ + invalid UTF-16)", errnum), + } + } + } + + fn from_system(err: u32, f: &str) -> WinError { + win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, f); + WinError::WindowsResult(err) + } + + fn last(f: &str) -> WinError { + WinError::from_system(GetLastError(), f) + } + + pub fn channel_is_closed(&self) -> bool { + match *self { + WinError::ChannelClosed => true, + _ => false, + } + } +} + +impl From for bincode::Error { + fn from(error: WinError) -> bincode::Error { + Error::from(error).into() + } +} + +impl From for Error { + fn from(error: WinError) -> Error { + match error { + WinError::ChannelClosed => { + Error::new(ErrorKind::BrokenPipe, "Win channel closed") + }, + WinError::NoData => { + Error::new(ErrorKind::WouldBlock, "Win channel has no data available") + }, + WinError::WindowsResult(err) => { + Error::from_raw_os_error(err as i32) + }, + } + } +} diff --git a/src/test.rs b/src/test.rs index c2c45424a..b6350c81b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,7 +9,6 @@ #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] @@ -19,26 +18,23 @@ use crate::router::ROUTER; use crossbeam_channel::{self, Sender}; #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] use libc; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cell::RefCell; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use std::env; use std::iter; #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] use std::process::{self, Command, Stdio}; #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] @@ -48,7 +44,6 @@ use std::thread; #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] @@ -56,7 +51,6 @@ use crate::ipc::IpcOneShotServer; #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] @@ -106,7 +100,7 @@ impl Wait for libc::pid_t { // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] pub fn get_channel_name_arg(which: &str) -> Option { for arg in env::args() { let arg_str = &*format!("channel_name-{}:", which); @@ -119,7 +113,7 @@ pub fn get_channel_name_arg(which: &str) -> Option { // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { Command::new(env::current_exe().unwrap()) .arg(test_name) @@ -226,7 +220,6 @@ fn select() { #[cfg(not(any( feature = "force-inprocess", - target_os = "windows", target_os = "android", target_os = "ios" )))] @@ -411,7 +404,14 @@ fn shared_memory() { let (tx, rx) = ipc::channel().unwrap(); tx.send(person_and_shared_memory.clone()).unwrap(); let received_person_and_shared_memory = rx.recv().unwrap(); - assert_eq!(received_person_and_shared_memory, person_and_shared_memory); + // On Windows, we don't have a way to check whether two handles + // refer to the same underlying object before Windows 10. It's questionable + // if this test *really* wants that anyway. + if cfg!(not(windows)) { + assert_eq!(received_person_and_shared_memory, person_and_shared_memory); + } else { + assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); + } assert!(person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); assert!(received_person_and_shared_memory .1 From 57bc52e7ccc0ab32a39eafb1dc54e4a7fa262a77 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 5 Nov 2017 15:00:44 +0100 Subject: [PATCH 011/113] appveyor.yml: Restore testing `inprocess` back-end, not only native one --- appveyor.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2b25ba4f3..2af4d1c64 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,8 +3,17 @@ environment: RUST_BACKTRACE: 1 matrix: - TARGET: x86_64-pc-windows-msvc + FEATURES: "unstable" - TARGET: i686-pc-windows-msvc + FEATURES: "unstable" - TARGET: i686-pc-windows-gnu + FEATURES: "unstable" + - TARGET: x86_64-pc-windows-msvc + FEATURES: "unstable force-inprocess" + - TARGET: i686-pc-windows-msvc + FEATURES: "unstable force-inprocess" + - TARGET: i686-pc-windows-gnu + FEATURES: "unstable force-inprocess" install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" @@ -14,4 +23,4 @@ install: build: false test_script: - - 'cargo test --verbose --features "unstable"' + - cargo test --verbose --features "%FEATURES%" From 3eb82d9f89e3e235e457f161d0122128d66bb726 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 18:56:07 +0200 Subject: [PATCH 012/113] tests: Split out SHM object equality check Move the check for finding equality of cloned/received SHM objects -- known to fail on Windows -- into a separate test case, rather than using a conditional in the main SHM test case. This makes it more explicit that this is a know limitation, and distinct from other SHM functionality. Also, more explicitly document this limitation in the relevant part of the Windows back-end implementation itself, rather than elaborating on it in the test case. --- src/platform/windows/mod.rs | 19 ++++++++++++++++--- src/test.rs | 21 +++++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 573390749..217eb431a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -296,10 +296,16 @@ impl Deref for WinHandle { impl PartialEq for WinHandle { fn eq(&self, other: &WinHandle) -> bool { - // FIXME this is not correct! We need to compare the object - // the handles refer to. On Windows 10, we have: + // FIXME This does not actually implement the desired behaviour: + // we want a way to compare the underlying objects the handles refer to, + // rather than just comparing the handles. + // + // On Windows 10, we could use: + // ``` // unsafe { kernel32::CompareObjectHandles(self.h, other.h) == winapi::TRUE } - // But that + // ``` + // + // This API call however is not available on older versions. self.h == other.h } } @@ -1269,6 +1275,13 @@ impl Clone for OsIpcSharedMemory { impl PartialEq for OsIpcSharedMemory { fn eq(&self, other: &OsIpcSharedMemory) -> bool { + // Due to the way `WinHandle.eq()` is currently implemented, + // this only finds equality when comparing the very same SHM structure -- + // it doesn't recognize cloned SHM structures as equal. + // (Neither when cloned explicitly, nor implicitly through an IPC transfer.) + // + // It's not clear though whether the inability to test this + // is really a meaningful limitation... self.handle == other.handle } } diff --git a/src/test.rs b/src/test.rs index b6350c81b..9d7216eef 100644 --- a/src/test.rs +++ b/src/test.rs @@ -404,14 +404,7 @@ fn shared_memory() { let (tx, rx) = ipc::channel().unwrap(); tx.send(person_and_shared_memory.clone()).unwrap(); let received_person_and_shared_memory = rx.recv().unwrap(); - // On Windows, we don't have a way to check whether two handles - // refer to the same underlying object before Windows 10. It's questionable - // if this test *really* wants that anyway. - if cfg!(not(windows)) { - assert_eq!(received_person_and_shared_memory, person_and_shared_memory); - } else { - assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); - } + assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); assert!(person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); assert!(received_person_and_shared_memory .1 @@ -419,6 +412,18 @@ fn shared_memory() { .all(|byte| *byte == 0xba)); } +#[test] +// The Windows implementation can't handle this case due to system API limitations. +#[cfg_attr(all(target_os = "windows", not(feature = "force-inprocess")), ignore)] +fn shared_memory_object_equality() { + let person = ("Patrick Walton".to_owned(), 29); + let person_and_shared_memory = (person, IpcSharedMemory::from_byte(0xba, 1024 * 1024)); + let (tx, rx) = ipc::channel().unwrap(); + tx.send(person_and_shared_memory.clone()).unwrap(); + let received_person_and_shared_memory = rx.recv().unwrap(); + assert_eq!(received_person_and_shared_memory, person_and_shared_memory); +} + #[test] fn opaque_sender() { let person = ("Patrick Walton".to_owned(), 29); From ab9973475f4b7039d94b7899362dce87a35d5e6d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 19:13:03 +0200 Subject: [PATCH 013/113] windows: fix warning: `reader` doesn't need to be mutable --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 217eb431a..9da2710c9 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1185,7 +1185,7 @@ impl OsIpcReceiverSet { // we need to (maybe) remove an element from it below. { let reader_index = reader_index.unwrap(); - let mut reader = &mut self.readers[reader_index]; + let reader = &mut self.readers[reader_index]; win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); From 75763d0aad2fd680262da94c873906513d83c525 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 22:51:57 +0200 Subject: [PATCH 014/113] windows: cleanup: Change suitable plain comments into doc comments Turn comments describing the purpose of data types, functions etc. into proper doc comments. Also restructure them as needed to fit the expected form for doc comments; and in some cases, clarify/enhance the contents too. --- src/platform/windows/mod.rs | 160 +++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 67 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9da2710c9..19e6d786b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -33,21 +33,22 @@ lazy_static! { static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } -// some debug bump macros to better track what's going on in case of errors +/// Debug macro to better track what's going on in case of errors. macro_rules! win32_trace { ($($rest:tt)*) => { if cfg!(feature = "win32-trace") { if *DEBUG_TRACE_ENABLED { println!($($rest)*); } } } } -// When we create the pipe, how big of a write buffer do we specify? -// This is reserved in the nonpaged pool. The fragment size is the -// max we can write to the pipe without fragmentation, and the -// buffer size is what we tell the pipe it is, so we have room -// for out of band data etc. +/// When we create the pipe, how big of a write buffer do we specify? +/// +/// This is reserved in the nonpaged pool. The fragment size is the +/// max we can write to the pipe without fragmentation, and the +/// buffer size is what we tell the pipe it is, so we have room +/// for out of band data etc. const MAX_FRAGMENT_SIZE: usize = 64 * 1024; -// Size of the pipe's write buffer, with excess room for the header. +/// Size of the pipe's write buffer, with excess room for the header. const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024; #[allow(non_snake_case)] @@ -67,7 +68,7 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { Ok((sender, receiver)) } -// Holds data len and out-of-band data len +/// Holds data len and out-of-band data len. struct MessageHeader(u32, u32); impl MessageHeader { @@ -137,26 +138,26 @@ impl<'data> Message<'data> { } } -// If we have any channel handles or shmem segments, then we'll send an -// OutOfBandMessage after the data message. -// -// This includes the receiver's process ID, which the receiver checks to -// make sure that the message was originally sent to it, and was not sitting -// in another channel's buffer when that channel got transferred to another -// process. On Windows, we duplicate handles on the sender side to a specific -// reciever. If the wrong receiver gets it, those handles are not valid. -// -// TODO(vlad): We could attempt to recover from the above situation by -// duplicating from the intended target process to ourselves (the receiver). -// That would only work if the intended process a) still exists; b) can be -// opened by the receiver with handle dup privileges. Another approach -// could be to use a separate dedicated process intended purely for handle -// passing, though that process would need to be global to any processes -// amongst which you want to share channels or connect one-shot servers to. -// There may be a system process that we could use for this purpose, but -// I haven't foundone -- and in the system process case, we'd need to ensure -// that we don't leak the handles (e.g. dup a handle to the system process, -// and then everything dies -- we don't want those resources to be leaked). +/// If we have any channel handles or shmem segments, then we'll send an +/// OutOfBandMessage after the data message. +/// +/// This includes the receiver's process ID, which the receiver checks to +/// make sure that the message was originally sent to it, and was not sitting +/// in another channel's buffer when that channel got transferred to another +/// process. On Windows, we duplicate handles on the sender side to a specific +/// reciever. If the wrong receiver gets it, those handles are not valid. +/// +/// TODO(vlad): We could attempt to recover from the above situation by +/// duplicating from the intended target process to ourselves (the receiver). +/// That would only work if the intended process a) still exists; b) can be +/// opened by the receiver with handle dup privileges. Another approach +/// could be to use a separate dedicated process intended purely for handle +/// passing, though that process would need to be global to any processes +/// amongst which you want to share channels or connect one-shot servers to. +/// There may be a system process that we could use for this purpose, but +/// I haven't found one -- and in the system process case, we'd need to ensure +/// that we don't leak the handles (e.g. dup a handle to the system process, +/// and then everything dies -- we don't want those resources to be leaked). #[derive(Debug)] struct OutOfBandMessage { target_process_id: u32, @@ -216,11 +217,11 @@ fn make_pipe_name(pipe_id: &Uuid) -> CString { CString::new(format!("\\\\.\\pipe\\rust-ipc-{}", pipe_id.to_string())).unwrap() } -// Duplicate a given handle from this process to the target one, passing the -// given flags to DuplicateHandle. -// -// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is -// also the pseudohandle for the current process). +/// Duplicate a given handle from this process to the target one, passing the +/// given flags to DuplicateHandle. +/// +/// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is +/// also the pseudohandle for the current process). unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) -> Result { @@ -239,12 +240,12 @@ unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE } } -// duplicate a handle in the current process +/// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) } -// duplicate a handle to the target process +/// Duplicate a handle to the target process. fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( @@ -253,7 +254,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul } } -// duplicate a handle to the target process, closing the source handle +/// Duplicate a handle to the target process, closing the source handle. fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( @@ -333,29 +334,34 @@ enum GetMessageResult { Message(Vec, Vec, Vec), } -// MessageReader implements blocking/nonblocking reads of messages -// from the handle +/// Main object keeping track of a receive handle and its associated state. +/// +/// Implements blocking/nonblocking reads of messages from the handle. #[derive(Debug)] struct MessageReader { - // The pipe read handle + /// The pipe read handle. handle: WinHandle, - // The OVERLAPPED struct for async IO on this receiver; we'll only - // ever have one in flight + /// The OVERLAPPED struct for async IO on this receiver. + /// + /// We'll only ever have one in flight. ov: Box, - // A read buffer for any pending reads + /// A read buffer for any pending reads. read_buf: Vec, - // If we have already issued an async read + /// Whether we have already issued an async read. read_in_progress: bool, - // If we received a BROKEN_PIPE or other error - // indicating that the remote end has closed the pipe + /// Whether we received a BROKEN_PIPE or other error + /// indicating that the remote end has closed the pipe. closed: bool, - // If this is part of a Set, then this is the ID that is used to identify - // this reader. If this is None, then this isn't part of a set. + /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. + /// + /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. + /// + /// `None` if this `MessageReader` is not part of any set. set_id: Option, } @@ -380,7 +386,7 @@ impl MessageReader { } } - // Called when we receive an IO Completion Packet for this handle. + /// Called when we receive an IO Completion Packet for this handle. fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -415,7 +421,7 @@ impl MessageReader { Ok(()) } - // kick off an asynchronous read + /// Kick off an asynchronous read. fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { return Ok(()); @@ -557,9 +563,12 @@ impl MessageReader { } } - // This is a specialized read when the buffser size is known ahead of time, - // and without our typical message framing. It's only valid to call this - // as the one and only call after creating a MessageReader. + /// Specialized read for out-of-band data ports. + /// + /// Here the buffer size is known in advance, + /// and the transfer doesn't have our typical message framing. + /// + /// It's only valid to call this as the one and only call after creating a MessageReader. fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); @@ -609,8 +618,16 @@ impl MessageReader { #[derive(Debug)] pub struct OsIpcReceiver { - // A MessageReader that implements most of the work of this - // MessageReader + /// The receive handle and its associated state. + /// + /// We can't just deal with raw handles like in the other platform back-ends, + /// since this implementation -- using plain pipes with no native packet handling -- + /// requires keeping track of various bits of receiver state, + /// which must not be separated from the handle itself. + /// + /// Note: Inner mutability is necessary, + /// since the `consume()` method needs to move out the reader + /// despite only getting a shared reference to `self`. reader: RefCell, } @@ -743,7 +760,9 @@ impl OsIpcReceiver { self.receive_message(false) } - // Do a pipe connect. Only used for one-shot servers + /// Do a pipe connect. + /// + /// Only used for one-shot servers. fn accept(&mut self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); @@ -791,9 +810,10 @@ impl OsIpcReceiver { } } - // Does a single explicitly-sized recv from the handle, consuming - // the receiver in the process. This is used for receiving data - // from the out-of-band big data buffer. + /// Does a single explicitly-sized recv from the handle, + /// consuming the receiver in the process. + /// + /// This is used for receiving data from the out-of-band big data buffer. fn recv_raw(self, size: usize) -> Result, WinError> { self.reader.borrow_mut().read_raw_sized(size) } @@ -801,7 +821,6 @@ impl OsIpcReceiver { #[derive(Debug, PartialEq)] pub struct OsIpcSender { - // The client hande itself handle: WinHandle, // Make sure this is `!Sync`, to match `mpsc::Sender`; and to discourage sharing references. // @@ -821,8 +840,12 @@ impl Clone for OsIpcSender { } } -// Write_msg, unlike write_buf, requires that bytes be sent -// in one operation. +/// Atomic write to a handle. +/// +/// Fails if the data can't be written in a single system call. +/// This is important, since otherwise concurrent sending +/// could result in parts of different messages getting intermixed, +/// and we would not be able to extract the individual messages. fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { if bytes.len() == 0 { return Ok(()); @@ -848,6 +871,10 @@ fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { Ok(()) } +/// Non-atomic write to a handle. +/// +/// Can be used for writes to an exclusive pipe, +/// where the send being split up into several calls poses no danger. fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { @@ -893,7 +920,7 @@ impl OsIpcSender { } } - // Connect to a pipe server + /// Connect to a pipe server. fn connect_named(pipe_name: &CString) -> Result { unsafe { let handle = @@ -952,8 +979,7 @@ impl OsIpcSender { data_len >= bytes_left_for_data } - // An internal-use-only send method that sends just raw data, with - // no header. + /// An internal-use-only send method that sends just raw data, with no header. fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); @@ -1063,13 +1089,13 @@ pub enum OsIpcSelectionResult { } pub struct OsIpcReceiverSet { - // Our incrementor, for unique handle IDs + /// Our incrementor, for unique handle IDs. incrementor: RangeFrom, - // the IOCP that we select on + /// The IOCP that we select on. iocp: WinHandle, - // The set of receivers, stored as MessageReaders + /// The set of receivers, stored as MessageReaders. readers: Vec, } From 0e48d3f0af2c80e637b5492a71d38b666655fd04 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 22:57:08 +0200 Subject: [PATCH 015/113] windows: cleanup: Use more standard indentation for macro --- src/platform/windows/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 19e6d786b..fc01d4081 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -34,11 +34,13 @@ lazy_static! { } /// Debug macro to better track what's going on in case of errors. -macro_rules! win32_trace { ($($rest:tt)*) => { - if cfg!(feature = "win32-trace") { - if *DEBUG_TRACE_ENABLED { println!($($rest)*); } +macro_rules! win32_trace { + ($($rest:tt)*) => { + if cfg!(feature = "win32-trace") { + if *DEBUG_TRACE_ENABLED { println!($($rest)*); } + } } -} } +} /// When we create the pipe, how big of a write buffer do we specify? /// From 19af7c95a66e4a51f046b2228d74e7f69f04db38 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 14:19:00 +0200 Subject: [PATCH 016/113] windows: Move out original handle in `to_receiver()` and `to_sender()` Like in the other back-ends, remove (`mem::replae()`) the original handle while constructing the wrappers, to make sure we do not keep around a copy that might interfere if reused accidentally. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index fc01d4081..1b5537b25 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1437,11 +1437,11 @@ impl OsOpaqueIpcChannel { } pub fn to_receiver(&mut self) -> OsIpcReceiver { - unsafe { OsIpcReceiver::from_handle(self.handle) } + unsafe { OsIpcReceiver::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } } pub fn to_sender(&mut self) -> OsIpcSender { - unsafe { OsIpcSender::from_handle(self.handle) } + unsafe { OsIpcSender::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } } } From db077793bb8ae47043912720923bd35c5ec2ef9d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 14:46:17 +0200 Subject: [PATCH 017/113] windows: Drop unnecessary `&mut self` in a few places Use `&self` rather than `&mut self` in some private methods that do not actually modify the main object. (Mostly because they rely on inner mutability.) This also removes the need for `mut` in a few places in the callers. --- src/platform/windows/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1b5537b25..a1352495c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -671,7 +671,7 @@ impl OsIpcReceiver { } } - fn prepare_for_transfer(&mut self) -> Result { + fn prepare_for_transfer(&self) -> Result { let mut reader = self.reader.borrow_mut(); // cancel any outstanding IO request reader.cancel_io(); @@ -765,7 +765,7 @@ impl OsIpcReceiver { /// Do a pipe connect. /// /// Only used for one-shot servers. - fn accept(&mut self) -> Result<(),WinError> { + fn accept(&self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); let handle = *reader_borrow.handle; @@ -1020,7 +1020,7 @@ impl OsIpcSender { let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, - OsIpcChannel::Receiver(mut r) => { + OsIpcChannel::Receiver(r) => { if try!(r.prepare_for_transfer()) == false { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } @@ -1412,7 +1412,7 @@ impl OsIpcOneShotServer { Vec, Vec, Vec),WinError> { - let mut receiver = self.receiver; + let receiver = self.receiver; try!(receiver.accept()); let (data, channels, shmems) = try!(receiver.recv()); Ok((receiver, data, channels, shmems)) From 80ee2d73e6d3d287278633bbedc203babb07bc98 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:31:10 +0200 Subject: [PATCH 018/113] windows: cleanup: Switch methods for a more logical order --- src/platform/windows/mod.rs | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a1352495c..cfaa25d55 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -388,41 +388,6 @@ impl MessageReader { } } - /// Called when we receive an IO Completion Packet for this handle. - fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { - win32_trace!("[$ {:?}] notify_completion", self.handle); - - // mark a read as no longer in progress even before we check errors - self.read_in_progress = false; - - if err == winapi::ERROR_BROKEN_PIPE { - assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); - self.closed = true; - return Ok(()); - } - - let nbytes = self.ov.InternalHigh as u32; - let offset = self.ov.Offset; - - assert!(offset == 0); - - // if the remote end closed... - if err != winapi::ERROR_SUCCESS { - // This should never happen - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } - - unsafe { - let new_size = self.read_buf.len() + nbytes as usize; - win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", - nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); - assert!(new_size <= self.read_buf.capacity()); - self.read_buf.set_len(new_size); - } - - Ok(()) - } - /// Kick off an asynchronous read. fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { @@ -484,6 +449,41 @@ impl MessageReader { } } + /// Called when we receive an IO Completion Packet for this handle. + fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + win32_trace!("[$ {:?}] notify_completion", self.handle); + + // mark a read as no longer in progress even before we check errors + self.read_in_progress = false; + + if err == winapi::ERROR_BROKEN_PIPE { + assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); + self.closed = true; + return Ok(()); + } + + let nbytes = self.ov.InternalHigh as u32; + let offset = self.ov.Offset; + + assert!(offset == 0); + + // if the remote end closed... + if err != winapi::ERROR_SUCCESS { + // This should never happen + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } + + unsafe { + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); + } + + Ok(()) + } + // This is split between get_message and get_message_inner, so that // this function can handle removing bytes from the buffer, since // get_message_inner borrows the buffer. From 505a3dff5e5a8f6e4ae72ae8a40c4f75089e564d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:37:21 +0200 Subject: [PATCH 019/113] windows: Mark `notify_completion()` as unsafe This method could yield uninitialised data when invoked incorrectly. --- src/platform/windows/mod.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cfaa25d55..42eaf48b2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -450,7 +450,12 @@ impl MessageReader { } /// Called when we receive an IO Completion Packet for this handle. - fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + /// + /// Unsafe, since calling this with an invalid object or at the wrong time + /// could result in uninitialized data being passed off as valid. + /// While this may seem less critical than other memory errors, + /// it can also break type safety. + unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); // mark a read as no longer in progress even before we check errors @@ -473,13 +478,11 @@ impl MessageReader { panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); } - unsafe { - let new_size = self.read_buf.len() + nbytes as usize; - win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", - nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); - assert!(new_size <= self.read_buf.capacity()); - self.read_buf.set_len(new_size); - } + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); Ok(()) } @@ -1218,7 +1221,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - try!(reader.notify_completion(io_err)); + unsafe { try!(reader.notify_completion(io_err)); } // then drain as many messages as we can loop { From 27376c33f4d841fd21c70b4b4ca6dc34d9be0c96 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 16:19:23 +0200 Subject: [PATCH 020/113] windows: Widen too narrow `unsafe` block Move some more calculations inside the unsafe block, since the other unsafe code relies upon them to be correct in order for soundness to be ensured. --- src/platform/windows/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 42eaf48b2..2635a4e39 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -396,17 +396,17 @@ impl MessageReader { win32_trace!("[$ {:?}] start_read", self.handle); - let buf_len = self.read_buf.len(); - let mut buf_cap = self.read_buf.capacity(); - let mut bytes_read: u32 = 0; + unsafe { + let buf_len = self.read_buf.len(); + let mut buf_cap = self.read_buf.capacity(); + let mut bytes_read: u32 = 0; - if buf_len == buf_cap { - self.read_buf.reserve(PIPE_BUFFER_SIZE); - buf_cap = self.read_buf.capacity(); - } + if buf_len == buf_cap { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + buf_cap = self.read_buf.capacity(); + } - // issue the read to the buffer, at the current length offset - unsafe { + // issue the read to the buffer, at the current length offset *self.ov.deref_mut() = mem::zeroed(); let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; let max_read_bytes = buf_cap - buf_len; From 4cc840176722acb52758bafe4f7184a394c59775 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 16:27:49 +0200 Subject: [PATCH 021/113] windows: Drop custom `GetMessageResult` type Just use a tuple in an `Option<>` instead. The named type didn't really add anything here -- it only made the code harder to follow. (The original introduction of this custom type resulted from a communication failure...) --- src/platform/windows/mod.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 2635a4e39..abb94846c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -331,11 +331,6 @@ impl WinHandle { } } -enum GetMessageResult { - NoMessage, - Message(Vec, Vec, Vec), -} - /// Main object keeping track of a receive handle and its associated state. /// /// Implements blocking/nonblocking reads of messages from the handle. @@ -490,7 +485,8 @@ impl MessageReader { // This is split between get_message and get_message_inner, so that // this function can handle removing bytes from the buffer, since // get_message_inner borrows the buffer. - fn get_message(&mut self) -> Result { + fn get_message(&mut self) -> Result, Vec, Vec)>, + WinError> { let drain_bytes; let result; if let Some(message) = Message::from_bytes(&self.read_buf) { @@ -525,10 +521,10 @@ impl MessageReader { win32_trace!("[$ {:?}] get_message success -> {} bytes, {} channels, {} shmems", self.handle, buf_data.len(), channels.len(), shmems.len()); drain_bytes = Some(message.size()); - result = GetMessageResult::Message(buf_data, channels, shmems); + result = Some((buf_data, channels, shmems)); } else { drain_bytes = None; - result = GetMessageResult::NoMessage; + result = None; } if let Some(size) = drain_bytes { @@ -703,9 +699,9 @@ impl OsIpcReceiver { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer match try!(reader.get_message()) { - GetMessageResult::Message(data, channels, shmems) => + Some((data, channels, shmems)) => return Ok((data, channels, shmems)), - GetMessageResult::NoMessage => + None => {}, } @@ -1226,11 +1222,11 @@ impl OsIpcReceiverSet { // then drain as many messages as we can loop { match try!(reader.get_message()) { - GetMessageResult::Message(data, channels, shmems) => { + Some((data, channels, shmems)) => { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); }, - GetMessageResult::NoMessage => { + None => { win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); break; }, From 712c37abfb3e3c7bd42b20b41d29b1b2facd4399 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 20:03:57 +0200 Subject: [PATCH 022/113] windows: More defensive receive buffer handling Extend the buffer's exposed length to cover its entire allocated capacity before using it in receive calls, so we can use safe slice operations rather than manual pointer arithmetic for determining addresses and lengths to be passed to the system calls. To keep the effects localised, we reset the length to the actually filled part again after the system calls, rather than keeping it at the full allocated size permanently. I'm not entirely sure yet whether to consider that more or less defensive than the other option -- but at least I'm confident that it's more robust than the original approach. --- src/platform/windows/mod.rs | 69 ++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index abb94846c..5e0ac42b3 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -391,25 +391,43 @@ impl MessageReader { win32_trace!("[$ {:?}] start_read", self.handle); + if self.read_buf.len() == self.read_buf.capacity() { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + } + unsafe { + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. let buf_len = self.read_buf.len(); - let mut buf_cap = self.read_buf.capacity(); - let mut bytes_read: u32 = 0; - - if buf_len == buf_cap { - self.read_buf.reserve(PIPE_BUFFER_SIZE); - buf_cap = self.read_buf.capacity(); - } + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset *self.ov.deref_mut() = mem::zeroed(); - let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; - let max_read_bytes = buf_cap - buf_len; - let ok = kernel32::ReadFile(*self.handle, - buf_ptr.offset(buf_len as isize), - max_read_bytes as u32, - &mut bytes_read, - self.ov.deref_mut()); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.read_buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.deref_mut()) + }; + + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.read_buf.set_len(buf_len); // ReadFile can return TRUE; if it does, an IO completion // packet is still posted to any port, and the OVERLAPPED @@ -584,17 +602,26 @@ impl MessageReader { let ov = self.ov.deref_mut(); *ov = mem::zeroed(); + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. let buf_len = buf.len(); - let dest_ptr = buf.as_mut_ptr().offset(buf_len as isize) as LPVOID; + let buf_cap = buf.capacity(); + buf.set_len(buf_cap); - let bytes_left = (size - buf_len) as u32; let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + ov) + }; + + // Restore the original size before error handling, + // so we never leave the function with the buffer exposing uninitialized data. + buf.set_len(buf_len); - let ok = kernel32::ReadFile(*self.handle, - dest_ptr, - bytes_left, - &mut bytes_read, - ov); if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { return Err(WinError::last("ReadFile")); } From 1c33a043cfd71ad7e991571ae1c1b538726f5cba Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 20:50:59 +0200 Subject: [PATCH 023/113] [RemoveMe] Temporarily disable most CI targets No need to generate spare heat while I'm working only on the windows back-end... --- .travis.yml | 7 ------- appveyor.yml | 10 ---------- 2 files changed, 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index e18320868..39b51c48e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,12 @@ rust: os: - linux - - osx env: global: - RUST_BACKTRACE=1 matrix: - FEATURES="unstable" - - FEATURES="unstable force-inprocess" - -matrix: - include: - - os: linux - env: FEATURES="unstable memfd" script: - cargo build --features "$FEATURES" diff --git a/appveyor.yml b/appveyor.yml index 2af4d1c64..445910295 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,16 +4,6 @@ environment: matrix: - TARGET: x86_64-pc-windows-msvc FEATURES: "unstable" - - TARGET: i686-pc-windows-msvc - FEATURES: "unstable" - - TARGET: i686-pc-windows-gnu - FEATURES: "unstable" - - TARGET: x86_64-pc-windows-msvc - FEATURES: "unstable force-inprocess" - - TARGET: i686-pc-windows-msvc - FEATURES: "unstable force-inprocess" - - TARGET: i686-pc-windows-gnu - FEATURES: "unstable force-inprocess" install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" From 6642fbecea064f74e78c8f5cc7b4dbe8454ef73f Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 27 Sep 2017 23:40:40 +0200 Subject: [PATCH 024/113] windows: Add explanation why the `ov` structure needs to be boxed --- src/platform/windows/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5e0ac42b3..e9ba1a442 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -342,6 +342,10 @@ struct MessageReader { /// The OVERLAPPED struct for async IO on this receiver. /// /// We'll only ever have one in flight. + /// + /// This must be on the heap, so its memory location -- + /// which is registered in the kernel during an async read -- + /// remains stable even when the enclosing structure is passed around. ov: Box, /// A read buffer for any pending reads. From 82fc0776915a44378e15636452b8a25b5a483393 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 27 Sep 2017 23:41:26 +0200 Subject: [PATCH 025/113] windows: Implement `Drop` for `MessageReader` Make sure any outstanding async I/O operation is cancelled before freeing the memory of the `ov` structure and receive buffer. This is an important safety fix: leaving the operation pending after the memory is freed might result in the kernel later writing to memory locations that are no longer valid. --- src/platform/windows/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e9ba1a442..b4fda9d89 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -366,6 +366,14 @@ struct MessageReader { set_id: Option, } +impl Drop for MessageReader { + fn drop(&mut self) { + // Before dropping the `ov` structure and read buffer, + // make sure the kernel won't do any more async updates to them! + self.cancel_io(); + } +} + impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { From 8a546b8871da998cf2d98ca12af1634441dca3ff Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 28 Sep 2017 23:16:01 +0200 Subject: [PATCH 026/113] windows: Consume reader immediately when moving out handle When moving the associated handle to another process, immediately consume the `MessageReader` containing it, so we do not leave the reader hanging around with an invalid handle. While this doesn't really change much in practice -- the reader was dropped along with the receiver shortly after anyway -- it's cleaner semantically, and should be a tick more robust. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b4fda9d89..f4514212a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1063,7 +1063,7 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.borrow_mut().handle, &server_h)); + let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.into_inner().handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, } @@ -1082,7 +1082,7 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.borrow_mut().handle, &server_h)); + let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.into_inner().handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; From 5f70a3070e9cf1b0dc49fcf9032b2af51a22ac97 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:18:16 +0200 Subject: [PATCH 027/113] windows: Take ownership of handle in `move_handle_to_process()` More explicitly reflect the fact that this function consumes (moves) the original handle. --- src/platform/windows/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4514212a..b9744c2a2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -257,7 +257,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul } /// Duplicate a handle to the target process, closing the source handle. -fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { +fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( handle.take(), **other_process, @@ -1054,8 +1054,8 @@ impl OsIpcSender { for port in ports { match port { - OsIpcChannel::Sender(mut s) => { - let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); + OsIpcChannel::Sender(s) => { + let mut raw_remote_handle = try!(move_handle_to_process(s.handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, OsIpcChannel::Receiver(r) => { @@ -1063,7 +1063,8 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.into_inner().handle, &server_h)); + let handle = WinHandle::new(r.reader.into_inner().handle.take()); + let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, } @@ -1082,7 +1083,8 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.into_inner().handle, &server_h)); + let handle = WinHandle::new(receiver.reader.into_inner().handle.take()); + let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; From 5649048f98def93b590c844577296b867f5ddfcf Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 14:55:51 +0200 Subject: [PATCH 028/113] windows: Assert valid state before consuming receiver --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b9744c2a2..d6bd22fed 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,7 +718,9 @@ impl OsIpcReceiver { } pub fn consume(&self) -> OsIpcReceiver { - let mut handle = dup_handle(&self.reader.borrow().handle).unwrap(); + let reader = self.reader.borrow(); + assert!(!reader.read_in_progress); + let mut handle = dup_handle(&reader.handle).unwrap(); unsafe { OsIpcReceiver::from_handle(handle.take()) } } From 80a2898683226c0135992fe7933f5a065fd0bb6d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 15:00:43 +0200 Subject: [PATCH 029/113] windows: Don't duplicate handle when consuming receiver The temporary duplicate was only necessary as a workaround, to prevent the new `OsIpcReceiver` copy from ending up with an invalid handle, when the original receiver gets dropped. (Specifically, the original receiver gets dropped after serialisation, before the actual transfer happens using the copy...) However, now we just can use inner mutability to actually unset the handle value in the original `OsIpcReceiver` struct (since the handle now lives inside a `RefCell` as part of `MessageReader`) -- so there is no longer a danger of the handle being dropped prematurely along with the original receiver. --- src/platform/windows/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d6bd22fed..9dec4f79e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,10 +718,9 @@ impl OsIpcReceiver { } pub fn consume(&self) -> OsIpcReceiver { - let reader = self.reader.borrow(); + let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - let mut handle = dup_handle(&reader.handle).unwrap(); - unsafe { OsIpcReceiver::from_handle(handle.take()) } + unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } fn receive_message(&self, mut block: bool) From 5934d72db483752722e72c640f81e5e7f708ca02 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 21:00:41 +0200 Subject: [PATCH 030/113] windows: Remove bogus explicit `impl Send` for `OsIpcSender` `OsIpcSender` is automatically `Sync`, since all its constituents are. We aren't doing anything on top of that to ensure it can indeed be shared -- so declaring `Sync` explicitly is wrong, and could obscure problems if the inherent `Sync` properties ever change for some reason. --- src/platform/windows/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9dec4f79e..90623e676 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -870,8 +870,6 @@ pub struct OsIpcSender { nosync_marker: PhantomData>, } -unsafe impl Send for OsIpcSender { } - impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { unsafe { From b14fa51515d473bb219c123efab8764e0f9b986b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 14:10:51 +0200 Subject: [PATCH 031/113] windows: Add a comment explaining the `impl Send` on `OsIpcReceiver` --- src/platform/windows/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 90623e676..06e6d2384 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -671,6 +671,19 @@ pub struct OsIpcReceiver { reader: RefCell, } +// We need to explicitly declare this, because of the raw pointer +// contained in the `OVERLAPPED` structure inside `MessageReader`. +// +// Note: the `Send` claim is only really fulfilled +// as long as nothing can ever alias the aforementioned raw pointer. +// While this seems to be true as far as I can tell, +// it's a rather fragile condition, which should be managed much more tightly +// (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. +// The current implementation doesn't follow such a strict encapsulation however, +// with `OsIpcReceiver` directly accessing `reader.ov` (in `receive_message()`), +// outside the `MessageReader` implementation -- +// so for now, `OsIpcReceiver` needs to be considered responsible as a whole +// for upholding the non-aliasing condition. unsafe impl Send for OsIpcReceiver { } impl PartialEq for OsIpcReceiver { From c0f3fd830d1567bfc5d82a1083dc1284f5e8bf1e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 18:44:51 +0200 Subject: [PATCH 032/113] windows: cleanup: Use enum rather than anonymous bool for blocking mode parameter This is more in line with the other back-ends, as well as general Rust conventions. --- src/platform/windows/mod.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 06e6d2384..b08a3e8ca 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -656,6 +656,12 @@ impl MessageReader { } } +#[derive(Clone, Copy, Debug, PartialEq)] +enum BlockingMode { + Blocking, + Nonblocking, +} + #[derive(Debug)] pub struct OsIpcReceiver { /// The receive handle and its associated state. @@ -736,7 +742,7 @@ impl OsIpcReceiver { unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } - fn receive_message(&self, mut block: bool) + fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then // the implementation in select() is used. It does much the same thing, but across multiple @@ -778,11 +784,17 @@ impl OsIpcReceiver { // Then, get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; let mut err = winapi::ERROR_SUCCESS; - let ok = kernel32::GetOverlappedResult(*reader.handle, reader.ov.deref_mut(), &mut nbytes, - if block { winapi::TRUE } else { winapi::FALSE }); + let block = match blocking_mode { + BlockingMode::Blocking => winapi::TRUE, + BlockingMode::Nonblocking => winapi::FALSE, + }; + let ok = kernel32::GetOverlappedResult(*reader.handle, + reader.ov.deref_mut(), + &mut nbytes, + block); if ok == winapi::FALSE { err = GetLastError(); - if !block && err == winapi::ERROR_IO_INCOMPLETE { + if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Nonblocking read, no message, read's in flight, we're // done. An error is expected in this case. return Err(WinError::NoData); @@ -797,7 +809,7 @@ impl OsIpcReceiver { // If we're not blocking, pretend that we are blocking, since we got part of // a message already. Keep reading until we get a complete message. - block = true; + blocking_mode = BlockingMode::Blocking; } } } @@ -805,13 +817,13 @@ impl OsIpcReceiver { pub fn recv(&self) -> Result<(Vec, Vec, Vec),WinError> { win32_trace!("recv"); - self.receive_message(true) + self.receive_message(BlockingMode::Blocking) } pub fn try_recv(&self) -> Result<(Vec, Vec, Vec),WinError> { win32_trace!("try_recv"); - self.receive_message(false) + self.receive_message(BlockingMode::Nonblocking) } /// Do a pipe connect. From e27cfaf788f97b1da11e94504ccb963a6fcfae39 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 1 Oct 2017 18:18:17 +0200 Subject: [PATCH 033/113] windows: Improve placement of comments in `receive_message()` --- src/platform/windows/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b08a3e8ca..db7b4cf0c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -742,18 +742,17 @@ impl OsIpcReceiver { unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } + // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then + // the implementation in select() is used. It does much the same thing, but across multiple + // channels. fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { - // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then - // the implementation in select() is used. It does much the same thing, but across multiple - // channels. - - // This function loops, because in the case of a blocking read, we may need to - // read multiple sets of bytes from the pipe to receive a complete message. unsafe { let mut reader = self.reader.borrow_mut(); assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. loop { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer From 65699b15361f42c3fa31e824d8f976aa84723be0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 21:24:40 +0200 Subject: [PATCH 034/113] windows: Add checks and warnings regarding dangers of `ov` and `read_buf` The whole thing is highly unsafe. To handle it *properly*, we would need to encapsulate these values such that nothing else can access them while there is an outstanding async read in progress with the kernel... However, that's quite tricky to achieve -- so for now, the best we can do is putting big fat warnings everywhere, and adding extra safety checks where possible. --- src/platform/windows/mod.rs | 207 ++++++++++++++++++++++++------------ 1 file changed, 137 insertions(+), 70 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index db7b4cf0c..5d729587d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -343,15 +343,39 @@ struct MessageReader { /// /// We'll only ever have one in flight. /// - /// This must be on the heap, so its memory location -- + /// This must be on the heap, in order for its memory location -- /// which is registered in the kernel during an async read -- - /// remains stable even when the enclosing structure is passed around. + /// to remain stable even when the enclosing structure is passed around. + /// + /// WARNING: As the kernel holds a mutable alias of this structure + /// while an async read is in progress, + /// it is crucial that this value is never accessed in user space + /// from the moment we issue an async read in `start_read()`, + /// until the moment we process the event + /// signalling completion of the async read in `notify_completion()`. + /// + /// Since Rust's type system is not aware of the kernel alias, + /// the compiler cannot guarantee exclusive access the way it normally would, + /// i.e. any access to this value is inherently unsafe! + /// The only way to avoid undefined behaviour + /// is to always make sure the `read_in_progress` indicator is not set, + /// before performing any access to this value. + /// + /// (Unfortunately, there is no way to express this condition in the type system, + /// without some fundamental change to how we handle these fields...) ov: Box, /// A read buffer for any pending reads. + /// + /// WARNING: This has the same safety problem as `ov` above. read_buf: Vec, - /// Whether we have already issued an async read. + /// Indicates whether the kernel currently has an async read in flight for this port. + /// + /// WARNING: Rather than just managing our internal state, + /// this flag plays a critical role in keeping track of kernel aliasing + /// of the `ov` and `read_buf` fields, as explained in the comment for `ov'. + /// Thus it is crucial that we always set this correctly! read_in_progress: bool, /// Whether we received a BROKEN_PIPE or other error @@ -396,7 +420,22 @@ impl MessageReader { } /// Kick off an asynchronous read. - fn start_read(&mut self) -> Result<(),WinError> { + /// + /// Note: This is *highly* unsafe, since upon successful return, + /// the `ov` and `read_buf` fields will be left mutably aliased by the kernel + /// (until we receive an event signalling completion of the async read) -- + /// and Rust's type system doesn't know about these aliases! + /// + /// This means that after invoking this method, + /// up to the point where we receive the completion notification, + /// nothing is allowed to access the `ov` and `read_buf` fields; + /// but the compiler cannot guarantee this for us. + /// It is our responsibility to make sure of it -- + /// i.e. all code on the path from invoking this method, + /// up to receiving the completion event, is unsafe. + /// + /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + unsafe fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { return Ok(()); } @@ -407,83 +446,92 @@ impl MessageReader { self.read_buf.reserve(PIPE_BUFFER_SIZE); } - unsafe { - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = self.read_buf.len(); - let buf_cap = self.read_buf.capacity(); - self.read_buf.set_len(buf_cap); - - // issue the read to the buffer, at the current length offset - *self.ov.deref_mut() = mem::zeroed(); - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(*self.handle, - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - self.ov.deref_mut()) - }; - - // Reset the vector to only expose the already filled part. - // - // This means that the async read - // will actually fill memory beyond the exposed part of the vector. - // While this use of a vector is officially sanctioned for such cases, - // it still feel rather icky to me... - // - // On the other hand, this way we make sure - // the buffer never appears to have more valid data - // than what is actually present, - // which could pose a potential danger in its own right. - // Also, it avoids the need to keep a separate state variable -- - // which would bear some risk of getting out of sync. - self.read_buf.set_len(buf_len); - - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - if ok == winapi::FALSE { - let err = GetLastError(); - if err == winapi::ERROR_BROKEN_PIPE { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - } + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. + let buf_len = self.read_buf.len(); + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); + + // issue the read to the buffer, at the current length offset + *self.ov.deref_mut() = mem::zeroed(); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.read_buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.deref_mut()) + }; - if err == winapi::ERROR_IO_PENDING { - self.read_in_progress = true; - return Ok(()); - } + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.read_buf.set_len(buf_len); + + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + if ok == winapi::FALSE { + let err = GetLastError(); + if err == winapi::ERROR_BROKEN_PIPE { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + } - Err(WinError::from_system(err, "ReadFile")) - } else { + if err == winapi::ERROR_IO_PENDING { self.read_in_progress = true; - Ok(()) + return Ok(()); } + + Err(WinError::from_system(err, "ReadFile")) + } else { + self.read_in_progress = true; + Ok(()) } } /// Called when we receive an IO Completion Packet for this handle. /// - /// Unsafe, since calling this with an invalid object or at the wrong time - /// could result in uninitialized data being passed off as valid. - /// While this may seem less critical than other memory errors, - /// it can also break type safety. + /// Unsafe, since calling this in error + /// while an async read is actually still in progress in the kernel + /// would have catastrophic effects, + /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! + /// + /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// + /// Also, this method relies on `ov` and `read_buf` actually having valid data, + /// i.e. nothing should modify these fields + /// between receiving the completion notification from the kernel + /// and invoking this method. unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + assert!(self.read_in_progress); + win32_trace!("[$ {:?}] notify_completion", self.handle); - // mark a read as no longer in progress even before we check errors + // Regardless whether the kernel reported success or error, + // it doesn't have an async read operation in flight at this point anymore. + // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; if err == winapi::ERROR_BROKEN_PIPE { @@ -517,6 +565,11 @@ impl MessageReader { // get_message_inner borrows the buffer. fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { + // Never touch the buffer while it's still mutably aliased by the kernel! + if self.read_in_progress { + return Ok(None); + } + let drain_bytes; let result; if let Some(message) = Message::from_bytes(&self.read_buf) { @@ -588,6 +641,10 @@ impl MessageReader { // Make sure that the reader has a read in flight, // otherwise a later select() will hang. + // + // Note: Just like in `OsIpcReceiver.receive_message()` below, + // this makes us vulnerable to invalid `ov` and `read_buf` modification + // from code not marked as unsafe... try!(self.start_read()); Ok(()) @@ -796,6 +853,16 @@ impl OsIpcReceiver { if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Nonblocking read, no message, read's in flight, we're // done. An error is expected in this case. + // + // Note: This leaks unsafety outside the `unsafe` block, + // since the method returns while an async read is still in progress; + // meaning the kernel still holds a mutable alias + // of the read buffer and `OVERLAPPED` structure + // that the Rust type system doesn't know about -- + // nothing prevents code that isn't marked as `unsafe` + // from performing invalid reads or writes to these fields! + // + // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) return Err(WinError::NoData); } // We pass err through to notify_completion so @@ -1304,7 +1371,7 @@ impl OsIpcReceiverSet { selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } else { - try!(reader.start_read()); + unsafe { try!(reader.start_read()); } } } From dfcae2187c49f8cac4acc8c822fa505016cb9205 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 21:32:55 +0200 Subject: [PATCH 035/113] windows: Shrink unnecessarily wide `unsafe` block Now that `get_message()` has a check to make sure it won't touch the buffer if an async read is in progress, the safety of the invocation is no longer affected by the correctness of the other code in the unsafe block, and vice versa. --- src/platform/windows/mod.rs | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5d729587d..91bf0bd41 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -804,28 +804,28 @@ impl OsIpcReceiver { // channels. fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { - unsafe { - let mut reader = self.reader.borrow_mut(); - assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); - - // This function loops, because in the case of a blocking read, we may need to - // read multiple sets of bytes from the pipe to receive a complete message. - loop { - // First, try to fetch a message, in case we have one pending - // in the reader's receive buffer - match try!(reader.get_message()) { - Some((data, channels, shmems)) => - return Ok((data, channels, shmems)), - None => - {}, - } + let mut reader = self.reader.borrow_mut(); + assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); - // If the pipe was already closed, we're done -- we've - // already drained all incoming bytes - if reader.closed { - return Err(WinError::ChannelClosed); - } + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. + loop { + // First, try to fetch a message, in case we have one pending + // in the reader's receive buffer + match try!(reader.get_message()) { + Some((data, channels, shmems)) => + return Ok((data, channels, shmems)), + None => + {}, + } + + // If the pipe was already closed, we're done -- we've + // already drained all incoming bytes + if reader.closed { + return Err(WinError::ChannelClosed); + } + unsafe { // Then, issue a read if we don't have one already in flight. // We must not issue a read if we have complete unconsumed // messages, because getting a message modifies the read_buf. @@ -872,11 +872,11 @@ impl OsIpcReceiver { // Notify that the read completed, which will update the // read pointers try!(reader.notify_completion(err)); - - // If we're not blocking, pretend that we are blocking, since we got part of - // a message already. Keep reading until we get a complete message. - blocking_mode = BlockingMode::Blocking; } + + // If we're not blocking, pretend that we are blocking, since we got part of + // a message already. Keep reading until we get a complete message. + blocking_mode = BlockingMode::Blocking; } } From 7a600b7dad7618cb680b6a71e42ad705f5406fa6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:12:37 +0200 Subject: [PATCH 036/113] windows: Minor comment fix --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 91bf0bd41..138e37a24 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -914,7 +914,7 @@ impl OsIpcReceiver { }, // This is a weird one -- if we create a named pipe (like we do - // in new(), the client connects, sends data, then drops its handle, + // in new() ), the client connects, sends data, then drops its handle, // a Connect here will get ERROR_NO_DATA -- but there may be data in // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. From 56c0f30ac3de9feb4ee96d97a3ab629eaac1a157 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:18:04 +0200 Subject: [PATCH 037/113] windows: cleanup: More straightforward use of default match case --- src/platform/windows/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 138e37a24..91138477a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -923,14 +923,8 @@ impl OsIpcReceiver { Ok(()) }, - // was it an actual error? - err if err != winapi::ERROR_IO_PENDING => { - win32_trace!("[$ {:?}] accept error -> {}", handle, err); - Err(WinError::last("ConnectNamedPipe")) - }, - // the connect is pending; wait for it to complete - _ /* winapi::ERROR_IO_PENDING */ => { + winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { @@ -938,6 +932,12 @@ impl OsIpcReceiver { } Ok(()) }, + + // Anything else signifies some actual I/O error. + err => { + win32_trace!("[$ {:?}] accept error -> {}", handle, err); + Err(WinError::last("ConnectNamedPipe")) + }, } } } From 2265c29fe7042f72b6242fa715faa144a5164c42 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:33:47 +0200 Subject: [PATCH 038/113] windows: Take `WinHandle` rather than `HANDLE` in `write_msg()` and `write_buf()` I see no reason to use the raw handle here -- so let's stick with the safer abstraction... --- src/platform/windows/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 91138477a..924a2b02f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -976,14 +976,14 @@ impl Clone for OsIpcSender { /// This is important, since otherwise concurrent sending /// could result in parts of different messages getting intermixed, /// and we would not be able to extract the individual messages. -fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { +fn write_msg(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { if bytes.len() == 0 { return Ok(()); } let mut size: u32 = 0; unsafe { - if kernel32::WriteFile(handle, + if kernel32::WriteFile(**handle, bytes.as_ptr() as LPVOID, bytes.len() as u32, &mut size, @@ -1005,7 +1005,7 @@ fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { /// /// Can be used for writes to an exclusive pipe, /// where the send being split up into several calls poses no danger. -fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { +fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { return Ok(()); @@ -1016,7 +1016,7 @@ fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { let mut sz: u32 = 0; unsafe { let bytes_to_write = &bytes[written..]; - if kernel32::WriteFile(handle, + if kernel32::WriteFile(**handle, bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, &mut sz, @@ -1027,7 +1027,7 @@ fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle, sz, written, bytes.len(), GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, bytes.len(), GetLastError()); } Ok(()) @@ -1114,7 +1114,7 @@ impl OsIpcSender { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); - write_buf(*self.handle, data) + write_buf(&self.handle, data) } pub fn send(&self, @@ -1203,10 +1203,10 @@ impl OsIpcSender { if big_data_sender.is_none() { &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - try!(write_msg(*self.handle, &full_message)); + try!(write_msg(&self.handle, &full_message)); } else { &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_msg(*self.handle, &full_message)); + try!(write_msg(&self.handle, &full_message)); try!(big_data_sender.unwrap().send_raw(data)); } } From 216d1ce43134727096b541e28a7fa09f133a5518 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 16:49:54 +0200 Subject: [PATCH 039/113] windows: cleanup: Use helper variable consistently --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 924a2b02f..488585fb7 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1027,7 +1027,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, bytes.len(), GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); } Ok(()) From 91e95bf7423f5927cb9a1e9accdf44a0796fb071 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:08:41 +0200 Subject: [PATCH 040/113] windows: cleanup: Merge `write_msg()` functionality into `write_buf()` Use a single function with a mode flag, rather than maintaining two separate, nearly identical functions. --- src/platform/windows/mod.rs | 63 +++++++++++++++---------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 488585fb7..971e9a6c8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -970,42 +970,16 @@ impl Clone for OsIpcSender { } } -/// Atomic write to a handle. -/// -/// Fails if the data can't be written in a single system call. -/// This is important, since otherwise concurrent sending -/// could result in parts of different messages getting intermixed, -/// and we would not be able to extract the individual messages. -fn write_msg(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { - if bytes.len() == 0 { - return Ok(()); - } - - let mut size: u32 = 0; - unsafe { - if kernel32::WriteFile(**handle, - bytes.as_ptr() as LPVOID, - bytes.len() as u32, - &mut size, - ptr::null_mut()) - == winapi::FALSE - { - return Err(WinError::last("WriteFile")); - } - } - - if size != bytes.len() as u32 { - panic!("Windows IPC write_msg expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", size, bytes.len()); - } - - Ok(()) +#[derive(Clone, Copy, Debug)] +enum AtomicMode { + Atomic, + Nonatomic, } -/// Non-atomic write to a handle. +/// Write data to a handle. /// -/// Can be used for writes to an exclusive pipe, -/// where the send being split up into several calls poses no danger. -fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { +/// In `Atomic` mode, this panics if the data can't be written in a single system call. +fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { return Ok(()); @@ -1027,7 +1001,16 @@ fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + match atomic { + AtomicMode::Atomic => { + if written != total { + panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); + } + }, + AtomicMode::Nonatomic => { + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + }, + } } Ok(()) @@ -1114,7 +1097,10 @@ impl OsIpcSender { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); - write_buf(&self.handle, data) + // Write doesn't need to be atomic, + // since the pipe is exclusive for this message, + // so we don't have to fear intermixing with parts of other messages. + write_buf(&self.handle, data, AtomicMode::Nonatomic) } pub fn send(&self, @@ -1203,10 +1189,13 @@ impl OsIpcSender { if big_data_sender.is_none() { &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - try!(write_msg(&self.handle, &full_message)); + // Write needs to be atomic, since otherwise concurrent sending + // could result in parts of different messages getting intermixed, + // and the receiver would not be able to extract the individual messages. + try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); } else { &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_msg(&self.handle, &full_message)); + try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); try!(big_data_sender.unwrap().send_raw(data)); } } From 3c921a748a0a5761d25a8316f43859541c5b0384 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:16:28 +0200 Subject: [PATCH 041/113] windows: Shrink an unnecessarily wide `unsafe` block Sub-slicing is a safe operation (always yielding a valid slice) -- so it shouldn't affect the soundness of the actual unsafe code. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 971e9a6c8..150f9587e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -988,8 +988,8 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), let mut written = 0; while written < total { let mut sz: u32 = 0; + let bytes_to_write = &bytes[written..]; unsafe { - let bytes_to_write = &bytes[written..]; if kernel32::WriteFile(**handle, bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, From ec03bd1653483629ecc47f8989237ebd7cee3026 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:28:15 +0200 Subject: [PATCH 042/113] windows: Fix overly eager size check While it's an unlikely case, I don't see any problem with the buffer being used up to the last byte... --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 150f9587e..f4f9cc685 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1086,7 +1086,7 @@ impl OsIpcSender { let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; // make sure we don't have too much oob data to begin with - assert!((oob_size as usize) < (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); data_len >= bytes_left_for_data From b50dc8bc9950d8f90b625976612b067f4f6305ff Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:29:47 +0200 Subject: [PATCH 043/113] windows: Fix another overly eager size check Again, while it's an unlikely case, I see no problem with fully using the available value range. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4f9cc685..e245431c0 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1112,7 +1112,7 @@ impl OsIpcSender { // We limit the max size we can send here; we can fix this // just by upping the header to be 2x u64 if we really want // to. - assert!(data.len() < u32::max_value() as usize); + assert!(data.len() <= u32::max_value() as usize); let (server_h, server_pid) = if !shared_memory_regions.is_empty() || !ports.is_empty() { try!(self.get_pipe_server_process_handle_and_pid()) From 42e8304ff116f56e00b064318bdfe6dd66b442dc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 18:45:25 +0200 Subject: [PATCH 044/113] windows: Revamp send buffer construction to minimise `unsafe` The only really unsafe part here is turning the `MessageHeader` struct into a series of bytes. All the actual buffer manipulations can be done just as efficiently using safe operations. This includes a major revamp of the way we deal with the header construction: making it more straightforward, and reusing existing functionality. --- src/platform/windows/mod.rs | 50 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e245431c0..6a2ffef5b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1175,29 +1175,33 @@ impl OsIpcSender { oob_data = bincode::serialize(&oob).unwrap(); } - unsafe { - let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; - let full_in_band_len = MessageHeader::size() + in_band_data_len + oob_data.len(); - assert!(full_in_band_len <= PIPE_BUFFER_SIZE); - - let mut full_message = Vec::::with_capacity(full_in_band_len); - full_message.set_len(full_in_band_len); - - let header = full_message.as_mut_ptr() as *mut MessageHeader; - *header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); - - if big_data_sender.is_none() { - &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); - &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - // Write needs to be atomic, since otherwise concurrent sending - // could result in parts of different messages getting intermixed, - // and the receiver would not be able to extract the individual messages. - try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); - } else { - &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); - try!(big_data_sender.unwrap().send_raw(data)); - } + let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; + let header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + let full_in_band_len = header.total_message_bytes_needed(); + assert!(full_in_band_len <= PIPE_BUFFER_SIZE); + let mut full_message = Vec::::with_capacity(full_in_band_len); + + { + let header_bytes = unsafe { slice::from_raw_parts(&header as *const _ as *const u8, + mem::size_of_val(&header)) }; + full_message.extend_from_slice(header_bytes); + } + + if big_data_sender.is_none() { + full_message.extend_from_slice(&*data); + full_message.extend_from_slice(&*oob_data); + assert!(full_message.len() == full_in_band_len); + + // Write needs to be atomic, since otherwise concurrent sending + // could result in parts of different messages getting intermixed, + // and the receiver would not be able to extract the individual messages. + try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); + } else { + full_message.extend_from_slice(&*oob_data); + assert!(full_message.len() == full_in_band_len); + + try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); + try!(big_data_sender.unwrap().send_raw(data)); } Ok(()) From d70b8c9c59abda2d169342bbd8fcd395531cd57d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:09:38 +0200 Subject: [PATCH 045/113] windows: Remove trivial `MessageHeader::size()` helper Abstracting this trivial functionality only makes it more obscure. --- src/platform/windows/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 6a2ffef5b..dd65ac08c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -74,12 +74,8 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { struct MessageHeader(u32, u32); impl MessageHeader { - fn size() -> usize { - mem::size_of::() - } - fn total_message_bytes_needed(&self) -> usize { - MessageHeader::size() + self.0 as usize + self.1 as usize + mem::size_of::() + self.0 as usize + self.1 as usize } } @@ -91,7 +87,7 @@ struct Message<'data> { impl<'data> Message<'data> { fn from_bytes(bytes: &'data [u8]) -> Option { - if bytes.len() < MessageHeader::size() { + if bytes.len() < mem::size_of::() { return None; } @@ -110,11 +106,11 @@ impl<'data> Message<'data> { } fn data(&self) -> &[u8] { - &self.bytes[MessageHeader::size()..(MessageHeader::size() + self.data_len)] + &self.bytes[mem::size_of::()..(mem::size_of::() + self.data_len)] } fn oob_bytes(&self) -> &[u8] { - &self.bytes[(MessageHeader::size() + self.data_len)..] + &self.bytes[(mem::size_of::() + self.data_len)..] } fn oob_data(&self) -> Option { @@ -136,7 +132,7 @@ impl<'data> Message<'data> { } fn size(&self) -> usize { - MessageHeader::size() + self.data_len + self.oob_len + mem::size_of::() + self.data_len + self.oob_len } } @@ -1086,9 +1082,9 @@ impl OsIpcSender { let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; // make sure we don't have too much oob data to begin with - assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE - mem::size_of::()), "too much oob data"); - let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); + let bytes_left_for_data = (PIPE_BUFFER_SIZE - mem::size_of::()) - (oob_size as usize); data_len >= bytes_left_for_data } From f742ceb687e8ebfd4ab22665eac6827aaa8c7e0a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:20:10 +0200 Subject: [PATCH 046/113] windows: Turn `MessageHeader` into normal struct Tuple structs are obscure, and are used pretty much only for the "newtype" pattern -- a struct with named fields is generally more readable. --- src/platform/windows/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index dd65ac08c..d59fdbd8a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -70,12 +70,14 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { Ok((sender, receiver)) } -/// Holds data len and out-of-band data len. -struct MessageHeader(u32, u32); +struct MessageHeader { + data_len: u32, + oob_len: u32, +} impl MessageHeader { fn total_message_bytes_needed(&self) -> usize { - mem::size_of::() + self.0 as usize + self.1 as usize + mem::size_of::() + self.data_len as usize + self.oob_len as usize } } @@ -98,8 +100,8 @@ impl<'data> Message<'data> { } Some(Message { - data_len: header.0 as usize, - oob_len: header.1 as usize, + data_len: header.data_len as usize, + oob_len: header.oob_len as usize, bytes: &bytes[0..header.total_message_bytes_needed()], }) } @@ -1172,7 +1174,10 @@ impl OsIpcSender { } let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; - let header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + let header = MessageHeader { + data_len: in_band_data_len as u32, + oob_len: oob_data.len() as u32 + }; let full_in_band_len = header.total_message_bytes_needed(); assert!(full_in_band_len <= PIPE_BUFFER_SIZE); let mut full_message = Vec::::with_capacity(full_in_band_len); From 9e28ebe11eba27805f5d7adfc58ebe8d02be2d55 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:40:37 +0200 Subject: [PATCH 047/113] windows: Check for errors on SHM unmapping --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d59fdbd8a..1e74a4c02 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -22,6 +22,7 @@ use std::mem; use std::ops::{Deref, DerefMut, RangeFrom}; use std::ptr; use std::slice; +use std::thread; use uuid::Uuid; use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; use winapi; @@ -1411,7 +1412,8 @@ unsafe impl Sync for OsIpcSharedMemory {} impl Drop for OsIpcSharedMemory { fn drop(&mut self) { unsafe { - kernel32::UnmapViewOfFile(self.ptr as LPVOID); + let result = kernel32::UnmapViewOfFile(self.ptr as LPVOID); + assert!(thread::panicking() || result != 0); } } } From 4656176d17f00ab813c18cec99a2186ca2c41fd8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:40:37 +0200 Subject: [PATCH 048/113] windows: Check for errors on file handle closing --- src/platform/windows/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1e74a4c02..8f83c8764 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -276,7 +276,10 @@ unsafe impl Sync for WinHandle { } impl Drop for WinHandle { fn drop(&mut self) { unsafe { - kernel32::CloseHandle(self.h); + if self.is_valid() { + let result = kernel32::CloseHandle(self.h); + assert!(thread::panicking() || result != 0); + } } } } From 776dd3171288afebd50ef583bd5e605ea1c3059d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 20:48:41 +0200 Subject: [PATCH 049/113] windows: Add check to make sure we don't leak `OsOpaqueIpcChannel` Adaptation of same mechanism in unix back-end. --- src/platform/windows/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8f83c8764..7eb6550ee 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1558,6 +1558,17 @@ pub struct OsOpaqueIpcChannel { handle: HANDLE, } +impl Drop for OsOpaqueIpcChannel { + fn drop(&mut self) { + // Make sure we don't leak! + // + // The `OsOpaqueIpcChannel` objects should always be used, + // i.e. converted with `to_sender()` or `to_receiver()` -- + // so the value should already be unset before the object gets dropped. + debug_assert!(self.handle == INVALID_HANDLE_VALUE); + } +} + impl OsOpaqueIpcChannel { fn new(handle: HANDLE) -> OsOpaqueIpcChannel { OsOpaqueIpcChannel { From f4987b59b5679edb2eb0ed39cede659b6d9687fc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 20:56:23 +0200 Subject: [PATCH 050/113] windows: Fix SHM mapping for areas larger than 2^32 That code resulted from some kind of misunderstanding... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 7eb6550ee..bb9e1d238 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1456,11 +1456,11 @@ impl Deref for OsIpcSharedMemory { } impl OsIpcSharedMemory { - #[allow(exceeding_bitshifts)] fn new(length: usize) -> Result { unsafe { assert!(length < u32::max_value() as usize); - let (lhigh, llow) = (0 as u32, (length & 0xffffffffusize) as u32); + let (lhigh, llow) = (length.checked_shr(32).unwrap_or(0) as u32, + (length & 0xffffffff) as u32); let handle = kernel32::CreateFileMappingA(INVALID_HANDLE_VALUE, ptr::null_mut(), From ad4e40d42b018196561d8ac0896409d395ee8596 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 21:58:41 +0200 Subject: [PATCH 051/113] windows: Derive `PartialEq` on `WinError` This way we can do simple comparision, rather than needing one-arm match statements. --- src/platform/windows/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index bb9e1d238..8c8c6c454 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1585,7 +1585,7 @@ impl OsOpaqueIpcChannel { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum WinError { WindowsResult(u32), ChannelClosed, @@ -1636,10 +1636,7 @@ impl WinError { } pub fn channel_is_closed(&self) -> bool { - match *self { - WinError::ChannelClosed => true, - _ => false, - } + *self == WinError::ChannelClosed } } From f801fbe07c0ed7e7ed212409adbb59388b50f3fe Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 22:09:29 +0200 Subject: [PATCH 052/113] windows: cleanup: Don't intersperse method impls with free-standing function The convention is to keep all `impl`s of a type together in one place. --- src/platform/windows/mod.rs | 92 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8c8c6c454..ae8e582f9 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -715,6 +715,52 @@ impl MessageReader { } } +#[derive(Clone, Copy, Debug)] +enum AtomicMode { + Atomic, + Nonatomic, +} + +/// Write data to a handle. +/// +/// In `Atomic` mode, this panics if the data can't be written in a single system call. +fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { + let total = bytes.len(); + if total == 0 { + return Ok(()); + } + + let mut written = 0; + while written < total { + let mut sz: u32 = 0; + let bytes_to_write = &bytes[written..]; + unsafe { + if kernel32::WriteFile(**handle, + bytes_to_write.as_ptr() as LPVOID, + bytes_to_write.len() as u32, + &mut sz, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + written += sz as usize; + match atomic { + AtomicMode::Atomic => { + if written != total { + panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); + } + }, + AtomicMode::Nonatomic => { + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + }, + } + } + + Ok(()) +} + #[derive(Clone, Copy, Debug, PartialEq)] enum BlockingMode { Blocking, @@ -972,52 +1018,6 @@ impl Clone for OsIpcSender { } } -#[derive(Clone, Copy, Debug)] -enum AtomicMode { - Atomic, - Nonatomic, -} - -/// Write data to a handle. -/// -/// In `Atomic` mode, this panics if the data can't be written in a single system call. -fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { - let total = bytes.len(); - if total == 0 { - return Ok(()); - } - - let mut written = 0; - while written < total { - let mut sz: u32 = 0; - let bytes_to_write = &bytes[written..]; - unsafe { - if kernel32::WriteFile(**handle, - bytes_to_write.as_ptr() as LPVOID, - bytes_to_write.len() as u32, - &mut sz, - ptr::null_mut()) - == winapi::FALSE - { - return Err(WinError::last("WriteFile")); - } - } - written += sz as usize; - match atomic { - AtomicMode::Atomic => { - if written != total { - panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); - } - }, - AtomicMode::Nonatomic => { - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); - }, - } - } - - Ok(()) -} - impl OsIpcSender { pub fn connect(name: String) -> Result { let pipe_name = make_pipe_name(&Uuid::parse_str(&name).unwrap()); From 9b288b782abca22a44a548c5fee1335553144192 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 16:21:26 +0200 Subject: [PATCH 053/113] windows: cleanup: `start_read()`: Use `match` for error handling --- src/platform/windows/mod.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ae8e582f9..34565ef31 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -494,19 +494,20 @@ impl MessageReader { // handle is part of, meaning that we don't have to do any // special handling for sync-completed operations. if ok == winapi::FALSE { - let err = GetLastError(); - if err == winapi::ERROR_BROKEN_PIPE { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - } - - if err == winapi::ERROR_IO_PENDING { - self.read_in_progress = true; - return Ok(()); + match GetLastError() { + winapi::ERROR_BROKEN_PIPE => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + }, + winapi::ERROR_IO_PENDING => { + self.read_in_progress = true; + return Ok(()); + }, + err => { + Err(WinError::from_system(err, "ReadFile")) + }, } - - Err(WinError::from_system(err, "ReadFile")) } else { self.read_in_progress = true; Ok(()) From 15e834605ad35c9d0246fe3d96956aa44a8ee17b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:10:26 +0200 Subject: [PATCH 054/113] windows: cleanup: `start_read()`: Consistenly use explicit `return` Should be easier to read than mixing explicit and implicit return for no obvious reason... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 34565ef31..002cffe2d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -505,12 +505,12 @@ impl MessageReader { return Ok(()); }, err => { - Err(WinError::from_system(err, "ReadFile")) + return Err(WinError::from_system(err, "ReadFile")); }, } } else { self.read_in_progress = true; - Ok(()) + return Ok(()); } } From 888773d849b6060419d2bcc4fdb70c5656d36172 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:11:14 +0200 Subject: [PATCH 055/113] windows: cleanup: `start_read()`: Merge handling of identical cases Since the `Ok` and `ERROR_IO_PENDING` cases are identical (as explained in the comment), just fall through to a common default handler. --- src/platform/windows/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 002cffe2d..8a42728f3 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -501,17 +501,15 @@ impl MessageReader { return Ok(()); }, winapi::ERROR_IO_PENDING => { - self.read_in_progress = true; - return Ok(()); }, err => { return Err(WinError::from_system(err, "ReadFile")); }, } - } else { - self.read_in_progress = true; - return Ok(()); } + + self.read_in_progress = true; + Ok(()) } /// Called when we receive an IO Completion Packet for this handle. From 6e406d8bba33846e47743ff35f67c848f525c6b9 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:14:06 +0200 Subject: [PATCH 056/113] windows: cleanup: `start_read()`: Switch cases for more logical order Put the success case before the error cases, rather than in between... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8a42728f3..bc8196481 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -495,13 +495,13 @@ impl MessageReader { // special handling for sync-completed operations. if ok == winapi::FALSE { match GetLastError() { + winapi::ERROR_IO_PENDING => { + }, winapi::ERROR_BROKEN_PIPE => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); self.closed = true; return Ok(()); }, - winapi::ERROR_IO_PENDING => { - }, err => { return Err(WinError::from_system(err, "ReadFile")); }, From f3a0c1d3b0a23c474714ef6f03ef1121f5eaa851 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:17:38 +0200 Subject: [PATCH 057/113] windows: cleanup: `start_read()`: Construct a temporary `Result<>` Further streamline error handling by turning the FFI result into a `Result<>` value, so we can handle it in one uniform `match` expression. While it could be argue that this adds unnecessary extra code, I believe it makes the error handling easier to follow -- especially the common handling of the `Ok` and `ERROR_IO_PENDING` cases. It also allows moving the comment closer to the actual case it explains... --- src/platform/windows/mod.rs | 60 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index bc8196481..a2010eea5 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -481,35 +481,39 @@ impl MessageReader { // which would bear some risk of getting out of sync. self.read_buf.set_len(buf_len); - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - if ok == winapi::FALSE { - match GetLastError() { - winapi::ERROR_IO_PENDING => { - }, - winapi::ERROR_BROKEN_PIPE => { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - }, - err => { - return Err(WinError::from_system(err, "ReadFile")); - }, - } - } + let result = if ok == winapi::FALSE { + Err(GetLastError()) + } else { + Ok(()) + }; - self.read_in_progress = true; - Ok(()) + match result { + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + Ok(()) | + Err(winapi::ERROR_IO_PENDING) => { + self.read_in_progress = true; + Ok(()) + }, + Err(winapi::ERROR_BROKEN_PIPE) => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + Ok(()) + }, + Err(err) => { + Err(WinError::from_system(err, "ReadFile")) + }, + } } /// Called when we receive an IO Completion Packet for this handle. From 608a0f5cc98fb2b8eb8b515d0376ca4e30a1f439 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 16:35:49 +0200 Subject: [PATCH 058/113] windows: cleanup: Drop redundant comment part The second paragraph explains the same thing as the first one, only clearer... So we can just get rid of the first one entirely. --- src/platform/windows/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a2010eea5..f16ce2c68 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -488,10 +488,6 @@ impl MessageReader { }; match result { - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // // Normally, for an async operation, a call like // `ReadFile` would return `FALSE`, and the error code // would be `ERROR_IO_PENDING`. But in some situations, From 8163de2d06d72b88186d6ada76ff110782c10230 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 23:34:06 +0200 Subject: [PATCH 059/113] windows: Fix misplaced comment Apparently this ended up in the wrong place during some edit... While at it, also improve wording. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f16ce2c68..e5d94280b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -535,6 +535,7 @@ impl MessageReader { // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; + // Remote end closed the channel. if err == winapi::ERROR_BROKEN_PIPE { assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); self.closed = true; @@ -546,7 +547,6 @@ impl MessageReader { assert!(offset == 0); - // if the remote end closed... if err != winapi::ERROR_SUCCESS { // This should never happen panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); From fa9ecb2be6ddbc4498d159acbd589e76fc25355c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 9 Oct 2017 00:11:37 +0200 Subject: [PATCH 060/113] windows: Drop bogus `Result<>` from `notify_completion()` Don't know whether there was a point to it in some earlier iteration -- but in the current code, this method never returns anything else than `Ok(())`... --- src/platform/windows/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e5d94280b..8d9cc17cc 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -525,7 +525,7 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + unsafe fn notify_completion(&mut self, err: u32) { assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -539,7 +539,7 @@ impl MessageReader { if err == winapi::ERROR_BROKEN_PIPE { assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); self.closed = true; - return Ok(()); + return; } let nbytes = self.ov.InternalHigh as u32; @@ -557,8 +557,6 @@ impl MessageReader { nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); assert!(new_size <= self.read_buf.capacity()); self.read_buf.set_len(new_size); - - Ok(()) } // This is split between get_message and get_message_inner, so that @@ -918,7 +916,7 @@ impl OsIpcReceiver { // Notify that the read completed, which will update the // read pointers - try!(reader.notify_completion(err)); + reader.notify_completion(err); } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1345,7 +1343,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - unsafe { try!(reader.notify_completion(io_err)); } + unsafe { reader.notify_completion(io_err); } // then drain as many messages as we can loop { From b41423c5d6d6b3b3ca88cf275fe3a3800316f19e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 02:21:28 +0200 Subject: [PATCH 061/113] windows: Drop bogus comment This seems to be a leftover from some earlier version of the code... --- src/platform/windows/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8d9cc17cc..ef4470fbe 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -559,9 +559,6 @@ impl MessageReader { self.read_buf.set_len(new_size); } - // This is split between get_message and get_message_inner, so that - // this function can handle removing bytes from the buffer, since - // get_message_inner borrows the buffer. fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! From 1eb97b817c7a2e66f2b1fe6e0079211341b84009 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 02:49:25 +0200 Subject: [PATCH 062/113] windows: Introduce `MessageReader.fetch_async_result()` helper method Move the raw FFI call from `OsIpcReceiver.get_message()` to a new helper method in `MessageReader`. This fixes a major layering violation, and makes the functionality more reusable. Note: this doesn't address the related raw FFI call in `OsIpcReceiverSet.select()` -- that one is a whole other can of worms, which will require a larger refactoring to fix... --- src/platform/windows/mod.rs | 107 ++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ef4470fbe..feb3ad338 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -559,6 +559,57 @@ impl MessageReader { self.read_buf.set_len(new_size); } + /// Attempt to conclude an already issued async read operation. + /// + /// If successful, the result will be ready for picking up by `get_message()`. + /// + /// (`get_message()` might still yield nothing though, + /// in case only part of the message was received in this read, + /// and further read operations are necessary to get the rest.) + /// + /// In non-blocking mode, this may return with `WinError:NoData`, + /// while the async operation remains in flight. + /// + /// Note: Upon successful return, + /// the internal `ov` and `read_buf` fields + /// won't be aliased by the kernel anymore. + /// When getting `NoData` however, + /// access to these fields remains invalid, + /// i.e. we are still in unsafe mode in that case! + fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { + unsafe { + assert!(self.read_in_progress); + + // Get the overlapped result, blocking if we need to. + let mut nbytes: u32 = 0; + let mut err = winapi::ERROR_SUCCESS; + let block = match blocking_mode { + BlockingMode::Blocking => winapi::TRUE, + BlockingMode::Nonblocking => winapi::FALSE, + }; + let ok = kernel32::GetOverlappedResult(*self.handle, + self.ov.deref_mut(), + &mut nbytes, + block); + if ok == winapi::FALSE { + err = GetLastError(); + if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { + // Async read hasn't completed yet. + // Inform the caller, while keeping the read in flight. + return Err(WinError::NoData); + } + // We pass err through to notify_completion so + // that it can handle other errors. + } + + // Notify that the read completed, which will update the + // read pointers + self.notify_completion(err); + } + + Ok(()) + } + fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! @@ -784,11 +835,6 @@ pub struct OsIpcReceiver { // While this seems to be true as far as I can tell, // it's a rather fragile condition, which should be managed much more tightly // (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. -// The current implementation doesn't follow such a strict encapsulation however, -// with `OsIpcReceiver` directly accessing `reader.ov` (in `receive_message()`), -// outside the `MessageReader` implementation -- -// so for now, `OsIpcReceiver` needs to be considered responsible as a whole -// for upholding the non-aliasing condition. unsafe impl Send for OsIpcReceiver { } impl PartialEq for OsIpcReceiver { @@ -879,41 +925,22 @@ impl OsIpcReceiver { return Err(WinError::ChannelClosed); } - // Then, get the overlapped result, blocking if we need to. - let mut nbytes: u32 = 0; - let mut err = winapi::ERROR_SUCCESS; - let block = match blocking_mode { - BlockingMode::Blocking => winapi::TRUE, - BlockingMode::Nonblocking => winapi::FALSE, - }; - let ok = kernel32::GetOverlappedResult(*reader.handle, - reader.ov.deref_mut(), - &mut nbytes, - block); - if ok == winapi::FALSE { - err = GetLastError(); - if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { - // Nonblocking read, no message, read's in flight, we're - // done. An error is expected in this case. - // - // Note: This leaks unsafety outside the `unsafe` block, - // since the method returns while an async read is still in progress; - // meaning the kernel still holds a mutable alias - // of the read buffer and `OVERLAPPED` structure - // that the Rust type system doesn't know about -- - // nothing prevents code that isn't marked as `unsafe` - // from performing invalid reads or writes to these fields! - // - // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) - return Err(WinError::NoData); - } - // We pass err through to notify_completion so - // that it can handle other errors. - } - - // Notify that the read completed, which will update the - // read pointers - reader.notify_completion(err); + // May return `WinError::NoData` in non-blocking mode. + // + // The async read remains in flight in that case; + // and another attempt at getting a result + // can be done the next time we are called. + // + // Note: This leaks unsafety outside the `unsafe` block, + // since the method returns while an async read is still in progress; + // meaning the kernel still holds a mutable alias + // of the read buffer and `OVERLAPPED` structure + // that the Rust type system doesn't know about -- + // nothing prevents code that isn't marked as `unsafe` + // from performing invalid reads or writes to these fields! + // + // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) + try!(reader.fetch_async_result(blocking_mode)); } // If we're not blocking, pretend that we are blocking, since we got part of From db945e4cae62aa621e36e545522760e2430a13e0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 10 May 2018 18:55:17 +0200 Subject: [PATCH 063/113] windows: Move `Send` declaration from `OsIpcReceiver` to `MessageReader` Now that nothing touches the `ov` field outside the implementation of `MessageReader`, it makes much more sense to make `MessageReader` responsible for upholding the `Send` property. --- src/platform/windows/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index feb3ad338..facc4af5e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -392,6 +392,17 @@ struct MessageReader { set_id: Option, } +// We need to explicitly declare this, because of the raw pointer +// contained in the `OVERLAPPED` structure. +// +// Note: the `Send` claim is only really fulfilled +// as long as nothing can ever alias the aforementioned raw pointer. +// As explained in the documentation of the `ov` field, +// this is a tricky condition (because of kernel aliasing), +// which we however need to uphold regardless of the `Send` property -- +// so claiming `Send` should not introduce any additional issues. +unsafe impl Send for OsIpcReceiver { } + impl Drop for MessageReader { fn drop(&mut self) { // Before dropping the `ov` structure and read buffer, @@ -827,16 +838,6 @@ pub struct OsIpcReceiver { reader: RefCell, } -// We need to explicitly declare this, because of the raw pointer -// contained in the `OVERLAPPED` structure inside `MessageReader`. -// -// Note: the `Send` claim is only really fulfilled -// as long as nothing can ever alias the aforementioned raw pointer. -// While this seems to be true as far as I can tell, -// it's a rather fragile condition, which should be managed much more tightly -// (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. -unsafe impl Send for OsIpcReceiver { } - impl PartialEq for OsIpcReceiver { fn eq(&self, other: &OsIpcReceiver) -> bool { self.reader.borrow().handle == other.reader.borrow().handle From 41e5b9c951f636640f629a83be7c2a8aee66f9d1 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 03:44:11 +0200 Subject: [PATCH 064/113] windows: cleanup: Consume reader in `read_raw_sized()` The reader is not supposed to be used for anything else; and the caller drops it immediately afterwards anyway. Taking it by value (and dropping it ourself) makes this more explicit. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index facc4af5e..cd93f2b10 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -715,7 +715,7 @@ impl MessageReader { /// and the transfer doesn't have our typical message framing. /// /// It's only valid to call this as the one and only call after creating a MessageReader. - fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { + fn read_raw_sized(mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); // We use with_capacity() to allocate an uninitialized buffer, @@ -1017,7 +1017,7 @@ impl OsIpcReceiver { /// /// This is used for receiving data from the out-of-band big data buffer. fn recv_raw(self, size: usize) -> Result, WinError> { - self.reader.borrow_mut().read_raw_sized(size) + self.reader.into_inner().read_raw_sized(size) } } From 3de41e9a79809e210c6d5dcf96ec512c5a071aa4 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 03:19:15 +0200 Subject: [PATCH 065/113] windows: Avoid code duplication in `read_raw_sized()` As far as I can tell, this method only differs from the regular receive pattern in that it allocates the entire buffer for the message of known size in advance, and then reads into it repeatedly until the expected size is filled; at which point it returns the entire buffer -- rather than trying to extract individual messages from the buffer with `get_message()` between reads. Since the actual async read initiation and completion is pretty much the same -- apart from some shortcuts, which should be insignificant I believe -- we can just implement this functionality in terms of the regular methods, rather than keeping separate code paths that do pretty much the same. --- src/platform/windows/mod.rs | 53 ++++++++----------------------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cd93f2b10..adeb66908 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,56 +718,25 @@ impl MessageReader { fn read_raw_sized(mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); - // We use with_capacity() to allocate an uninitialized buffer, - // since we're going to read into it and don't need to - // zero it. - let mut buf = Vec::with_capacity(size); - while buf.len() < size { + self.read_buf.reserve(size); + while self.read_buf.len() < size { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. unsafe { - let ov = self.ov.deref_mut(); - *ov = mem::zeroed(); - - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = buf.len(); - let buf_cap = buf.capacity(); - buf.set_len(buf_cap); - - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut buf[buf_len..]; - kernel32::ReadFile(*self.handle, - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - ov) - }; - - // Restore the original size before error handling, - // so we never leave the function with the buffer exposing uninitialized data. - buf.set_len(buf_len); - - if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { - return Err(WinError::last("ReadFile")); + try!(self.start_read()); + // Sender should not close until it sent as much data as we expect... + if self.closed { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); } - - if ov.Internal as i32 == winapi::STATUS_PENDING { - let ok = kernel32::GetOverlappedResult(*self.handle, ov, &mut bytes_read, winapi::TRUE); - if ok == winapi::FALSE { - return Err(WinError::last("GetOverlappedResult")); - } - } else { - bytes_read = ov.InternalHigh as u32; + // In blocking mode, this should never fail... + self.fetch_async_result(BlockingMode::Blocking).unwrap(); + if self.closed { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); } - - let new_len = buf_len + bytes_read as usize; - buf.set_len(new_len); } } - Ok(buf) + Ok(mem::replace(&mut self.read_buf, vec![])) } } From bcf026f583b0982ff1b5e30e446ef3708262e22e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 20 Oct 2017 00:19:25 +0200 Subject: [PATCH 066/113] windows: Take `WinHandle` rather than `HANDLE` in `add_to_iocp()` Just as with `write_buf()`, I see no reason to use the raw handle -- so let's stick with the safer abstraction here as well... --- src/platform/windows/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index adeb66908..f4e81ccea 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -683,12 +683,12 @@ impl MessageReader { Ok(result) } - fn add_to_iocp(&mut self, iocp: HANDLE, set_id: u64) -> Result<(),WinError> { + fn add_to_iocp(&mut self, iocp: &WinHandle, set_id: u64) -> Result<(),WinError> { unsafe { assert!(self.set_id.is_none()); let ret = kernel32::CreateIoCompletionPort(*self.handle, - iocp, + **iocp, *self.handle as winapi::ULONG_PTR, 0); if ret.is_null() { @@ -1244,7 +1244,7 @@ impl OsIpcReceiverSet { let mut reader = receiver.reader.into_inner(); let set_id = self.incrementor.next().unwrap(); - try!(reader.add_to_iocp(*self.iocp, set_id)); + try!(reader.add_to_iocp(&self.iocp, set_id)); win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); From 21c92ab15ac87c686835c6ede89e6ac956576e2e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 21 Oct 2017 02:28:58 +0200 Subject: [PATCH 067/113] windows: Comment on boxing `OVERLAPPED` inside `OsIpcReceiver.accept()` --- src/platform/windows/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4e81ccea..26e969807 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -938,6 +938,8 @@ impl OsIpcReceiver { unsafe { let reader_borrow = self.reader.borrow(); let handle = *reader_borrow.handle; + // Boxing this to get a stable address is not strictly necesssary here, + // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); From 3cc52e8988142bbc5215de95c4d30446d6a04fd6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 28 Oct 2017 19:51:45 +0200 Subject: [PATCH 068/113] windows: cleanup: `OsIpcReceiver.accept()`: Unwrap handle at actual use sites Extract the raw handle from the `WinHandle` structure individually wherever it's actually needed for the Windows API calls, rather than doing it up front at the beginning of the method. Dealing with the raw handles as locally as possible seems more robust; and it's also more in line with what we do elsewhere. --- src/platform/windows/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 26e969807..ac897e75f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -937,11 +937,11 @@ impl OsIpcReceiver { fn accept(&self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); - let handle = *reader_borrow.handle; + let handle = &reader_borrow.handle; // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); + let ok = kernel32::ConnectNamedPipe(**handle, ov.deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); @@ -950,7 +950,7 @@ impl OsIpcReceiver { match err { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { - win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle); + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", **handle); Ok(()) }, @@ -960,14 +960,14 @@ impl OsIpcReceiver { // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. winapi::ERROR_NO_DATA => { - win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle); + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", **handle); Ok(()) }, // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(**handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -976,7 +976,7 @@ impl OsIpcReceiver { // Anything else signifies some actual I/O error. err => { - win32_trace!("[$ {:?}] accept error -> {}", handle, err); + win32_trace!("[$ {:?}] accept error -> {}", **handle, err); Err(WinError::last("ConnectNamedPipe")) }, } From 3fa1de2f0874fd058a77c3a59556fd14635dd904 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 20:46:32 +0100 Subject: [PATCH 069/113] windows: Fix a misleading (outdated?) comment --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ac897e75f..47139e5aa 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1355,7 +1355,7 @@ impl OsIpcReceiverSet { } } - // We may have already been closed, or the read resulted in us being closed. + // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. if reader.closed { win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); From cc31739d47aac427ba5bc426514ccd34d559be76 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 20:56:01 +0100 Subject: [PATCH 070/113] windows: cleanup: Use `if let` rather than `is_some()` + `unwrap()` --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 47139e5aa..92e92b9f7 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1366,8 +1366,8 @@ impl OsIpcReceiverSet { } } - if remove_index.is_some() { - self.readers.swap_remove(remove_index.unwrap()); + if let Some(index) = remove_index { + self.readers.swap_remove(index); } // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; From f6c276c49d40fbd23e4a6065edb95766084ba2d3 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 21:25:02 +0100 Subject: [PATCH 071/113] windows: cleanup: More idiomatic search code --- src/platform/windows/mod.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 92e92b9f7..34aecc1b1 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1285,10 +1285,9 @@ impl OsIpcReceiverSet { // read a complete message. loop { let mut nbytes: u32 = 0; - let mut reader_index: Option = None; let mut io_err = winapi::ERROR_SUCCESS; - unsafe { + let reader_index = unsafe { let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! @@ -1314,26 +1313,17 @@ impl OsIpcReceiverSet { assert!(completion_key != INVALID_HANDLE_VALUE); // Find the matching receiver - for (index, ref mut reader) in self.readers.iter_mut().enumerate() { - if completion_key != *reader.handle { - continue; - } - - reader_index = Some(index); - break; - } - } - - if reader_index.is_none() { - panic!("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - } + let (index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| *reader.handle == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + index + }; let mut remove_index = None; // We need a scope here for the mutable borrow of self.readers; // we need to (maybe) remove an element from it below. { - let reader_index = reader_index.unwrap(); let reader = &mut self.readers[reader_index]; win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); From 1dc0270a64d2731392efeba74459a301d8801e8f Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 31 Oct 2017 17:54:11 +0100 Subject: [PATCH 072/113] windows: cleanup: Use `if let` rather than one-arm `match` The other arm is empty; and since this is on an `Option<>`, we do not benefit from the exhaustiveness check either -- so the more compact variant seems preferable. --- src/platform/windows/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 34aecc1b1..2205a7073 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -870,11 +870,8 @@ impl OsIpcReceiver { loop { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer - match try!(reader.get_message()) { - Some((data, channels, shmems)) => - return Ok((data, channels, shmems)), - None => - {}, + if let Some((data, channels, shmems)) = try!(reader.get_message()) { + return Ok((data, channels, shmems)); } // If the pipe was already closed, we're done -- we've From cfeab5f7f2e11e926c2cc721458b12d5884b56b0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 5 Nov 2017 18:37:37 +0100 Subject: [PATCH 073/113] windows: Don't explicitly set `ErrorKind::BrokenPipe` on closed sender This is related to the fix for the `inprocess` back-end in 750d9a12512727e31c9e9bc0616d271a106d7a57 : according to the documentation of `ErrorKind`, just like the related Unix error, `ErrorKind::BrokenPipe` is supposed to signal only a "receiver closed" condition -- and not "sender closed". Note that the situation is quite confusing here, since the Windows API actually returns a `winapi::ERROR_BROKEN_PIPE` for the "sender closed" condition; so it would seem an obvious choice to turn it into `ErrorKind::BrokenPipe`... Except that this conflicts with the stated purpose according to the documentation. Rather than explicitly deciding on the right `ErrorKind` to use here, we just punt this back to the Windows API, letting it assign whatever `ErrorKind` it deems appropriate for this situation -- thus avoiding a potential confusing discrepancy with other `winapi` users, at the cost of a potential confusing discrepancy with other `ipc-channel` back-ends... The situation here differs from that in the `inprocess` back-end also in that we were already using a distinct `WinError::ChannelClosed` internal state for closed senders, rather than throwing it in with the handling of closed receivers -- so it didn't affect the behaviour of the public `channel_is_closed()` method here, but only the converted `Error` value. (And thus the message printed on `unwrap()`.) --- src/platform/windows/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 2205a7073..46249f5d6 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1630,7 +1630,10 @@ impl From for Error { fn from(error: WinError) -> Error { match error { WinError::ChannelClosed => { - Error::new(ErrorKind::BrokenPipe, "Win channel closed") + // This is the error code we originally got from the Windows API + // to signal the "channel closed" (no sender) condition -- + // so hand it back to the Windows API to create an appropriate `Error` value. + Error::from_raw_os_error(winapi::ERROR_BROKEN_PIPE as i32) }, WinError::NoData => { Error::new(ErrorKind::WouldBlock, "Win channel has no data available") From 686960faa80ea952f7bbd648a55575f619a8fd86 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 12 Nov 2017 17:33:06 +0100 Subject: [PATCH 074/113] windows: cleanup: Use `while let` rather than `loop` + `match` More idiomatic code is much shorter, and probably easier to follow too. --- src/platform/windows/mod.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 46249f5d6..d7c86f7d0 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1329,18 +1329,11 @@ impl OsIpcReceiverSet { unsafe { reader.notify_completion(io_err); } // then drain as many messages as we can - loop { - match try!(reader.get_message()) { - Some((data, channels, shmems)) => { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - }, - None => { - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - break; - }, - } + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. From b1d2b97ac7b482b348b121ebaeff8086f3240a1d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 12 Nov 2017 21:24:39 +0100 Subject: [PATCH 075/113] windows: refactor: More straightforward "closed" status handling Check for the "closed" status right after issuing operations that can set it, rather than only after draining pending messages. This makes the control flow much clearer in general; and it also avoids suggesting that we could have both pending messages and closed status at the same time -- which is not the case. The more straightforward handling flow will also facilitate further refactoring. Note that there is one place where we still do not handle the closed status immediately: if we get a "closed" notification while starting a read in the course of adding a receiver to a set, we actually have to delay reporting the status until the next `select()` call. (Though the check there still happens before trying to drain messages -- even before trying to fetch the completion status, in fact.) We also do not immediately handle the "closed" status when re-initiating the async read after receiving data inside `select()`; keeping the original approach instead for now, where it gets handled as a side effect of the code for the case described above. We aren't changing this bit for now, since returning the "closed" event immediately (rather than delaying to the next `select()` call) will actually be a slight behaviour change, as opposed to just pure refactoring. --- src/platform/windows/mod.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d7c86f7d0..12166e050 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -449,7 +449,10 @@ impl MessageReader { /// /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - if self.read_in_progress || self.closed { + // There is no valid reason to call this again after getting a channel closed notification. + assert!(!self.closed); + + if self.read_in_progress { return Ok(()); } @@ -623,6 +626,9 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { + // We should never expect having pending data after receiving a channel closed notification. + assert!(!self.closed); + // Never touch the buffer while it's still mutably aliased by the kernel! if self.read_in_progress { return Ok(None); @@ -874,12 +880,6 @@ impl OsIpcReceiver { return Ok((data, channels, shmems)); } - // If the pipe was already closed, we're done -- we've - // already drained all incoming bytes - if reader.closed { - return Err(WinError::ChannelClosed); - } - unsafe { // Then, issue a read if we don't have one already in flight. // We must not issue a read if we have complete unconsumed @@ -908,6 +908,10 @@ impl OsIpcReceiver { // // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) try!(reader.fetch_async_result(blocking_mode)); + + if reader.closed { + return Err(WinError::ChannelClosed); + } } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1328,13 +1332,6 @@ impl OsIpcReceiverSet { // tell it about the completed IO op unsafe { reader.notify_completion(io_err); } - // then drain as many messages as we can - while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. if reader.closed { @@ -1342,6 +1339,18 @@ impl OsIpcReceiverSet { selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } else { + // Otherwise, drain as many messages as we can. + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + + // Now that we are done frobbing the buffer, + // we can safely initiate the next async read operation. + // + // Note: if this operation sets the `closed` status for this reader, + // it will be handled at the beginning of the next `select()` call. unsafe { try!(reader.start_read()); } } } From fbd863222fd54f5e8493c7292a891a7d74c12621 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 13 Nov 2017 01:24:52 +0100 Subject: [PATCH 076/113] windows: Don't delay "sender closed" notification from `select()` Immediately handle the `closed` status -- rather than postponing handling to the next `select()` call -- even if it gets set in `start_read()`. (It already was being reported immediately in case the status got set in `notify_completion()`.) This is undoubtedly the more obvious and desirable behaviour. (I wonder whether we should actually enforce it with a test case?...) Also, the code should be easier to follow this way. --- src/platform/windows/mod.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 12166e050..fce529155 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1332,14 +1332,8 @@ impl OsIpcReceiverSet { // tell it about the completed IO op unsafe { reader.notify_completion(io_err); } - // Instead of new data, we might have received a broken pipe notification. - // If so, add that to the result and remove the reader from our list. - if reader.closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); - remove_index = Some(reader_index); - } else { - // Otherwise, drain as many messages as we can. + if !reader.closed { + // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); @@ -1348,11 +1342,19 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - // - // Note: if this operation sets the `closed` status for this reader, - // it will be handled at the beginning of the next `select()` call. unsafe { try!(reader.start_read()); } } + + // If we got a "sender closed" notification -- + // either instead of new data, + // or while trying to re-initiate an async read after receiving data -- + // add an event to this effect to the result list, + // and remove the reader in question from our set. + if reader.closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + remove_index = Some(reader_index); + } } if let Some(index) = remove_index { From 668701620cd8052c4b017bfa5e576e88962bfaa9 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 13 Nov 2017 21:45:41 +0100 Subject: [PATCH 077/113] windows: Handle channel closed events directly where possible Only use the indirection with the `MessageReader.closed` flag where it is really necessary, i.e. where we aren't directly passing the notification to the caller, but rather need to delay it. Specifically, this only happens when getting a "closed" notification while adding a receiver to a set. In all other cases, just work with a regular return status. This simplifies the code in some places, and complicated it in others -- but in *all* cases, the more direct handling should make the code easier to follow... --- src/platform/windows/mod.rs | 93 ++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index fce529155..53e6d6b85 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -380,8 +380,10 @@ struct MessageReader { /// Thus it is crucial that we always set this correctly! read_in_progress: bool, - /// Whether we received a BROKEN_PIPE or other error - /// indicating that the remote end has closed the pipe. + /// We got added to a receiver set + /// after the the sender end of the channel got closed -- + /// so we need to report a "closed" event in the next `select()` call, + /// rather than actually waiting for data from this channel. closed: bool, /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. @@ -517,8 +519,7 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - Ok(()) + Err(WinError::ChannelClosed) }, Err(err) => { Err(WinError::from_system(err, "ReadFile")) @@ -539,7 +540,8 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) { + unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { + assert!(!self.closed); assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -551,9 +553,7 @@ impl MessageReader { // Remote end closed the channel. if err == winapi::ERROR_BROKEN_PIPE { - assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); - self.closed = true; - return; + return Err(WinError::ChannelClosed); } let nbytes = self.ov.InternalHigh as u32; @@ -571,6 +571,8 @@ impl MessageReader { nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); assert!(new_size <= self.read_buf.capacity()); self.read_buf.set_len(new_size); + + Ok(()) } /// Attempt to conclude an already issued async read operation. @@ -618,10 +620,8 @@ impl MessageReader { // Notify that the read completed, which will update the // read pointers - self.notify_completion(err); + self.notify_completion(err) } - - Ok(()) } fn get_message(&mut self) -> Result, Vec, Vec)>, @@ -709,9 +709,15 @@ impl MessageReader { // Note: Just like in `OsIpcReceiver.receive_message()` below, // this makes us vulnerable to invalid `ov` and `read_buf` modification // from code not marked as unsafe... - try!(self.start_read()); - - Ok(()) + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the sender has already been closed, we need to stash this information, + // so we can report the corresponding event in the next `select()` call. + self.closed = true; + Ok(()) + } + result => result, + } } } @@ -729,16 +735,25 @@ impl MessageReader { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. unsafe { - try!(self.start_read()); - // Sender should not close until it sent as much data as we expect... - if self.closed { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } - // In blocking mode, this should never fail... - self.fetch_async_result(BlockingMode::Blocking).unwrap(); - if self.closed { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the helper channel closes unexpectedly + // (i.e. before supplying the expected amount of data), + // don't report that as a "sender closed" condition on the main channel: + // rather, fail with the actual raw error code. + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); + } + Err(err) => return Err(err), + Ok(()) => {} + }; + match self.fetch_async_result(BlockingMode::Blocking) { + Err(WinError::ChannelClosed) => { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) + } + // In blocking mode, `fetch_async_result()` has no other expected failure modes. + Err(_) => unreachable!(), + Ok(()) => {} + }; } } @@ -886,12 +901,6 @@ impl OsIpcReceiver { // messages, because getting a message modifies the read_buf. try!(reader.start_read()); - // If the last read flagged us closed we're done; we've already - // drained all incoming bytes earlier in the loop. - if reader.closed { - return Err(WinError::ChannelClosed); - } - // May return `WinError::NoData` in non-blocking mode. // // The async read remains in flight in that case; @@ -908,10 +917,6 @@ impl OsIpcReceiver { // // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) try!(reader.fetch_async_result(blocking_mode)); - - if reader.closed { - return Err(WinError::ChannelClosed); - } } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1330,9 +1335,15 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - unsafe { reader.notify_completion(io_err); } + let mut closed = unsafe { + match reader.notify_completion(io_err) { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; - if !reader.closed { + if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); @@ -1342,7 +1353,13 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - unsafe { try!(reader.start_read()); } + closed = unsafe { + match reader.start_read() { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; } // If we got a "sender closed" notification -- @@ -1350,7 +1367,7 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list, // and remove the reader in question from our set. - if reader.closed { + if closed { win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); From 91b4331b31204ce681a8340fc5e3ad078d716916 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 14:05:32 +0200 Subject: [PATCH 078/113] windows: cleanup: Simplify control flow --- src/platform/windows/mod.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 53e6d6b85..0d0587359 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1282,14 +1282,9 @@ impl OsIpcReceiverSet { } }); - // if we had prematurely closed elements, just process them first - if !selection_results.is_empty() { - return Ok(selection_results); - } - // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. - loop { + while selection_results.is_empty() { let mut nbytes: u32 = 0; let mut io_err = winapi::ERROR_SUCCESS; @@ -1377,12 +1372,6 @@ impl OsIpcReceiverSet { if let Some(index) = remove_index { self.readers.swap_remove(index); } - - // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; - // otherwise we're done. - if !selection_results.is_empty() { - break; - } } win32_trace!("select() -> {} results", selection_results.len()); From 17c7901e630235a9eeff2d0914b09577e9019ece Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 10:20:58 +0200 Subject: [PATCH 079/113] windows: Introduce `closed_readers` vector instead of `MessageReader.closed` flag Since the `closed` flag was now only being used to signal a reader that has received a "closed" notification while being added to a set, the set can just keep such closed readers in a dedicated vector instead, thus avoiding the need for the `closed` flag altogether. This makes the code simpler, clearer, and less error prone. There is a slight behaviour change resulting from this: the `MessageReader` object of the closed reader is now dropped already while the reader is being added to the set, rather than only on the next `select()` call. This shouldn't have any observable effect, though. --- src/platform/windows/mod.rs | 71 +++++++++++++++---------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 0d0587359..f1f917e8e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -380,12 +380,6 @@ struct MessageReader { /// Thus it is crucial that we always set this correctly! read_in_progress: bool, - /// We got added to a receiver set - /// after the the sender end of the channel got closed -- - /// so we need to report a "closed" event in the next `select()` call, - /// rather than actually waiting for data from this channel. - closed: bool, - /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -420,7 +414,6 @@ impl MessageReader { ov: Box::new(unsafe { mem::zeroed::() }), read_buf: Vec::new(), read_in_progress: false, - closed: false, set_id: None, } } @@ -451,9 +444,6 @@ impl MessageReader { /// /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - // There is no valid reason to call this again after getting a channel closed notification. - assert!(!self.closed); - if self.read_in_progress { return Ok(()); } @@ -541,7 +531,6 @@ impl MessageReader { /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { - assert!(!self.closed); assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -626,9 +615,6 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { - // We should never expect having pending data after receiving a channel closed notification. - assert!(!self.closed); - // Never touch the buffer while it's still mutably aliased by the kernel! if self.read_in_progress { return Ok(None); @@ -709,15 +695,7 @@ impl MessageReader { // Note: Just like in `OsIpcReceiver.receive_message()` below, // this makes us vulnerable to invalid `ov` and `read_buf` modification // from code not marked as unsafe... - match self.start_read() { - Err(WinError::ChannelClosed) => { - // If the sender has already been closed, we need to stash this information, - // so we can report the corresponding event in the next `select()` call. - self.closed = true; - Ok(()) - } - result => result, - } + self.start_read() } } @@ -1226,6 +1204,13 @@ pub struct OsIpcReceiverSet { /// The set of receivers, stored as MessageReaders. readers: Vec, + + /// Readers that got closed before adding them to the set. + /// + /// These need to report a "closed" event on the next `select()` call. + /// + /// Only the `set_id` is necessary for that. + closed_readers: Vec, } impl OsIpcReceiverSet { @@ -1243,6 +1228,7 @@ impl OsIpcReceiverSet { incrementor: 0.., iocp: WinHandle::new(iocp), readers: vec![], + closed_readers: vec![], }) } } @@ -1252,35 +1238,36 @@ impl OsIpcReceiverSet { let mut reader = receiver.reader.into_inner(); let set_id = self.incrementor.next().unwrap(); - try!(reader.add_to_iocp(&self.iocp, set_id)); - - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); - self.readers.push(reader); + match reader.add_to_iocp(&self.iocp, set_id) { + Ok(()) => { + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + self.readers.push(reader); + } + Err(WinError::ChannelClosed) => { + // If the sender has already been closed, we need to stash this information, + // so we can report the corresponding event in the next `select()` call. + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", *self.iocp, *reader.handle, set_id); + self.closed_readers.push(set_id); + } + Err(err) => return Err(err), + }; Ok(set_id) } pub fn select(&mut self) -> Result,WinError> { - assert!(!self.readers.is_empty(), "selecting with no objects?"); - win32_trace!("[# {:?}] select() with {} receivers", *self.iocp, self.readers.len()); + assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); + win32_trace!("[# {:?}] select() with {} active and {} closed receivers", *self.iocp, self.readers.len(), self.closed_readers.len()); // the ultimate results let mut selection_results = vec![]; - // Make a quick first-run check for any closed receivers. - // This will only happen if we have a receiver that - // gets added to the Set after it was closed (the - // router_drops_callbacks_on_cloned_sender_shutdown test - // causes this.) - self.readers.retain(|ref r| { - if r.closed { - selection_results.push(OsIpcSelectionResult::ChannelClosed(r.set_id.unwrap())); - false - } else { - true - } - }); + // Process any pending "closed" events + // from channels that got closed before being added to the set, + // and thus received "closed" notifications while being added. + self.closed_readers.drain(..) + .for_each(|set_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(set_id))); // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. From 4f0d5e257c0771ed2d9d2b01807c9f8ef51f9600 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:00:39 +0200 Subject: [PATCH 080/113] windows: cleanup: Rename `WinHandle.take()` to `take_raw()` This makes it more explicit that we are getting a raw handle back rather than another `WinHandle`; and avoids confusion with the ordinary meaning of `take()`, which preserves the type. --- src/platform/windows/mod.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f1f917e8e..001a2b8de 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -259,7 +259,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( - handle.take(), **other_process, + handle.take_raw(), **other_process, winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); Ok(WinHandle::new(h)) } @@ -328,7 +328,7 @@ impl WinHandle { self.h != INVALID_HANDLE_VALUE } - fn take(&mut self) -> HANDLE { + fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } } @@ -853,7 +853,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } + unsafe { OsIpcReceiver::from_handle(reader.handle.take_raw()) } } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -990,7 +990,7 @@ impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { unsafe { let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSender::from_handle(handle.take()) + OsIpcSender::from_handle(handle.take_raw()) } } } @@ -1104,23 +1104,23 @@ impl OsIpcSender { for ref shmem in shared_memory_regions { // shmem.handle, shmem.length let mut remote_handle = try!(dup_handle_to_process(&shmem.handle, &server_h)); - oob.shmem_handles.push((remote_handle.take() as intptr_t, shmem.length as u64)); + oob.shmem_handles.push((remote_handle.take_raw() as intptr_t, shmem.length as u64)); } for port in ports { match port { OsIpcChannel::Sender(s) => { let mut raw_remote_handle = try!(move_handle_to_process(s.handle, &server_h)); - oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, OsIpcChannel::Receiver(r) => { if try!(r.prepare_for_transfer()) == false { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let handle = WinHandle::new(r.reader.into_inner().handle.take()); + let handle = WinHandle::new(r.reader.into_inner().handle.take_raw()); let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); - oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, } } @@ -1138,9 +1138,9 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let handle = WinHandle::new(receiver.reader.into_inner().handle.take()); + let handle = WinHandle::new(receiver.reader.into_inner().handle.take_raw()); let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); - oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); + oob.big_data_receiver_handle = Some((raw_receiver_handle.take_raw() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; Some(sender) @@ -1402,7 +1402,7 @@ impl Clone for OsIpcSharedMemory { fn clone(&self) -> OsIpcSharedMemory { unsafe { let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSharedMemory::from_handle(handle.take(), self.length).unwrap() + OsIpcSharedMemory::from_handle(handle.take_raw(), self.length).unwrap() } } } From fb768aadad125b31132a093b6a2e8cbe3dd43aca Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:19:10 +0200 Subject: [PATCH 081/113] windows: cleanup: More idiomatic `for` loops Drop unnecessary explicit `iter()` calls; and don't use reference iterators when we can (and indeed should) consume the values. --- src/platform/windows/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 001a2b8de..0cc4596c8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -633,11 +633,11 @@ impl MessageReader { oob.big_data_receiver_handle); unsafe { - for handle in oob.channel_handles.iter() { - channels.push(OsOpaqueIpcChannel::new(*handle as HANDLE)); + for handle in oob.channel_handles { + channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); } - for sh in oob.shmem_handles.iter() { + for sh in oob.shmem_handles { shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); } From 5374198c5746d9e89202ad4632031fce994a44a0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:24:41 +0200 Subject: [PATCH 082/113] windows: cleanup: More idiomatic use of tuples --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 0cc4596c8..4047f1441 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -637,8 +637,8 @@ impl MessageReader { channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); } - for sh in oob.shmem_handles { - shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); + for (handle, size) in oob.shmem_handles { + shmems.push(OsIpcSharedMemory::from_handle(handle as HANDLE, size as usize).unwrap()); } if oob.big_data_receiver_handle.is_some() { From 4d1dd1d164e738150d2ea19a69339246ca8164b5 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 22:08:38 +0200 Subject: [PATCH 083/113] windows: Use `WinHandle` in more places There was a bunch of places that used raw handles, although using the `WinHandle` wrapper works perfectly fine, and there is no reason to forsake the additional guarantees provided by the type system. This slightly complicates the code in some places, while somewhat simplifying it in others -- it pretty much feels cleaner across the board, though. As a side effect, a bunch of functions are not marked `unsafe` anymore, since they were only marked as such (somewhat controversially) for their use of raw handles. --- src/platform/windows/mod.rs | 139 +++++++++++++++++------------------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 4047f1441..d7b002ad2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -29,7 +29,7 @@ use winapi; lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; - static ref CURRENT_PROCESS_HANDLE: intptr_t = unsafe { kernel32::GetCurrentProcess() as intptr_t }; + static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } @@ -223,46 +223,44 @@ fn make_pipe_name(pipe_id: &Uuid) -> CString { /// /// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is /// also the pseudohandle for the current process). -unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) - -> Result +fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandle, flags: winapi::DWORD) + -> Result { - if handle == INVALID_HANDLE_VALUE { - return Ok(INVALID_HANDLE_VALUE); + if !handle.is_valid() { + return Ok(WinHandle::invalid()); } - let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let ok = kernel32::DuplicateHandle(*CURRENT_PROCESS_HANDLE as HANDLE, handle, - other_process, &mut new_handle, - 0, winapi::FALSE, flags); - if ok == winapi::FALSE { - Err(WinError::last("DuplicateHandle")) - } else { - Ok(new_handle) + unsafe { + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let ok = kernel32::DuplicateHandle(**CURRENT_PROCESS_HANDLE, **handle, + **other_process, &mut new_handle, + 0, winapi::FALSE, flags); + if ok == winapi::FALSE { + Err(WinError::last("DuplicateHandle")) + } else { + Ok(WinHandle::new(new_handle)) + } } } /// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { - dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) + dup_handle_to_process(handle, &WinHandle::new(**CURRENT_PROCESS_HANDLE)) } /// Duplicate a handle to the target process. fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { - unsafe { - let h = try!(dup_handle_to_process_with_flags( - **handle, **other_process, winapi::DUPLICATE_SAME_ACCESS)); - Ok(WinHandle::new(h)) - } + dup_handle_to_process_with_flags(handle, other_process, winapi::DUPLICATE_SAME_ACCESS) } /// Duplicate a handle to the target process, closing the source handle. -fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { - unsafe { - let h = try!(dup_handle_to_process_with_flags( - handle.take_raw(), **other_process, - winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); - Ok(WinHandle::new(h)) - } +fn move_handle_to_process(handle: WinHandle, other_process: &WinHandle) -> Result { + let result = dup_handle_to_process_with_flags(&handle, other_process, + winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS); + // Since the handle was moved to another process, the original is no longer valid; + // so we probably shouldn't try to close it explicitly? + mem::forget(handle); + result } #[derive(Debug)] @@ -632,20 +630,20 @@ impl MessageReader { self.handle, message.data_len, oob.channel_handles.len(), oob.shmem_handles.len(), oob.big_data_receiver_handle); - unsafe { - for handle in oob.channel_handles { - channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); - } + for handle in oob.channel_handles { + channels.push(OsOpaqueIpcChannel::new(WinHandle::new(handle as HANDLE))); + } - for (handle, size) in oob.shmem_handles { - shmems.push(OsIpcSharedMemory::from_handle(handle as HANDLE, size as usize).unwrap()); - } + for (handle, size) in oob.shmem_handles { + shmems.push(OsIpcSharedMemory::from_handle(WinHandle::new(handle as HANDLE), + size as usize, + ).unwrap()); + } - if oob.big_data_receiver_handle.is_some() { - let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); - let receiver = OsIpcReceiver::from_handle(handle as HANDLE); - big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); - } + if oob.big_data_receiver_handle.is_some() { + let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); + let receiver = OsIpcReceiver::from_handle(WinHandle::new(handle as HANDLE)); + big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); } } @@ -813,9 +811,9 @@ impl PartialEq for OsIpcReceiver { } impl OsIpcReceiver { - unsafe fn from_handle(handle: HANDLE) -> OsIpcReceiver { + fn from_handle(handle: WinHandle) -> OsIpcReceiver { OsIpcReceiver { - reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + reader: RefCell::new(MessageReader::new(handle)), } } @@ -853,7 +851,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - unsafe { OsIpcReceiver::from_handle(reader.handle.take_raw()) } + OsIpcReceiver::from_handle(WinHandle::new(reader.handle.take_raw())) } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -988,10 +986,7 @@ pub struct OsIpcSender { impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { - unsafe { - let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSender::from_handle(handle.take_raw()) - } + OsIpcSender::from_handle(dup_handle(&self.handle).unwrap()) } } @@ -1005,9 +1000,9 @@ impl OsIpcSender { MAX_FRAGMENT_SIZE } - unsafe fn from_handle(handle: HANDLE) -> OsIpcSender { + fn from_handle(handle: WinHandle) -> OsIpcSender { OsIpcSender { - handle: WinHandle::new(handle), + handle: handle, nosync_marker: PhantomData, } } @@ -1029,7 +1024,7 @@ impl OsIpcSender { win32_trace!("[c {:?}] connect_to_server success", handle); - Ok(OsIpcSender::from_handle(handle)) + Ok(OsIpcSender::from_handle(WinHandle::new(handle))) } } @@ -1047,7 +1042,7 @@ impl OsIpcSender { unsafe { let server_pid = try!(self.get_pipe_server_process_id()); if server_pid == *CURRENT_PROCESS_ID { - return Ok((WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE), server_pid)); + return Ok((WinHandle::new(**CURRENT_PROCESS_HANDLE), server_pid)); } let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, @@ -1400,10 +1395,7 @@ impl Drop for OsIpcSharedMemory { impl Clone for OsIpcSharedMemory { fn clone(&self) -> OsIpcSharedMemory { - unsafe { - let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSharedMemory::from_handle(handle.take_raw(), self.length).unwrap() - } + OsIpcSharedMemory::from_handle(dup_handle(&self.handle).unwrap(), self.length).unwrap() } } @@ -1448,7 +1440,7 @@ impl OsIpcSharedMemory { return Err(WinError::last("CreateFileMapping")); } - OsIpcSharedMemory::from_handle(handle, length) + OsIpcSharedMemory::from_handle(WinHandle::new(handle), length) } } @@ -1458,22 +1450,21 @@ impl OsIpcSharedMemory { // // This function takes ownership of the handle, and will close it // when finished. - unsafe fn from_handle(handle_raw: HANDLE, length: usize) -> Result { - // turn this into a WinHandle, because that will - // take care of closing it - let handle = WinHandle::new(handle_raw); - let address = kernel32::MapViewOfFile(handle_raw, - winapi::FILE_MAP_ALL_ACCESS, - 0, 0, 0); - if address.is_null() { - return Err(WinError::last("MapViewOfFile")); - } + fn from_handle(handle: WinHandle, length: usize) -> Result { + unsafe { + let address = kernel32::MapViewOfFile(*handle, + winapi::FILE_MAP_ALL_ACCESS, + 0, 0, 0); + if address.is_null() { + return Err(WinError::last("MapViewOfFile")); + } - Ok(OsIpcSharedMemory { - handle: handle, - ptr: address as *mut u8, - length: length - }) + Ok(OsIpcSharedMemory { + handle: handle, + ptr: address as *mut u8, + length: length + }) + } } pub fn from_byte(byte: u8, length: usize) -> OsIpcSharedMemory { @@ -1532,7 +1523,7 @@ pub enum OsIpcChannel { #[derive(PartialEq, Debug)] pub struct OsOpaqueIpcChannel { - handle: HANDLE, + handle: WinHandle, } impl Drop for OsOpaqueIpcChannel { @@ -1542,23 +1533,23 @@ impl Drop for OsOpaqueIpcChannel { // The `OsOpaqueIpcChannel` objects should always be used, // i.e. converted with `to_sender()` or `to_receiver()` -- // so the value should already be unset before the object gets dropped. - debug_assert!(self.handle == INVALID_HANDLE_VALUE); + debug_assert!(!self.handle.is_valid()); } } impl OsOpaqueIpcChannel { - fn new(handle: HANDLE) -> OsOpaqueIpcChannel { + fn new(handle: WinHandle) -> OsOpaqueIpcChannel { OsOpaqueIpcChannel { handle: handle, } } pub fn to_receiver(&mut self) -> OsIpcReceiver { - unsafe { OsIpcReceiver::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } + OsIpcReceiver::from_handle(WinHandle::new(self.handle.take_raw())) } pub fn to_sender(&mut self) -> OsIpcSender { - unsafe { OsIpcSender::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } + OsIpcSender::from_handle(WinHandle::new(self.handle.take_raw())) } } From 7119bee50ac8809aab76897df3e3bcab661efad0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 23:19:45 +0200 Subject: [PATCH 084/113] windows: refactor: (Re-)introduce `WinHandle.take()` with expected meaning Now that `WinHandle` is used in more places, many previous uses of `take_raw()` now actually benefit from a proper `take()` method, returning another `WinHandle` while invalidating the original one. --- src/platform/windows/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d7b002ad2..e29457064 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -329,6 +329,10 @@ impl WinHandle { fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } + + fn take(&mut self) -> WinHandle { + WinHandle::new(self.take_raw()) + } } /// Main object keeping track of a receive handle and its associated state. @@ -851,7 +855,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - OsIpcReceiver::from_handle(WinHandle::new(reader.handle.take_raw())) + OsIpcReceiver::from_handle(reader.handle.take()) } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -1113,7 +1117,7 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let handle = WinHandle::new(r.reader.into_inner().handle.take_raw()); + let handle = r.reader.into_inner().handle.take(); let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, @@ -1133,7 +1137,7 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let handle = WinHandle::new(receiver.reader.into_inner().handle.take_raw()); + let handle = receiver.reader.into_inner().handle.take(); let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take_raw() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; @@ -1545,11 +1549,11 @@ impl OsOpaqueIpcChannel { } pub fn to_receiver(&mut self) -> OsIpcReceiver { - OsIpcReceiver::from_handle(WinHandle::new(self.handle.take_raw())) + OsIpcReceiver::from_handle(self.handle.take()) } pub fn to_sender(&mut self) -> OsIpcSender { - OsIpcSender::from_handle(WinHandle::new(self.handle.take_raw())) + OsIpcSender::from_handle(self.handle.take()) } } From 143cce187f6c1d32cbe6acc96cbfe6d12082c0b1 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 07:09:28 +0200 Subject: [PATCH 085/113] windows: Use explicit `as_raw()` on WinHandle instead of `deref()` The use of dereferencing to get at the raw handle always felt unintuitive to me, and hard to keep track of. Using a more explicit `as_raw()` method instead seems clearer. (It should also be more flexible I think -- though we aren't actually making use of any of the added possibilities...) --- src/platform/windows/mod.rs | 73 +++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e29457064..9599c03c5 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -232,8 +232,8 @@ fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandl unsafe { let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let ok = kernel32::DuplicateHandle(**CURRENT_PROCESS_HANDLE, **handle, - **other_process, &mut new_handle, + let ok = kernel32::DuplicateHandle(CURRENT_PROCESS_HANDLE.as_raw(), handle.as_raw(), + other_process.as_raw(), &mut new_handle, 0, winapi::FALSE, flags); if ok == winapi::FALSE { Err(WinError::last("DuplicateHandle")) @@ -245,7 +245,7 @@ fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandl /// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { - dup_handle_to_process(handle, &WinHandle::new(**CURRENT_PROCESS_HANDLE)) + dup_handle_to_process(handle, &WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw())) } /// Duplicate a handle to the target process. @@ -288,15 +288,6 @@ impl Default for WinHandle { } } -impl Deref for WinHandle { - type Target = HANDLE; - - #[inline] - fn deref(&self) -> &HANDLE { - &self.h - } -} - impl PartialEq for WinHandle { fn eq(&self, other: &WinHandle) -> bool { // FIXME This does not actually implement the desired behaviour: @@ -326,6 +317,10 @@ impl WinHandle { self.h != INVALID_HANDLE_VALUE } + fn as_raw(&self) -> HANDLE { + self.h + } + fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } @@ -423,7 +418,7 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.read_in_progress { - kernel32::CancelIoEx(*self.handle, self.ov.deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), self.ov.deref_mut()); self.read_in_progress = false; } } @@ -467,7 +462,7 @@ impl MessageReader { let mut bytes_read: u32 = 0; let ok = { let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(*self.handle, + kernel32::ReadFile(self.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, @@ -594,7 +589,7 @@ impl MessageReader { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, }; - let ok = kernel32::GetOverlappedResult(*self.handle, + let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), self.ov.deref_mut(), &mut nbytes, block); @@ -681,9 +676,9 @@ impl MessageReader { unsafe { assert!(self.set_id.is_none()); - let ret = kernel32::CreateIoCompletionPort(*self.handle, - **iocp, - *self.handle as winapi::ULONG_PTR, + let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), + iocp.as_raw(), + self.handle.as_raw() as winapi::ULONG_PTR, 0); if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); @@ -761,7 +756,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), let mut sz: u32 = 0; let bytes_to_write = &bytes[written..]; unsafe { - if kernel32::WriteFile(**handle, + if kernel32::WriteFile(handle.as_raw(), bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, &mut sz, @@ -779,7 +774,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), } }, AtomicMode::Nonatomic => { - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle.as_raw(), sz, written, total, GetLastError()); }, } } @@ -927,7 +922,7 @@ impl OsIpcReceiver { // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(**handle, ov.deref_mut()); + let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); @@ -936,7 +931,7 @@ impl OsIpcReceiver { match err { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { - win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", **handle); + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle.as_raw()); Ok(()) }, @@ -946,14 +941,14 @@ impl OsIpcReceiver { // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. winapi::ERROR_NO_DATA => { - win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", **handle); + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle.as_raw()); Ok(()) }, // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(**handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -962,7 +957,7 @@ impl OsIpcReceiver { // Anything else signifies some actual I/O error. err => { - win32_trace!("[$ {:?}] accept error -> {}", **handle, err); + win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); Err(WinError::last("ConnectNamedPipe")) }, } @@ -1035,7 +1030,7 @@ impl OsIpcSender { fn get_pipe_server_process_id(&self) -> Result { unsafe { let mut server_pid: winapi::ULONG = 0; - if kernel32::GetNamedPipeServerProcessId(*self.handle, &mut server_pid) == winapi::FALSE { + if kernel32::GetNamedPipeServerProcessId(self.handle.as_raw(), &mut server_pid) == winapi::FALSE { return Err(WinError::last("GetNamedPipeServerProcessId")); } Ok(server_pid) @@ -1046,7 +1041,7 @@ impl OsIpcSender { unsafe { let server_pid = try!(self.get_pipe_server_process_id()); if server_pid == *CURRENT_PROCESS_ID { - return Ok((WinHandle::new(**CURRENT_PROCESS_HANDLE), server_pid)); + return Ok((WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw()), server_pid)); } let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, @@ -1072,7 +1067,7 @@ impl OsIpcSender { /// An internal-use-only send method that sends just raw data, with no header. fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { - win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, + win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", self.handle.as_raw(), data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); // Write doesn't need to be atomic, @@ -1240,13 +1235,13 @@ impl OsIpcReceiverSet { match reader.add_to_iocp(&self.iocp, set_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", *self.iocp, *reader.handle, set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); self.closed_readers.push(set_id); } Err(err) => return Err(err), @@ -1257,7 +1252,7 @@ impl OsIpcReceiverSet { pub fn select(&mut self) -> Result,WinError> { assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); - win32_trace!("[# {:?}] select() with {} active and {} closed receivers", *self.iocp, self.readers.len(), self.closed_readers.len()); + win32_trace!("[# {:?}] select() with {} active and {} closed receivers", self.iocp.as_raw(), self.readers.len(), self.closed_readers.len()); // the ultimate results let mut selection_results = vec![]; @@ -1278,12 +1273,12 @@ impl OsIpcReceiverSet { let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! - let ok = kernel32::GetQueuedCompletionStatus(*self.iocp, + let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), &mut nbytes, &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, &mut ov_ptr, winapi::INFINITE); - win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", *self.iocp, ok, nbytes, completion_key); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. @@ -1301,7 +1296,7 @@ impl OsIpcReceiverSet { // Find the matching receiver let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| *reader.handle == completion_key) + .find(|&(_, ref reader)| reader.handle.as_raw() == completion_key) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); index }; @@ -1313,7 +1308,7 @@ impl OsIpcReceiverSet { { let reader = &mut self.readers[reader_index]; - win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); // tell it about the completed IO op let mut closed = unsafe { @@ -1327,10 +1322,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1349,7 +1344,7 @@ impl OsIpcReceiverSet { // add an event to this effect to the result list, // and remove the reader in question from our set. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } @@ -1456,7 +1451,7 @@ impl OsIpcSharedMemory { // when finished. fn from_handle(handle: WinHandle, length: usize) -> Result { unsafe { - let address = kernel32::MapViewOfFile(*handle, + let address = kernel32::MapViewOfFile(handle.as_raw(), winapi::FILE_MAP_ALL_ACCESS, 0, 0, 0); if address.is_null() { From 8b9db47b6d93ec54927ef2dadcd4962d02f55b4d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 07:43:32 +0200 Subject: [PATCH 086/113] windows: cleanup: Simpler casting for `completion_key` Do the type casting in a different place, which makes it quite trivial compared to the somewhat icky pointer conversion needed before. --- src/platform/windows/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9599c03c5..505084d7a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1270,12 +1270,12 @@ impl OsIpcReceiverSet { let mut io_err = winapi::ERROR_SUCCESS; let reader_index = unsafe { - let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; + let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), &mut nbytes, - &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, + &mut completion_key, &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); @@ -1292,11 +1292,11 @@ impl OsIpcReceiverSet { } assert!(!ov_ptr.is_null()); - assert!(completion_key != INVALID_HANDLE_VALUE); + assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); // Find the matching receiver let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() == completion_key) + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); index }; From da172ac99441b2766f805674012a69bdf3bb635c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 13:16:12 +0200 Subject: [PATCH 087/113] windows: cleanup: Tighten scope of `nbytes` local variable --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 505084d7a..448cb5ace 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1266,10 +1266,10 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let mut nbytes: u32 = 0; let mut io_err = winapi::ERROR_SUCCESS; let reader_index = unsafe { + let mut nbytes: u32 = 0; let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! From 3627454adebe37269efa159950843db6ce5601fc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 13:18:00 +0200 Subject: [PATCH 088/113] windows: cleanup: Streamline handling of reader list in `select()` When receiving an event for a reader, instead of keeping it in the list, and only removing it at the end if it got closed, we now remove it immediately, only to add it back later when a new read is started successfully. This simplifies the code a bit; and it should be more robust too, since a reader's presence in the list is now tied more closely to it having a read in flight, i.e. being able to receive events. --- src/platform/windows/mod.rs | 90 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 448cb5ace..42d887d7e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -415,6 +415,14 @@ impl MessageReader { } } + fn take(&mut self) -> MessageReader { + // This is currently somewhat inefficient, + // because of the initialisation of things that won't be used. + // Moving the data items of `MessageReader` into an enum will fix this, + // as that way we will be able to just define a data-less `Invalid` case. + mem::replace(self, MessageReader::new(WinHandle::invalid())) + } + fn cancel_io(&mut self) { unsafe { if self.read_in_progress { @@ -1301,57 +1309,55 @@ impl OsIpcReceiverSet { index }; - let mut remove_index = None; + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); - // We need a scope here for the mutable borrow of self.readers; - // we need to (maybe) remove an element from it below. - { - let reader = &mut self.readers[reader_index]; + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + // tell it about the completed IO op + let mut closed = unsafe { + match reader.notify_completion(io_err) { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; - // tell it about the completed IO op - let mut closed = unsafe { - match reader.notify_completion(io_err) { - Ok(()) => false, + if !closed { + // Drain as many messages as we can. + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + + // Now that we are done frobbing the buffer, + // we can safely initiate the next async read operation. + closed = unsafe { + match reader.start_read() { + Ok(()) => { + // We just successfully reinstated it as an active reader -- + // so add it back to the list. + // + // Note: `take()` is a workaround for the compiler not seeing + // that we won't actually be using it anymore after this... + self.readers.push(reader.take()); + false + } Err(WinError::ChannelClosed) => true, Err(err) => return Err(err), } }; - - if !closed { - // Drain as many messages as we can. - while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - - // Now that we are done frobbing the buffer, - // we can safely initiate the next async read operation. - closed = unsafe { - match reader.start_read() { - Ok(()) => false, - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), - } - }; - } - - // If we got a "sender closed" notification -- - // either instead of new data, - // or while trying to re-initiate an async read after receiving data -- - // add an event to this effect to the result list, - // and remove the reader in question from our set. - if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); - remove_index = Some(reader_index); - } } - if let Some(index) = remove_index { - self.readers.swap_remove(index); + // If we got a "sender closed" notification -- + // either instead of new data, + // or while trying to re-initiate an async read after receiving data -- + // add an event to this effect to the result list. + if closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); } } From d070683c51a3d9b6abe8026c9f0d1da3bca32018 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 14:46:15 +0200 Subject: [PATCH 089/113] windows: Fix `unsafe` coverage in `OsIpcReceiverSet.select()` Correct IOCP event handling is critical to the soundness of the `select()` code: mixing it up might result in trying to access a buffer and `OVERLAPPED` structure that the kernel is not actually done with yet... This means that all code on the path from receiving IOCP events, to changing the status of the corresponding readers in `notify_completion()`, needs to be considered unsafe. Handling the results of `notify_completion()` and `start_read()` on the other hand isn't unsafe at all, since neither affects the individual readers' `read_in_progress` flags -- which have sole responsibility for keeping track of kernel aliasing. The worst that could happen here is trying to fetch messages from the a reader which didn't actually receive (which is begnign), and/or losing track of which readers are still open. --- src/platform/windows/mod.rs | 62 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 42d887d7e..058010abc 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1274,9 +1274,7 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let mut io_err = winapi::ERROR_SUCCESS; - - let reader_index = unsafe { + let (mut reader, result) = unsafe { let mut nbytes: u32 = 0; let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); @@ -1287,6 +1285,8 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); + + let mut io_err = winapi::ERROR_SUCCESS; if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. @@ -1303,25 +1303,25 @@ impl OsIpcReceiverSet { assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); // Find the matching receiver - let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) - .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - index - }; + let (reader_index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - // Remove the entry from the set for now -- we will re-add it later, - // if we can successfully initiate another async read operation. - let mut reader = self.readers.swap_remove(reader_index); + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - // tell it about the completed IO op - let mut closed = unsafe { - match reader.notify_completion(io_err) { - Ok(()) => false, - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), - } + // tell it about the completed IO op + let result = reader.notify_completion(io_err); + (reader, result) + }; + + let mut closed = match result { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), }; if !closed { @@ -1334,20 +1334,18 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - closed = unsafe { - match reader.start_read() { - Ok(()) => { - // We just successfully reinstated it as an active reader -- - // so add it back to the list. - // - // Note: `take()` is a workaround for the compiler not seeing - // that we won't actually be using it anymore after this... - self.readers.push(reader.take()); - false - } - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), + closed = match unsafe { reader.start_read() } { + Ok(()) => { + // We just successfully reinstated it as an active reader -- + // so add it back to the list. + // + // Note: `take()` is a workaround for the compiler not seeing + // that we won't actually be using it anymore after this... + self.readers.push(reader.take()); + false } + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), }; } From f36ffa44c7cb9a7d262fece1d0abc100b74431fe Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 08:30:31 +0200 Subject: [PATCH 090/113] windows: cleanup: Be more explicit about completion key creation --- src/platform/windows/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 058010abc..b9d161d13 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -684,9 +684,10 @@ impl MessageReader { unsafe { assert!(self.set_id.is_none()); + let completion_key = self.handle.as_raw() as winapi::ULONG_PTR; let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), iocp.as_raw(), - self.handle.as_raw() as winapi::ULONG_PTR, + completion_key, 0); if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); From e90a90fe35f8b208f8341ebd73bf31649e8d09d3 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 09:38:06 +0200 Subject: [PATCH 091/113] windows: Rename `set_id` to `entry_id` `set_id` kept misleading me, sounding like it's the ID of the set the receiver is part of, rather than the ID of the receiver within the set... --- src/platform/windows/mod.rs | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b9d161d13..1f3faa479 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -382,7 +382,7 @@ struct MessageReader { /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. /// /// `None` if this `MessageReader` is not part of any set. - set_id: Option, + entry_id: Option, } // We need to explicitly declare this, because of the raw pointer @@ -411,7 +411,7 @@ impl MessageReader { ov: Box::new(unsafe { mem::zeroed::() }), read_buf: Vec::new(), read_in_progress: false, - set_id: None, + entry_id: None, } } @@ -680,9 +680,9 @@ impl MessageReader { Ok(result) } - fn add_to_iocp(&mut self, iocp: &WinHandle, set_id: u64) -> Result<(),WinError> { + fn add_to_iocp(&mut self, iocp: &WinHandle, entry_id: u64) -> Result<(),WinError> { unsafe { - assert!(self.set_id.is_none()); + assert!(self.entry_id.is_none()); let completion_key = self.handle.as_raw() as winapi::ULONG_PTR; let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), @@ -693,7 +693,7 @@ impl MessageReader { return Err(WinError::last("CreateIoCompletionPort")); } - self.set_id = Some(set_id); + self.entry_id = Some(entry_id); // Make sure that the reader has a read in flight, // otherwise a later select() will hang. @@ -868,7 +868,7 @@ impl OsIpcReceiver { fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { let mut reader = self.reader.borrow_mut(); - assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + assert!(reader.entry_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); // This function loops, because in the case of a blocking read, we may need to // read multiple sets of bytes from the pipe to receive a complete message. @@ -1212,7 +1212,7 @@ pub struct OsIpcReceiverSet { /// /// These need to report a "closed" event on the next `select()` call. /// - /// Only the `set_id` is necessary for that. + /// Only the `entry_id` is necessary for that. closed_readers: Vec, } @@ -1240,23 +1240,23 @@ impl OsIpcReceiverSet { // consume the receiver, and take the reader out let mut reader = receiver.reader.into_inner(); - let set_id = self.incrementor.next().unwrap(); + let entry_id = self.incrementor.next().unwrap(); - match reader.add_to_iocp(&self.iocp, set_id) { + match reader.add_to_iocp(&self.iocp, entry_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); - self.closed_readers.push(set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + self.closed_readers.push(entry_id); } Err(err) => return Err(err), }; - Ok(set_id) + Ok(entry_id) } pub fn select(&mut self) -> Result,WinError> { @@ -1270,7 +1270,7 @@ impl OsIpcReceiverSet { // from channels that got closed before being added to the set, // and thus received "closed" notifications while being added. self.closed_readers.drain(..) - .for_each(|set_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(set_id))); + .for_each(|entry_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(entry_id))); // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. @@ -1328,10 +1328,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.entry_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1355,8 +1355,8 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.entry_id.unwrap())); } } From 55afd64a4dde88a65d175cf34ace65beea314e1a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 17:52:17 +0200 Subject: [PATCH 092/113] windows: cleanup: More idiomatic handling of `io_err` Initialise using exhaustive conditionals, avoiding mutation. --- src/platform/windows/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1f3faa479..ec88d9f2e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -592,7 +592,6 @@ impl MessageReader { // Get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; - let mut err = winapi::ERROR_SUCCESS; let block = match blocking_mode { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, @@ -601,8 +600,8 @@ impl MessageReader { self.ov.deref_mut(), &mut nbytes, block); - if ok == winapi::FALSE { - err = GetLastError(); + let io_err = if ok == winapi::FALSE { + let err = GetLastError(); if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Async read hasn't completed yet. // Inform the caller, while keeping the read in flight. @@ -610,11 +609,14 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - } + err + } else { + winapi::ERROR_SUCCESS + }; // Notify that the read completed, which will update the // read pointers - self.notify_completion(err) + self.notify_completion(io_err) } } @@ -1286,9 +1288,7 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - - let mut io_err = winapi::ERROR_SUCCESS; - if ok == winapi::FALSE { + let io_err = if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and @@ -1297,8 +1297,10 @@ impl OsIpcReceiverSet { return Err(WinError::last("GetQueuedCompletionStatus")); } - io_err = GetLastError(); - } + GetLastError() + } else { + winapi::ERROR_SUCCESS + }; assert!(!ov_ptr.is_null()); assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); From 5062c2678bff1e763a229cf519e2776172730264 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 18:37:58 +0200 Subject: [PATCH 093/113] windows: cleanup: Pass status as a proper `Result<>` Rather than passing a naked error code (with a magic value denoting success), use a standard `Result<>` enum. --- src/platform/windows/mod.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ec88d9f2e..152da9054 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -535,7 +535,7 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { + unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -546,7 +546,7 @@ impl MessageReader { self.read_in_progress = false; // Remote end closed the channel. - if err == winapi::ERROR_BROKEN_PIPE { + if io_result == Err(winapi::ERROR_BROKEN_PIPE) { return Err(WinError::ChannelClosed); } @@ -555,8 +555,10 @@ impl MessageReader { assert!(offset == 0); - if err != winapi::ERROR_SUCCESS { - // This should never happen + if let Err(err) = io_result { + // Other errors shouldn't come up here... + // If they do, we don't really understand the situation -- + // so we can't handle this gracefully. panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); } @@ -600,7 +602,7 @@ impl MessageReader { self.ov.deref_mut(), &mut nbytes, block); - let io_err = if ok == winapi::FALSE { + let io_result = if ok == winapi::FALSE { let err = GetLastError(); if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Async read hasn't completed yet. @@ -609,14 +611,14 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - err + Err(err) } else { - winapi::ERROR_SUCCESS + Ok(()) }; // Notify that the read completed, which will update the // read pointers - self.notify_completion(io_err) + self.notify_completion(io_result) } } @@ -1288,18 +1290,18 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - let io_err = if ok == winapi::FALSE { + let io_result = if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and - // we want to hand io_err to notify_completion below. + // we want to hand the error to notify_completion below. if ov_ptr.is_null() { return Err(WinError::last("GetQueuedCompletionStatus")); } - GetLastError() + Err(GetLastError()) } else { - winapi::ERROR_SUCCESS + Ok(()) }; assert!(!ov_ptr.is_null()); @@ -1317,7 +1319,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); // tell it about the completed IO op - let result = reader.notify_completion(io_err); + let result = reader.notify_completion(io_result); (reader, result) }; From 6831ca29a45fc9197235244e81d2e66f774c1f8b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 18:47:12 +0200 Subject: [PATCH 094/113] windows: cleanup: `match` on errors in `notify_completion()` Use exhaustive match in one place rather than scattered conditionals. This should be much easier to follow. --- src/platform/windows/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 152da9054..be05fc5ae 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -545,9 +545,18 @@ impl MessageReader { // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; - // Remote end closed the channel. - if io_result == Err(winapi::ERROR_BROKEN_PIPE) { - return Err(WinError::ChannelClosed); + match io_result { + Ok(()) => {} + Err(winapi::ERROR_BROKEN_PIPE) => { + // Remote end closed the channel. + return Err(WinError::ChannelClosed); + } + Err(err) => { + // Other errors shouldn't come up here... + // If they do, we don't really understand the situation -- + // so we can't handle this gracefully. + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } } let nbytes = self.ov.InternalHigh as u32; @@ -555,13 +564,6 @@ impl MessageReader { assert!(offset == 0); - if let Err(err) = io_result { - // Other errors shouldn't come up here... - // If they do, we don't really understand the situation -- - // so we can't handle this gracefully. - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } - let new_size = self.read_buf.len() + nbytes as usize; win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); From 2b6c93e285dd23a366039b5acceef64ed91c7268 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 19:51:00 +0200 Subject: [PATCH 095/113] windows: More defensive `read_in_progress` handling Set the flag before issuing the system call, and only reset it later if it turns out no read was actually started. This way it's less likely that setting the flag gets ommited by mistake, which could have catastrophic effects, since setting this is crucial for tracking the kernel aliasing of `ov` and `read_buf`. --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index be05fc5ae..584393baa 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -466,6 +466,7 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset + self.read_in_progress = true; *self.ov.deref_mut() = mem::zeroed(); let mut bytes_read: u32 = 0; let ok = { @@ -509,14 +510,15 @@ impl MessageReader { // special handling for sync-completed operations. Ok(()) | Err(winapi::ERROR_IO_PENDING) => { - self.read_in_progress = true; Ok(()) }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.read_in_progress = false; Err(WinError::ChannelClosed) }, Err(err) => { + self.read_in_progress = false; Err(WinError::from_system(err, "ReadFile")) }, } From 48a33db0b0f2d6416058bfdd7d29e35302cb9de2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 20:14:09 +0200 Subject: [PATCH 096/113] windows: Make `MessageReader.ov` optional This field is only meaningful while an async read is in progress with the kernel; and we never reuse it between reads. Making this more explicit and robust by only giving it a value while it's in use. --- src/platform/windows/mod.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 584393baa..356201ac1 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -362,7 +362,7 @@ struct MessageReader { /// /// (Unfortunately, there is no way to express this condition in the type system, /// without some fundamental change to how we handle these fields...) - ov: Box, + ov: Option>, /// A read buffer for any pending reads. /// @@ -408,7 +408,7 @@ impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { handle: handle, - ov: Box::new(unsafe { mem::zeroed::() }), + ov: None, read_buf: Vec::new(), read_in_progress: false, entry_id: None, @@ -426,7 +426,7 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.read_in_progress { - kernel32::CancelIoEx(self.handle.as_raw(), self.ov.deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); self.read_in_progress = false; } } @@ -467,7 +467,7 @@ impl MessageReader { // issue the read to the buffer, at the current length offset self.read_in_progress = true; - *self.ov.deref_mut() = mem::zeroed(); + self.ov = Some(Box::new(mem::zeroed())); let mut bytes_read: u32 = 0; let ok = { let remaining_buf = &mut self.read_buf[buf_len..]; @@ -475,7 +475,7 @@ impl MessageReader { remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, - self.ov.deref_mut()) + self.ov.as_mut().unwrap().deref_mut()) }; // Reset the vector to only expose the already filled part. @@ -515,10 +515,12 @@ impl MessageReader { Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); self.read_in_progress = false; + self.ov = None; Err(WinError::ChannelClosed) }, Err(err) => { self.read_in_progress = false; + self.ov = None; Err(WinError::from_system(err, "ReadFile")) }, } @@ -546,6 +548,7 @@ impl MessageReader { // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; + let ov = self.ov.take().unwrap(); match io_result { Ok(()) => {} @@ -561,8 +564,8 @@ impl MessageReader { } } - let nbytes = self.ov.InternalHigh as u32; - let offset = self.ov.Offset; + let nbytes = ov.InternalHigh as u32; + let offset = ov.Offset; assert!(offset == 0); @@ -603,7 +606,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.deref_mut(), + self.ov.as_mut().unwrap().deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { From b9c300492d13c2fefc7574b3cc5f7c282a6b049c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 23:15:48 +0200 Subject: [PATCH 097/113] windows: Drop explicit `read_in_progress` flag Since the `ov` field is now only set when we have a read in progress, and thus effectively serves as an indicator of that state, the explicit flag became redundant: we can just check the presence of `ov` instead. (And since a check is performed implicitly when unwrapping the `ov` value, some of the explicit asserts on `read_in_progress` can be dropped entirely.) --- src/platform/windows/mod.rs | 48 ++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 356201ac1..b68a49f44 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -346,6 +346,11 @@ struct MessageReader { /// which is registered in the kernel during an async read -- /// to remain stable even when the enclosing structure is passed around. /// + /// Note: Since this field only has a value + /// when an async read operation is in progress + /// (i.e. has been issued to the system, and not completed yet), + /// this also serves as an indicator of the latter. + /// /// WARNING: As the kernel holds a mutable alias of this structure /// while an async read is in progress, /// it is crucial that this value is never accessed in user space @@ -357,8 +362,9 @@ struct MessageReader { /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! /// The only way to avoid undefined behaviour - /// is to always make sure the `read_in_progress` indicator is not set, - /// before performing any access to this value. + /// is to never access this field from user space, + /// except initialising it before issuing the async read to the kernel, + /// and taking out the result value after the kernel signals completion. /// /// (Unfortunately, there is no way to express this condition in the type system, /// without some fundamental change to how we handle these fields...) @@ -366,17 +372,12 @@ struct MessageReader { /// A read buffer for any pending reads. /// - /// WARNING: This has the same safety problem as `ov` above. + /// WARNING: This has the same aliasing problem as `ov` above, + /// meaning it never should be accessed from user space + /// while an async read operation is in progress with the kernel, + /// i.e. while the `ov` field has a value. read_buf: Vec, - /// Indicates whether the kernel currently has an async read in flight for this port. - /// - /// WARNING: Rather than just managing our internal state, - /// this flag plays a critical role in keeping track of kernel aliasing - /// of the `ov` and `read_buf` fields, as explained in the comment for `ov'. - /// Thus it is crucial that we always set this correctly! - read_in_progress: bool, - /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -410,7 +411,6 @@ impl MessageReader { handle: handle, ov: None, read_buf: Vec::new(), - read_in_progress: false, entry_id: None, } } @@ -425,9 +425,8 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { - if self.read_in_progress { + if self.ov.is_some() { kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); - self.read_in_progress = false; } } } @@ -447,9 +446,10 @@ impl MessageReader { /// i.e. all code on the path from invoking this method, /// up to receiving the completion event, is unsafe. /// - /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// (See documentation of the `ov` and `read_buf` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - if self.read_in_progress { + // Nothing needs to be done if an async read operation is already in progress. + if self.ov.is_some() { return Ok(()); } @@ -466,7 +466,6 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset - self.read_in_progress = true; self.ov = Some(Box::new(mem::zeroed())); let mut bytes_read: u32 = 0; let ok = { @@ -514,12 +513,10 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.read_in_progress = false; self.ov = None; Err(WinError::ChannelClosed) }, Err(err) => { - self.read_in_progress = false; self.ov = None; Err(WinError::from_system(err, "ReadFile")) }, @@ -533,21 +530,18 @@ impl MessageReader { /// would have catastrophic effects, /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! /// - /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// (See documentation of the `ov` and `read_buf` fields.) /// /// Also, this method relies on `ov` and `read_buf` actually having valid data, /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { - assert!(self.read_in_progress); - win32_trace!("[$ {:?}] notify_completion", self.handle); // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `ov` and `read_buf` fields.) - self.read_in_progress = false; let ov = self.ov.take().unwrap(); match io_result { @@ -597,8 +591,6 @@ impl MessageReader { /// i.e. we are still in unsafe mode in that case! fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { unsafe { - assert!(self.read_in_progress); - // Get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; let block = match blocking_mode { @@ -632,7 +624,7 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! - if self.read_in_progress { + if self.ov.is_some() { return Ok(None); } @@ -869,7 +861,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); - assert!(!reader.read_in_progress); + assert!(reader.ov.is_none()); OsIpcReceiver::from_handle(reader.handle.take()) } @@ -910,7 +902,7 @@ impl OsIpcReceiver { // nothing prevents code that isn't marked as `unsafe` // from performing invalid reads or writes to these fields! // - // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) + // (See documentation of `ov` and `read_buf` fields.) try!(reader.fetch_async_result(blocking_mode)); } From 05fd68de17637b8f4ce3070a59122c3c63084e79 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 7 May 2018 18:02:18 +0200 Subject: [PATCH 098/113] windows: Avoid leaking unsafety outside `unsafe` blocks Introduce an `AliasedCell` wrapper type for the fields that get aliased by the kernel during async read operations, making sure that these values can only be accessed through special unsafe methods, i.e. only inside `unsafe` blocks, even if the wrappers get passed around through safe code. This makes invoking `MessageReader.start_read()` safe; and consequently removes all unsafety from `OsIpcReceiver`. (`OsIpcReceiverSet.select()` retains some unsafe code though, since it does raw I/O itself... This can only be fixed by factoring out that code.) --- src/platform/windows/aliased_cell.rs | 363 +++++++++++++++++++++++++++ src/platform/windows/mod.rs | 319 ++++++++++++----------- 2 files changed, 521 insertions(+), 161 deletions(-) create mode 100644 src/platform/windows/aliased_cell.rs diff --git a/src/platform/windows/aliased_cell.rs b/src/platform/windows/aliased_cell.rs new file mode 100644 index 000000000..6399c40fc --- /dev/null +++ b/src/platform/windows/aliased_cell.rs @@ -0,0 +1,363 @@ +// Copyright 2018 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem::{self, ManuallyDrop}; +use std::thread; + +/// Dummy type that panics when its destructor is invoked. +#[derive(Debug)] +struct DropBomb(); + +impl Drop for DropBomb { + fn drop(&mut self) { + let message = "Trying to drop an AliasedCell, which may still have aliases outstanding."; + if thread::panicking() { + eprintln!("{}", message); + } else { + panic!(message); + } + } +} + +/// A wrapper for unsafely aliased memory locations. +/// +/// `AliasedCell' makes sure that its inner value +/// is completely inaccessible from safe code. +/// Once an `AliasedCell` has been constructed from some value, +/// until the value is moved out again with the (unsafe) `into_inner()` method, +/// the only way to access the wrapped value +/// is through the unsafe `alias_mut()` method. +/// +/// This is useful for FFI calls that take raw pointers as input, +/// and hold on to them even after returning control to the caller. +/// Since Rust's type system is not aware of such aliases, +/// it cannot provide the usual guarantees about validity of pointers +/// and exclusiveness of mutable pointers. +/// This means that any code that has access to the memory in question +/// is inherently unsafe as long as such untracked aliases exist. +/// Putting the value in an `AliasedCell` before the FFI call, +/// and only taking it out again +/// once the caller has ensured that all aliases have been dropped +/// (most likely through another FFI call), +/// makes certain that any such unsafe access to the aliased value +/// can only happen from code marked as `unsafe`. +/// +/// An `AliasedCell` should never be simply dropped in normal use. +/// Rather, it should always be freed explicitly with `into_inner()`, +/// signalling that there are no outstanding aliases anymore. +/// When an `AliasedCell` is dropped unexpectedly, +/// we have to assume that it likely still has untracked aliases, +/// and thus freeing the memory would probably be unsound. +/// Therefore, the `drop()` implementation of `AliasedCell` +/// leaks the inner value instead of freeing it; +/// and throws a panic. +/// Leaking the memory, while undesirable in general, +/// keeps the memory accessible to any outstanding aliases. +/// This is the only way to retain soundness during unwinding, +/// or when the panic gets catched. +/// +/// Note that making FFI access through untracked aliases +/// requires the value to have a stable memory location -- +/// typically by living on the heap rather than on the stack. +/// If the value isn't already in a heap-allocated container +/// such as `Box<>`, `Vec<>`, or `String`, +/// it is the caller's responsibility to wrap it in a `Box<>` explicitly. +/// `AliasedCell` itself cannot ensure that the address remains stable +/// when the `AliasedCell` gets moved around. +#[derive(Debug)] +pub struct AliasedCell { + value: ManuallyDrop, + drop_bomb: DropBomb, +} + +impl AliasedCell { + /// Wrap the provided value in an `AliasedCell`, making it inaccessible from safe code. + pub fn new(value: T) -> AliasedCell { + AliasedCell { + value: ManuallyDrop::new(value), + drop_bomb: DropBomb(), + } + } + + /// Get a pointer to the inner value. + /// + /// Note that this yields a regular reference. + /// To actually get an untracked alias, + /// it needs to be cast or coerced into a raw pointer. + /// This usually happens implicitly though + /// when calling an FFI function (or any other function) + /// taking a raw pointer as argument. + /// + /// `alias_mut()` can be called any number of times: + /// the wrapper doesn't keep track of the number of outstanding aliases -- + /// the caller is responsible for making sure that no aliases are left + /// before invoking `into_inner()`. + /// If you need to track the number of aliases, + /// wrap the inner value in an `Rc<>` or `Arc` -- + /// this way, the reference count will also be inaccessible from safe code. + pub unsafe fn alias_mut(&mut self) -> &mut T { + &mut self.value + } + + /// Move out the wrapped value, making it accessible from safe code again. + pub unsafe fn into_inner(self) -> T { + mem::forget(self.drop_bomb); + ManuallyDrop::into_inner(self.value) + } +} + +/// Some basic tests. +/// +/// Note: These mostly just check that various expected usage scenarios +/// can be compiled and basically work. +/// We can't verify though that the usage is actually sound; +/// nor do we check whether invalid usage is indeed prevented by the compiler... +/// +/// (The latter could probably be remedied though +/// with some sort of compile-fail tests.) +#[cfg(test)] +mod tests { + use super::AliasedCell; + + unsafe fn mutate_value(addr: *mut [i32; 4]) { + let value = addr.as_mut().unwrap(); + value[1] += value[3]; + } + + struct Mutator { + addr: *mut [i32; 4], + ascended: bool, + } + + impl Mutator { + unsafe fn new(addr: *mut [i32; 4]) -> Mutator { + Mutator { + addr: addr, + ascended: false, + } + } + + fn ascend(&mut self) { + self.ascended = true; + } + + unsafe fn mutate(&mut self) { + let value = self.addr.as_mut().unwrap(); + if self.ascended { + value[3] += value[2]; + } else { + value[1] += value[3]; + } + } + } + + #[test] + fn noop_roundtrip() { + let value = [1, 3, 3, 7]; + let cell = AliasedCell::new(Box::new(value)); + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 3, 3, 7]); + } + + #[test] + fn unused_alias() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + cell.alias_mut().as_mut(); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 3, 3, 7]); + } + + #[test] + fn mutate() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + /// Verify that we can take multiple aliases. + #[test] + fn mutate_twice() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Verify that we can do basic safe manipulations between unsafe blocks. + #[test] + fn moves() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let mut cell2 = cell; + unsafe { + mutate_value(cell2.alias_mut().as_mut()); + } + let cell3 = cell2; + let new_value = unsafe { + *cell3.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Verify that alias can be used at a later point. + #[test] + fn mutate_deferred() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + #[test] + fn mutate_deferred_twice() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Further safe manipulations. + #[test] + fn deferred_moves() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + let cell2 = cell; + let mut mutator2 = mutator; + unsafe { + mutator2.mutate(); + } + let cell3 = cell2; + let mutator3 = mutator2; + let new_value = unsafe { + drop(mutator3); + *cell3.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + /// Non-trivial safe manipulation. + #[test] + fn safe_frobbing() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + mutator.ascend(); + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 10]); + } + + /// Verify that two aliases can exist simultaneously. + #[test] + fn two_mutators() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator1 = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator1.mutate(); + } + let mut mutator2 = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator2.mutate(); + } + let new_value = unsafe { + drop(mutator1); + drop(mutator2); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + #[test] + #[should_panic(expected = "Trying to drop an AliasedCell, which may still have aliases outstanding.")] + fn invalid_drop() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + let mut mutator = Mutator::new(cell.alias_mut().as_mut()); + mutator.mutate(); + drop(cell); + } + } + + /// Verify that we skip the panic-on-drop while unwinding from another panic. + #[test] + #[should_panic(expected = "bye!")] + fn panic() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + let mut mutator = Mutator::new(cell.alias_mut().as_mut()); + mutator.mutate(); + panic!("bye!"); + } + } +} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b68a49f44..e00ff61b8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -27,6 +27,9 @@ use uuid::Uuid; use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; use winapi; +mod aliased_cell; +use self::aliased_cell::AliasedCell; + lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); @@ -361,23 +364,30 @@ struct MessageReader { /// Since Rust's type system is not aware of the kernel alias, /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! - /// The only way to avoid undefined behaviour - /// is to never access this field from user space, - /// except initialising it before issuing the async read to the kernel, - /// and taking out the result value after the kernel signals completion. - /// - /// (Unfortunately, there is no way to express this condition in the type system, - /// without some fundamental change to how we handle these fields...) - ov: Option>, + /// We thus wrap it in an `AliasedCell`, + /// making sure the value is only accessible from code marked `unsafe`; + /// and only move it out when the kernel signals that the async read is done. + ov: Option>>, - /// A read buffer for any pending reads. + /// Buffer for outstanding data, that has been received but not yet processed. /// - /// WARNING: This has the same aliasing problem as `ov` above, - /// meaning it never should be accessed from user space - /// while an async read operation is in progress with the kernel, - /// i.e. while the `ov` field has a value. + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// the receive buffer is aliased by the kernel just like `ov` above; + /// so we need to temporarily move it into an `AliasedCell` too, + /// thus making it inaccessible from safe code -- + /// see `aliased_buf` below. + /// We only move it back once the kernel signals completion of the async read. read_buf: Vec, + /// Buffer for the kernel to store the results of async read operations. + /// + /// WARNING: This has the same aliasing problem as `ov` above -- + /// i.e. it should never be accessed from user space + /// while an async read operation is in progress. + aliased_buf: Option>>, + /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -411,6 +421,7 @@ impl MessageReader { handle: handle, ov: None, read_buf: Vec::new(), + aliased_buf: None, entry_id: None, } } @@ -426,28 +437,23 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.ov.is_some() { - kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), + self.ov.as_mut().unwrap().alias_mut().deref_mut()); + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); } } } /// Kick off an asynchronous read. /// - /// Note: This is *highly* unsafe, since upon successful return, - /// the `ov` and `read_buf` fields will be left mutably aliased by the kernel - /// (until we receive an event signalling completion of the async read) -- - /// and Rust's type system doesn't know about these aliases! - /// - /// This means that after invoking this method, - /// up to the point where we receive the completion notification, - /// nothing is allowed to access the `ov` and `read_buf` fields; - /// but the compiler cannot guarantee this for us. - /// It is our responsibility to make sure of it -- - /// i.e. all code on the path from invoking this method, - /// up to receiving the completion event, is unsafe. - /// + /// When an async read is started successfully, + /// the receive buffer is moved out of `read_buf` + /// into the `AliasedCell<>` in `aliased_buf`, + /// thus making it inaccessible from safe code; + /// it will only be moved back in `notify_completion()`. /// (See documentation of the `ov` and `read_buf` fields.) - unsafe fn start_read(&mut self) -> Result<(),WinError> { + fn start_read(&mut self) -> Result<(),WinError> { // Nothing needs to be done if an async read operation is already in progress. if self.ov.is_some() { return Ok(()); @@ -459,80 +465,89 @@ impl MessageReader { self.read_buf.reserve(PIPE_BUFFER_SIZE); } - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = self.read_buf.len(); - let buf_cap = self.read_buf.capacity(); - self.read_buf.set_len(buf_cap); - - // issue the read to the buffer, at the current length offset - self.ov = Some(Box::new(mem::zeroed())); - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(self.handle.as_raw(), - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - self.ov.as_mut().unwrap().deref_mut()) - }; - - // Reset the vector to only expose the already filled part. - // - // This means that the async read - // will actually fill memory beyond the exposed part of the vector. - // While this use of a vector is officially sanctioned for such cases, - // it still feel rather icky to me... - // - // On the other hand, this way we make sure - // the buffer never appears to have more valid data - // than what is actually present, - // which could pose a potential danger in its own right. - // Also, it avoids the need to keep a separate state variable -- - // which would bear some risk of getting out of sync. - self.read_buf.set_len(buf_len); - - let result = if ok == winapi::FALSE { - Err(GetLastError()) - } else { - Ok(()) - }; + unsafe { + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. + let buf_len = self.read_buf.len(); + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); + + // issue the read to the buffer, at the current length offset + self.ov = Some(AliasedCell::new(Box::new(mem::zeroed()))); + self.aliased_buf = Some(AliasedCell::new(mem::replace(&mut self.read_buf, vec![]))); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.aliased_buf.as_mut().unwrap().alias_mut()[buf_len..]; + kernel32::ReadFile(self.handle.as_raw(), + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.as_mut().unwrap().alias_mut().deref_mut()) + }; - match result { - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - Ok(()) | - Err(winapi::ERROR_IO_PENDING) => { + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.aliased_buf.as_mut().unwrap().alias_mut().set_len(buf_len); + + let result = if ok == winapi::FALSE { + Err(GetLastError()) + } else { Ok(()) - }, - Err(winapi::ERROR_BROKEN_PIPE) => { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.ov = None; - Err(WinError::ChannelClosed) - }, - Err(err) => { - self.ov = None; - Err(WinError::from_system(err, "ReadFile")) - }, + }; + + match result { + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + Ok(()) | + Err(winapi::ERROR_IO_PENDING) => { + Ok(()) + }, + Err(winapi::ERROR_BROKEN_PIPE) => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + Err(WinError::ChannelClosed) + }, + Err(err) => { + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + Err(WinError::from_system(err, "ReadFile")) + }, + } } } /// Called when we receive an IO Completion Packet for this handle. /// - /// Unsafe, since calling this in error + /// During its course, this method moves `aliased_buf` back into `read_buf`, + /// thus making it accessible from normal code again; + /// so `get_message()` can extract the received messages from the buffer. + /// + /// Invoking this is unsafe, since calling it in error /// while an async read is actually still in progress in the kernel /// would have catastrophic effects, - /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! + /// as `ov` and `aliased_buf` are still mutably aliased by the kernel in that case! /// /// (See documentation of the `ov` and `read_buf` fields.) /// - /// Also, this method relies on `ov` and `read_buf` actually having valid data, + /// Also, this method relies on `ov` and `aliased_buf` actually having valid data, /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. @@ -541,8 +556,9 @@ impl MessageReader { // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. - // (And it's safe again to access the `ov` and `read_buf` fields.) - let ov = self.ov.take().unwrap(); + // (And it's safe again to access the `ov` and `aliased_buf` fields.) + let ov = self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); match io_result { Ok(()) => {} @@ -582,13 +598,12 @@ impl MessageReader { /// /// In non-blocking mode, this may return with `WinError:NoData`, /// while the async operation remains in flight. - /// - /// Note: Upon successful return, - /// the internal `ov` and `read_buf` fields - /// won't be aliased by the kernel anymore. - /// When getting `NoData` however, - /// access to these fields remains invalid, - /// i.e. we are still in unsafe mode in that case! + /// The read buffer remains unavailable in that case, + /// since it's still aliased by the kernel. + /// (And there is nothing new to pick up anyway.) + /// It will only become available again + /// when `fetch_async_result()` returns sucessfully upon retry. + /// (Or the async read is aborted with `cancel_io()`.) fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { unsafe { // Get the overlapped result, blocking if we need to. @@ -598,7 +613,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.as_mut().unwrap().deref_mut(), + self.ov.as_mut().unwrap().alias_mut().deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { @@ -695,17 +710,13 @@ impl MessageReader { if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); } + } - self.entry_id = Some(entry_id); + self.entry_id = Some(entry_id); - // Make sure that the reader has a read in flight, - // otherwise a later select() will hang. - // - // Note: Just like in `OsIpcReceiver.receive_message()` below, - // this makes us vulnerable to invalid `ov` and `read_buf` modification - // from code not marked as unsafe... - self.start_read() - } + // Make sure that the reader has a read in flight, + // otherwise a later select() will hang. + self.start_read() } /// Specialized read for out-of-band data ports. @@ -721,27 +732,25 @@ impl MessageReader { while self.read_buf.len() < size { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. - unsafe { - match self.start_read() { - Err(WinError::ChannelClosed) => { - // If the helper channel closes unexpectedly - // (i.e. before supplying the expected amount of data), - // don't report that as a "sender closed" condition on the main channel: - // rather, fail with the actual raw error code. - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } - Err(err) => return Err(err), - Ok(()) => {} - }; - match self.fetch_async_result(BlockingMode::Blocking) { - Err(WinError::ChannelClosed) => { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) - } - // In blocking mode, `fetch_async_result()` has no other expected failure modes. - Err(_) => unreachable!(), - Ok(()) => {} - }; - } + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the helper channel closes unexpectedly + // (i.e. before supplying the expected amount of data), + // don't report that as a "sender closed" condition on the main channel: + // rather, fail with the actual raw error code. + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); + } + Err(err) => return Err(err), + Ok(()) => {} + }; + match self.fetch_async_result(BlockingMode::Blocking) { + Err(WinError::ChannelClosed) => { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) + } + // In blocking mode, `fetch_async_result()` has no other expected failure modes. + Err(_) => unreachable!(), + Ok(()) => {} + }; } Ok(mem::replace(&mut self.read_buf, vec![])) @@ -882,29 +891,16 @@ impl OsIpcReceiver { return Ok((data, channels, shmems)); } - unsafe { - // Then, issue a read if we don't have one already in flight. - // We must not issue a read if we have complete unconsumed - // messages, because getting a message modifies the read_buf. - try!(reader.start_read()); - - // May return `WinError::NoData` in non-blocking mode. - // - // The async read remains in flight in that case; - // and another attempt at getting a result - // can be done the next time we are called. - // - // Note: This leaks unsafety outside the `unsafe` block, - // since the method returns while an async read is still in progress; - // meaning the kernel still holds a mutable alias - // of the read buffer and `OVERLAPPED` structure - // that the Rust type system doesn't know about -- - // nothing prevents code that isn't marked as `unsafe` - // from performing invalid reads or writes to these fields! - // - // (See documentation of `ov` and `read_buf` fields.) - try!(reader.fetch_async_result(blocking_mode)); - } + // Then, issue a read if we don't have one already in flight. + try!(reader.start_read()); + + // Attempt to complete the read. + // + // May return `WinError::NoData` in non-blocking mode. + // The async read remains in flight in that case; + // and another attempt at getting a result + // can be done the next time we are called. + try!(reader.fetch_async_result(blocking_mode)); // If we're not blocking, pretend that we are blocking, since we got part of // a message already. Keep reading until we get a complete message. @@ -933,14 +929,12 @@ impl OsIpcReceiver { let handle = &reader_borrow.handle; // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... - let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.deref_mut()); + let mut ov = AliasedCell::new(Box::new(mem::zeroed::())); + let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.alias_mut().deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); - let err = GetLastError(); - - match err { + let result = match GetLastError() { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle.as_raw()); @@ -960,7 +954,7 @@ impl OsIpcReceiver { // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.alias_mut().deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -972,7 +966,10 @@ impl OsIpcReceiver { win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); Err(WinError::last("ConnectNamedPipe")) }, - } + }; + + ov.into_inner(); + result } } @@ -1338,7 +1335,7 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - closed = match unsafe { reader.start_read() } { + closed = match reader.start_read() { Ok(()) => { // We just successfully reinstated it as an active reader -- // so add it back to the list. From b1eb0c9f8af8cf72dd67051302b836df482e503d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 16:53:02 +0200 Subject: [PATCH 099/113] windows: Clarify comment about `start_read()` for receivers in sets --- src/platform/windows/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e00ff61b8..ae5f85150 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -714,8 +714,9 @@ impl MessageReader { self.entry_id = Some(entry_id); - // Make sure that the reader has a read in flight, - // otherwise a later select() will hang. + // The readers in the IOCP need to have async reads in flight, + // so they can actually get completion events -- + // otherwise, a subsequent `select()` call would just hang indefinitely. self.start_read() } From eccd9d4c25cc9a9e811666edaa43963ade4043b2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 14 May 2018 20:31:24 +0200 Subject: [PATCH 100/113] windows: Put `ov` and `buf` in the same `AliasedCell<>` Put both of the fields aliased by the kernel during an async read operation together in a common `AliasedCell<>`. This increases robustness, and further tightens unsafe boundaries, by making sure the two fields stay consistent with each other. When they are wrapped in `AliasedCell<>` separately, safe code is prevented from giving any of them invalid values individually -- but they could still get out of sync with each other, if some code moves just one of them and not the other. Consistency between these fields however is crucial for correctness as well as soundness. --- src/platform/windows/mod.rs | 128 +++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ae5f85150..80639f763 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -333,6 +333,25 @@ impl WinHandle { } } +/// Helper struct for all data being aliased by the kernel during async reads. +#[derive(Debug)] +struct AsyncData { + /// Meta-data for this async read operation, filled by the kernel. + /// + /// This must be on the heap, in order for its memory location -- + /// which is registered in the kernel during an async read -- + /// to remain stable even when the enclosing structure is passed around. + ov: Box, + + /// Buffer for the kernel to store the results of the async read operation. + /// + /// The vector provided here needs to have some allocated yet unused space, + /// i.e. `capacity()` needs to be larger than `len()`. + /// If part of the vector is already filled, that is left in place; + /// the new data will only be written to the unused space. + buf: Vec, +} + /// Main object keeping track of a receive handle and its associated state. /// /// Implements blocking/nonblocking reads of messages from the handle. @@ -341,52 +360,39 @@ struct MessageReader { /// The pipe read handle. handle: WinHandle, - /// The OVERLAPPED struct for async IO on this receiver. - /// - /// We'll only ever have one in flight. + /// Buffer for outstanding data, that has been received but not yet processed. /// - /// This must be on the heap, in order for its memory location -- - /// which is registered in the kernel during an async read -- - /// to remain stable even when the enclosing structure is passed around. + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// the receive buffer is aliased by the kernel; + /// so we need to temporarily move it into an `AliasedCell`, + /// thus making it inaccessible from safe code -- + /// see `async` below. + /// We only move it back once the kernel signals completion of the async read. + read_buf: Vec, + + /// Data used by the kernel during an async read operation. /// /// Note: Since this field only has a value /// when an async read operation is in progress /// (i.e. has been issued to the system, and not completed yet), /// this also serves as an indicator of the latter. /// - /// WARNING: As the kernel holds a mutable alias of this structure + /// WARNING: As the kernel holds mutable aliases of this data /// while an async read is in progress, - /// it is crucial that this value is never accessed in user space + /// it is crucial that it is never accessed in user space /// from the moment we issue an async read in `start_read()`, /// until the moment we process the event /// signalling completion of the async read in `notify_completion()`. /// - /// Since Rust's type system is not aware of the kernel alias, + /// Since Rust's type system is not aware of the kernel aliases, /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! /// We thus wrap it in an `AliasedCell`, - /// making sure the value is only accessible from code marked `unsafe`; + /// making sure the data is only accessible from code marked `unsafe`; /// and only move it out when the kernel signals that the async read is done. - ov: Option>>, - - /// Buffer for outstanding data, that has been received but not yet processed. - /// - /// Note: this is only set while no async read operation - /// is currently in progress with the kernel. - /// When an async read is in progress, - /// the receive buffer is aliased by the kernel just like `ov` above; - /// so we need to temporarily move it into an `AliasedCell` too, - /// thus making it inaccessible from safe code -- - /// see `aliased_buf` below. - /// We only move it back once the kernel signals completion of the async read. - read_buf: Vec, - - /// Buffer for the kernel to store the results of async read operations. - /// - /// WARNING: This has the same aliasing problem as `ov` above -- - /// i.e. it should never be accessed from user space - /// while an async read operation is in progress. - aliased_buf: Option>>, + async: Option>, /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// @@ -401,7 +407,7 @@ struct MessageReader { // // Note: the `Send` claim is only really fulfilled // as long as nothing can ever alias the aforementioned raw pointer. -// As explained in the documentation of the `ov` field, +// As explained in the documentation of the `async` field, // this is a tricky condition (because of kernel aliasing), // which we however need to uphold regardless of the `Send` property -- // so claiming `Send` should not introduce any additional issues. @@ -419,9 +425,8 @@ impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { handle: handle, - ov: None, read_buf: Vec::new(), - aliased_buf: None, + async: None, entry_id: None, } } @@ -436,11 +441,11 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { - if self.ov.is_some() { + if self.async.is_some() { kernel32::CancelIoEx(self.handle.as_raw(), - self.ov.as_mut().unwrap().alias_mut().deref_mut()); - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let async_data = self.async.take().unwrap().into_inner(); + self.read_buf = async_data.buf; } } } @@ -449,13 +454,13 @@ impl MessageReader { /// /// When an async read is started successfully, /// the receive buffer is moved out of `read_buf` - /// into the `AliasedCell<>` in `aliased_buf`, + /// into the `AliasedCell<>` in `async`, /// thus making it inaccessible from safe code; /// it will only be moved back in `notify_completion()`. - /// (See documentation of the `ov` and `read_buf` fields.) + /// (See documentation of the `read_buf` and `async` fields.) fn start_read(&mut self) -> Result<(),WinError> { // Nothing needs to be done if an async read operation is already in progress. - if self.ov.is_some() { + if self.async.is_some() { return Ok(()); } @@ -473,16 +478,19 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset - self.ov = Some(AliasedCell::new(Box::new(mem::zeroed()))); - self.aliased_buf = Some(AliasedCell::new(mem::replace(&mut self.read_buf, vec![]))); + self.async = Some(AliasedCell::new(AsyncData { + ov: Box::new(mem::zeroed()), + buf: mem::replace(&mut self.read_buf, vec![]), + })); let mut bytes_read: u32 = 0; let ok = { - let remaining_buf = &mut self.aliased_buf.as_mut().unwrap().alias_mut()[buf_len..]; + let async_data = self.async.as_mut().unwrap().alias_mut(); + let remaining_buf = &mut async_data.buf[buf_len..]; kernel32::ReadFile(self.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, - self.ov.as_mut().unwrap().alias_mut().deref_mut()) + async_data.ov.deref_mut()) }; // Reset the vector to only expose the already filled part. @@ -498,7 +506,7 @@ impl MessageReader { // which could pose a potential danger in its own right. // Also, it avoids the need to keep a separate state variable -- // which would bear some risk of getting out of sync. - self.aliased_buf.as_mut().unwrap().alias_mut().set_len(buf_len); + self.async.as_mut().unwrap().alias_mut().buf.set_len(buf_len); let result = if ok == winapi::FALSE { Err(GetLastError()) @@ -521,13 +529,11 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.read_buf = self.async.take().unwrap().into_inner().buf; Err(WinError::ChannelClosed) }, Err(err) => { - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.read_buf = self.async.take().unwrap().into_inner().buf; Err(WinError::from_system(err, "ReadFile")) }, } @@ -536,19 +542,18 @@ impl MessageReader { /// Called when we receive an IO Completion Packet for this handle. /// - /// During its course, this method moves `aliased_buf` back into `read_buf`, + /// During its course, this method moves `async.buf` back into `read_buf`, /// thus making it accessible from normal code again; /// so `get_message()` can extract the received messages from the buffer. /// /// Invoking this is unsafe, since calling it in error /// while an async read is actually still in progress in the kernel /// would have catastrophic effects, - /// as `ov` and `aliased_buf` are still mutably aliased by the kernel in that case! - /// - /// (See documentation of the `ov` and `read_buf` fields.) + /// as the `async` data is still mutably aliased by the kernel in that case! + /// (See documentation of the `async` field.) /// - /// Also, this method relies on `ov` and `aliased_buf` actually having valid data, - /// i.e. nothing should modify these fields + /// Also, this method relies on `async` actually having valid data, + /// i.e. nothing should modify its constituent fields /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { @@ -556,9 +561,10 @@ impl MessageReader { // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. - // (And it's safe again to access the `ov` and `aliased_buf` fields.) - let ov = self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + // (And it's safe again to access the `async` data.) + let async_data = self.async.take().unwrap().into_inner(); + let ov = async_data.ov; + self.read_buf = async_data.buf; match io_result { Ok(()) => {} @@ -613,7 +619,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.as_mut().unwrap().alias_mut().deref_mut(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { @@ -639,7 +645,7 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! - if self.ov.is_some() { + if self.async.is_some() { return Ok(None); } @@ -871,7 +877,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); - assert!(reader.ov.is_none()); + assert!(reader.async.is_none()); OsIpcReceiver::from_handle(reader.handle.take()) } From 86ea0f10303945fdf68059b382d9a55235b0333a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 18:44:47 +0200 Subject: [PATCH 101/113] windows: Don't ignore `CancelIoEx()` errors --- src/platform/windows/mod.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 80639f763..3681ba621 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -442,8 +442,23 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.async.is_some() { - kernel32::CancelIoEx(self.handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = kernel32::CancelIoEx(self.handle.as_raw(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + + // A cancel operation is not expected to fail. + // If it does, callers are not prepared for that -- so we have to bail. + // + // Note that we should never ignore a failed cancel, + // since that would affect further operations; + // and the caller definitely must not free the aliased data in that case! + // + // Sometimes `CancelIoEx()` fails with `ERROR_NOT_FOUND` though, + // meaning there is actually no async operation outstanding at this point, + // i.e. we can safely free the async data without further action. + // (Specifically, this is triggered by the `receiver_set_big_data()` test.) + // Not sure why that happens -- but I *think* it should be benign... + assert!(status != winapi::FALSE || GetLastError() == winapi::ERROR_NOT_FOUND); + let async_data = self.async.take().unwrap().into_inner(); self.read_buf = async_data.buf; } From 66aa634a05afe81eff4415b1927890b126491372 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 20:36:45 +0200 Subject: [PATCH 102/113] windows: Don't panic on unknown errors in `notify_completion()` While this might have been a problem in the past, the current code properly passes errors from `notify_completion()` up through all layers; so there is nothing really preventing us from orderly returning any kind of error reported by the `GetOverlappedResult()` or `GetQueuedCompletionStatus()` system calls. (The only problem would be if `GetOverlappedResult()` has failure modes that actually leave the async operation in progress, and thus the async data in use: in that case, unpacking the `AliasedCell<>` in async and returning to the caller would be wrong... But if that's the case, the previous behaviour of panicking after unpacking the `AliasedCell<>` was just as wrong.) Since returning arbitrary errors requires us to invoke `WinError::from_system()` (either directly, or indirectly through `WinError::last()`), and this one wants to know the origin of the error (so it can put it in debug traces), we need to do these conversions near the call sites, and pass an already converted error to `notify_completion()`. (Which seems cleaner anyway.) This has the side effect of also logging "broken pipe" (sender closed) errors in the debug trace, which might be slightly redundant with any other tracing done by the "closed" handling... I don't think that's really a problem, though. --- src/platform/windows/mod.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 3681ba621..5c153fe1d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -571,7 +571,7 @@ impl MessageReader { /// i.e. nothing should modify its constituent fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { + unsafe fn notify_completion(&mut self, io_result: Result<(), WinError>) -> Result<(), WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); // Regardless whether the kernel reported success or error, @@ -583,16 +583,11 @@ impl MessageReader { match io_result { Ok(()) => {} - Err(winapi::ERROR_BROKEN_PIPE) => { + Err(WinError::WindowsResult(winapi::ERROR_BROKEN_PIPE)) => { // Remote end closed the channel. return Err(WinError::ChannelClosed); } - Err(err) => { - // Other errors shouldn't come up here... - // If they do, we don't really understand the situation -- - // so we can't handle this gracefully. - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } + Err(err) => return Err(err), } let nbytes = ov.InternalHigh as u32; @@ -646,7 +641,7 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - Err(err) + Err(WinError::from_system(err, "GetOverlappedResult")) } else { Ok(()) }; @@ -769,8 +764,7 @@ impl MessageReader { Err(WinError::ChannelClosed) => { return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) } - // In blocking mode, `fetch_async_result()` has no other expected failure modes. - Err(_) => unreachable!(), + Err(err) => return Err(err), Ok(()) => {} }; } @@ -1309,15 +1303,17 @@ impl OsIpcReceiverSet { winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); let io_result = if ok == winapi::FALSE { + let err = WinError::last("GetQueuedCompletionStatus"); + // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and // we want to hand the error to notify_completion below. if ov_ptr.is_null() { - return Err(WinError::last("GetQueuedCompletionStatus")); + return Err(err); } - Err(GetLastError()) + Err(err) } else { Ok(()) }; From 54e3faf90353e5191f3083d1c482cce077b0b4e8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 19 May 2018 21:41:25 +0200 Subject: [PATCH 103/113] windows: refactor: Split out `OsIpcReceiverSet.fetch_iocp_result()` Split out the system call and associated handling for getting completion notifications on an IOCP (set) from the rest of the `select()` method, similar to how `fetch_async_result()` encapsulates the event handling for regular readers. This will be necessary for proper `Drop` handling for `OsIpcReceiverSet`. Incidentally, this exactly covers tha unsafe code section of the `select()` implementation; and as such, it's a good step towards better layering of the IOCP handling in general. I guess it could be argued that a method call is also more readable than assigning from a large anonymous block... --- src/platform/windows/mod.rs | 115 ++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5c153fe1d..a64171e4f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1275,6 +1275,75 @@ impl OsIpcReceiverSet { Ok(entry_id) } + /// Conclude an async read operation on any of the receivers in the set. + /// + /// This fetches a completion event from the set's IOCP; + /// finds the matching `MessageReader`; + /// removes it from the list of active readers + /// (since no operation is in flight on this reader at this point); + /// and notifies the reader of the completion event. + /// + /// If the IOCP call is successful, this returns the respective reader, + /// along with an inner status describing the type of event received. + /// This can be a success status, indicating data has been received, + /// and is ready to be picked up with `get_message()` on the reader; + /// an error status indicating that the sender connected to this receiver + /// has closed the connection; + /// or some other I/O error status. + /// + /// Unless a "closed" status is returned, + /// the respective reader remains a member of the set, + /// and the caller should add it back to the list of active readers + /// after kicking off a new read operation on it. + fn fetch_iocp_result(&mut self) -> Result<(MessageReader, Result<(), WinError>), WinError> { + unsafe { + let mut nbytes: u32 = 0; + let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; + let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); + // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! + let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), + &mut nbytes, + &mut completion_key, + &mut ov_ptr, + winapi::INFINITE); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); + let io_result = if ok == winapi::FALSE { + let err = WinError::last("GetQueuedCompletionStatus"); + + // If the OVERLAPPED result is NULL, then the + // function call itself failed or timed out. + // Otherwise, the async IO operation failed, and + // we want to hand the error to notify_completion below. + if ov_ptr.is_null() { + return Err(err); + } + + Err(err) + } else { + Ok(()) + }; + + assert!(!ov_ptr.is_null()); + assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); + + // Find the matching receiver + let (reader_index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); + + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + + // tell it about the completed IO op + let result = reader.notify_completion(io_result); + + Ok((reader, result)) + } + } + pub fn select(&mut self) -> Result,WinError> { assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); win32_trace!("[# {:?}] select() with {} active and {} closed receivers", self.iocp.as_raw(), self.readers.len(), self.closed_readers.len()); @@ -1291,51 +1360,7 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let (mut reader, result) = unsafe { - let mut nbytes: u32 = 0; - let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; - let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); - // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! - let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), - &mut nbytes, - &mut completion_key, - &mut ov_ptr, - winapi::INFINITE); - win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - let io_result = if ok == winapi::FALSE { - let err = WinError::last("GetQueuedCompletionStatus"); - - // If the OVERLAPPED result is NULL, then the - // function call itself failed or timed out. - // Otherwise, the async IO operation failed, and - // we want to hand the error to notify_completion below. - if ov_ptr.is_null() { - return Err(err); - } - - Err(err) - } else { - Ok(()) - }; - - assert!(!ov_ptr.is_null()); - assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); - - // Find the matching receiver - let (reader_index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) - .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - - // Remove the entry from the set for now -- we will re-add it later, - // if we can successfully initiate another async read operation. - let mut reader = self.readers.swap_remove(reader_index); - - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - - // tell it about the completed IO op - let result = reader.notify_completion(io_result); - (reader, result) - }; + let (mut reader, result) = try!(self.fetch_iocp_result()); let mut closed = match result { Ok(()) => false, From b85e676952ef70d764759c44362d12183112c208 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 19 May 2018 22:11:46 +0200 Subject: [PATCH 104/113] windows: Make `cancel_io()` sound According to the documentation of `CancelIoEx()`, successful completion of the `CancelIoEx()` call only indicates that the cancel request has been successfully *queued* -- but it does *not* mean we can safely free the aliased buffers yet! Rather, we have to wait for a notification signalling the completion of the async operation itself. We thus split out the actual `CancelIoEx()` call into a new `issue_async_cancel()` method, and turn `cancel_io()` into a wrapper that waits for the actualy async read to conclude (using `fetch_async_result()`) after issuing the cancel request. Since that doesn't work on readers in a receiver set, we need to add an explicit `Drop` implementation for `OsIpcReceiverSet`, which issues cancel requests for all outstanding read operations, and then uses `fetch_iocp_result()` to wait for all of them to conclude. --- src/platform/windows/mod.rs | 77 ++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a64171e4f..4e37e107d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -439,12 +439,27 @@ impl MessageReader { mem::replace(self, MessageReader::new(WinHandle::invalid())) } - fn cancel_io(&mut self) { + /// Request the kernel to cancel a pending async I/O operation on this reader. + /// + /// Note that this only schedules the cancel request; + /// but doesn't guarantee that the operation is done + /// (and the buffers are no longer used by the kernel) + /// before this method returns. + /// + /// A caller that wants to ensure the operation is really done, + /// will need to wait using `fetch_async_result()`. + /// (Or `fetch_iocp_result()` for readers in a set.) + /// + /// The only exception is if the kernel indicates + /// that no operation was actually outstanding at this point. + /// In that case, the `async` data is released immediately; + /// and the caller should not attempt waiting for completion. + fn issue_async_cancel(&mut self) { unsafe { - if self.async.is_some() { - let status = kernel32::CancelIoEx(self.handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = kernel32::CancelIoEx(self.handle.as_raw(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + if status == winapi::FALSE { // A cancel operation is not expected to fail. // If it does, callers are not prepared for that -- so we have to bail. // @@ -453,14 +468,38 @@ impl MessageReader { // and the caller definitely must not free the aliased data in that case! // // Sometimes `CancelIoEx()` fails with `ERROR_NOT_FOUND` though, - // meaning there is actually no async operation outstanding at this point, - // i.e. we can safely free the async data without further action. + // meaning there is actually no async operation outstanding at this point. // (Specifically, this is triggered by the `receiver_set_big_data()` test.) // Not sure why that happens -- but I *think* it should be benign... - assert!(status != winapi::FALSE || GetLastError() == winapi::ERROR_NOT_FOUND); + // + // In that case, we can safely free the async data right now; + // and the caller should not attempt to wait for completion. + assert!(GetLastError() == winapi::ERROR_NOT_FOUND); + + self.read_buf = self.async.take().unwrap().into_inner().buf; + } + } + } + + fn cancel_io(&mut self) { + if self.async.is_some() { + // This doesn't work for readers in a receiver set. + // (`fetch_async_result()` would hang indefinitely.) + // Receiver sets have to handle cancellation specially, + // and make sure they always do that *before* dropping readers. + assert!(self.entry_id.is_none()); + + self.issue_async_cancel(); - let async_data = self.async.take().unwrap().into_inner(); - self.read_buf = async_data.buf; + // If there is an operation still in flight, wait for it to complete. + // + // This will usually fail with `ERROR_OPERATION_ABORTED`; + // but it could also return success, or some other error, + // if the operation actually completed in the mean time. + // We don't really care either way -- + // we just want to be certain there is no operation in flight any more. + if self.async.is_some() { + let _ = self.fetch_async_result(BlockingMode::Blocking); } } } @@ -1232,6 +1271,26 @@ pub struct OsIpcReceiverSet { closed_readers: Vec, } +impl Drop for OsIpcReceiverSet { + fn drop(&mut self) { + // We need to cancel any in-flight read operations before we drop the receivers, + // since otherwise the receivers' `Drop` implementation would try to cancel them -- + // but the implementation there doesn't work for receivers in a set... + for reader in &mut self.readers { + reader.issue_async_cancel(); + } + + // Wait for any reads still in flight to complete, + // thus freeing the associated async data. + self.readers.retain(|r| r.async.is_some()); + while !self.readers.is_empty() { + // We unwrap the outer result (can't deal with the IOCP call failing here), + // but don't care about the actual results of the completed read operations. + let _ = self.fetch_iocp_result().unwrap(); + } + } +} + impl OsIpcReceiverSet { pub fn new() -> Result { unsafe { From 0758099a0c0c9f78e0439348ab26efe1c8160068 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 30 May 2018 23:29:36 +0200 Subject: [PATCH 105/113] [RemoveMe] Temporarily restore all CI targets using `unix` back-end Increase chance of catching intermittent failures while working on other stuff... --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 39b51c48e..57140f31b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ env: - RUST_BACKTRACE=1 matrix: - FEATURES="unstable" + - FEATURES="unstable memfd" script: - cargo build --features "$FEATURES" From 3481ef1d112425028dc6698f3419739e0801bc05 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 7 Jun 2018 20:38:55 +0200 Subject: [PATCH 106/113] WIP: threaded fragment tests --- src/platform/test.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 835cb1079..44edc0127 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -198,9 +198,15 @@ fn with_n_fds(n: usize, size: usize) { let (super_tx, super_rx) = platform::channel().unwrap(); let data: Vec = (0..size).map(|i| (i % 251) as u8).collect(); - super_tx.send(&data[..], sender_fds, vec![]).unwrap(); + let thread = { + let data = data.clone(); + thread::spawn(move || { + super_tx.send(&data[..], sender_fds, vec![]).unwrap(); + }) + }; let (received_data, received_channels, received_shared_memory_regions) = super_rx.recv().unwrap(); + thread.join().unwrap(); assert_eq!(received_data.len(), data.len()); assert_eq!(&received_data[..], &data[..]); From 12e03b98334dfcd2d7c0e16c6aefe8998e10a688 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 8 Jun 2018 18:24:13 +0200 Subject: [PATCH 107/113] windows: Properly hide all `win32-trace` code behind conditionals Make sure all code related to the `win32-trace` feature is compiled only when the feature is enabled. This avoids unnecessary bloat; as well as potential compile errors when other code conditional on this feature is added. --- src/platform/windows/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 4e37e107d..278791029 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -14,6 +14,7 @@ use libc::intptr_t; use std::cell::{Cell, RefCell}; use std::cmp::PartialEq; use std::default::Default; +#[cfg(feature = "win32-trace")] use std::env; use std::ffi::CString; use std::io::{Error, ErrorKind}; @@ -33,14 +34,17 @@ use self::aliased_cell::AliasedCell; lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); +} +#[cfg(feature = "win32-trace")] +lazy_static! { static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } /// Debug macro to better track what's going on in case of errors. macro_rules! win32_trace { ($($rest:tt)*) => { - if cfg!(feature = "win32-trace") { + #[cfg(feature = "win32-trace")] { if *DEBUG_TRACE_ENABLED { println!($($rest)*); } } } @@ -1017,8 +1021,8 @@ impl OsIpcReceiver { }, // Anything else signifies some actual I/O error. - err => { - win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); + _err => { + win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), _err); Err(WinError::last("ConnectNamedPipe")) }, }; @@ -1700,8 +1704,8 @@ impl WinError { } } - fn from_system(err: u32, f: &str) -> WinError { - win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, f); + fn from_system(err: u32, _f: &str) -> WinError { + win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, _f); WinError::WindowsResult(err) } From 0aefbb9eb240b57c37d1ee0872e93f2af1babb21 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 8 Jun 2018 18:31:34 +0200 Subject: [PATCH 108/113] windows: Introduce `MessageReader.get_raw_handle()` debug helper Add a (conditional) helper method for obtaining the raw handle of the reader -- which is often needed for the `win32_trace` invocations -- to abstract the internal structure of this type, thus facilitating further refactoring. An alternate approach would be overriding the `Debug` trait on `MessageReader`, to just print the raw handle value. That would provide better encapsulation; however, it would also preclude the possibility of easily printing all the constituents of the structure during debugging... (Or we could leave `Debug` alone, and instead implement it as `Display` -- but that feels like an abuse of the `Display` facility... Not sure what to think about that.) --- src/platform/windows/mod.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 278791029..cdb27643d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -814,6 +814,14 @@ impl MessageReader { Ok(mem::replace(&mut self.read_buf, vec![])) } + + /// Get raw handle of the receive port. + /// + /// This is only for debug tracing purposes, and must not be used for anything else. + #[cfg(feature = "win32-trace")] + fn get_raw_handle(&self) -> HANDLE { + self.handle.as_raw() + } } #[derive(Clone, Copy, Debug)] @@ -1323,13 +1331,13 @@ impl OsIpcReceiverSet { match reader.add_to_iocp(&self.iocp, entry_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.get_raw_handle(), entry_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.get_raw_handle(), entry_id); self.closed_readers.push(entry_id); } Err(err) => return Err(err), @@ -1398,7 +1406,7 @@ impl OsIpcReceiverSet { // if we can successfully initiate another async read operation. let mut reader = self.readers.swap_remove(reader_index); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.get_raw_handle()); // tell it about the completed IO op let result = reader.notify_completion(io_result); @@ -1434,10 +1442,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.entry_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1461,7 +1469,7 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.entry_id.unwrap())); } } From 1a023e9761323f6f548a016549158ede842bfb53 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 21 May 2018 10:56:19 +0200 Subject: [PATCH 109/113] windows: Move `handle` into `AsyncData` as well For the duration of an async read operation, move the pipe handle into the `AliasedCell` along with the other fields used for the async operation. This prevents anything else from messing with the pipe while the async read is in progress; and makes sure the handle and the other fields can never get mismatched. While I'm not sure whether there is any scenario in which such a mismatch could result in undefined behaviour, it's good for general robustness in any case. --- src/platform/windows/aliased_cell.rs | 13 ++++++++ src/platform/windows/mod.rs | 46 ++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/aliased_cell.rs b/src/platform/windows/aliased_cell.rs index 6399c40fc..6488882aa 100644 --- a/src/platform/windows/aliased_cell.rs +++ b/src/platform/windows/aliased_cell.rs @@ -105,6 +105,19 @@ impl AliasedCell { &mut self.value } + /// Get a shared (immutable) pointer to the inner value. + /// + /// With this method it's possible to get an alias + /// while only holding a shared reference to the `AliasedCell`. + /// + /// Since all the unsafe aliases are untracked, + /// it's up to the callers to make sure no shared aliases are used + /// while the data might actually be mutated elsewhere + /// through some outstanding mutable aliases. + pub unsafe fn alias(&self) -> &T { + &self.inner + } + /// Move out the wrapped value, making it accessible from safe code again. pub unsafe fn into_inner(self) -> T { mem::forget(self.drop_bomb); diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cdb27643d..f8585c795 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -340,6 +340,9 @@ impl WinHandle { /// Helper struct for all data being aliased by the kernel during async reads. #[derive(Debug)] struct AsyncData { + /// File handle of the pipe on which the async operation is performed. + handle: WinHandle, + /// Meta-data for this async read operation, filled by the kernel. /// /// This must be on the heap, in order for its memory location -- @@ -362,12 +365,21 @@ struct AsyncData { #[derive(Debug)] struct MessageReader { /// The pipe read handle. + /// + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// it is moved into the `async` sub-structure (see below) + /// along with the other fields used for the async operation, + /// to make sure they all stay in sync, + /// and nothing else can meddle with the the pipe + /// until the operation is completed. handle: WinHandle, /// Buffer for outstanding data, that has been received but not yet processed. /// - /// Note: this is only set while no async read operation - /// is currently in progress with the kernel. + /// Note: just like `handle` above, + /// this is only set while no async read is in progress. /// When an async read is in progress, /// the receive buffer is aliased by the kernel; /// so we need to temporarily move it into an `AliasedCell`, @@ -460,7 +472,7 @@ impl MessageReader { /// and the caller should not attempt waiting for completion. fn issue_async_cancel(&mut self) { unsafe { - let status = kernel32::CancelIoEx(self.handle.as_raw(), + let status = kernel32::CancelIoEx(self.async.as_ref().unwrap().alias().handle.as_raw(), self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); if status == winapi::FALSE { @@ -480,7 +492,9 @@ impl MessageReader { // and the caller should not attempt to wait for completion. assert!(GetLastError() == winapi::ERROR_NOT_FOUND); - self.read_buf = self.async.take().unwrap().into_inner().buf; + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; } } } @@ -537,6 +551,7 @@ impl MessageReader { // issue the read to the buffer, at the current length offset self.async = Some(AliasedCell::new(AsyncData { + handle: self.handle.take(), ov: Box::new(mem::zeroed()), buf: mem::replace(&mut self.read_buf, vec![]), })); @@ -544,7 +559,7 @@ impl MessageReader { let ok = { let async_data = self.async.as_mut().unwrap().alias_mut(); let remaining_buf = &mut async_data.buf[buf_len..]; - kernel32::ReadFile(self.handle.as_raw(), + kernel32::ReadFile(async_data.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, @@ -587,11 +602,18 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.read_buf = self.async.take().unwrap().into_inner().buf; + + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; + Err(WinError::ChannelClosed) }, Err(err) => { - self.read_buf = self.async.take().unwrap().into_inner().buf; + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; + Err(WinError::from_system(err, "ReadFile")) }, } @@ -615,12 +637,13 @@ impl MessageReader { /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), WinError>) -> Result<(), WinError> { - win32_trace!("[$ {:?}] notify_completion", self.handle); + win32_trace!("[$ {:?}] notify_completion", self.async.as_ref().unwrap().alias().handle); // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `async` data.) let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; let ov = async_data.ov; self.read_buf = async_data.buf; @@ -671,7 +694,7 @@ impl MessageReader { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, }; - let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), + let ok = kernel32::GetOverlappedResult(self.async.as_ref().unwrap().alias().handle.as_raw(), self.async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); @@ -1399,7 +1422,10 @@ impl OsIpcReceiverSet { // Find the matching receiver let (reader_index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .find(|&(_, ref reader)| { + let raw_handle = reader.async.as_ref().unwrap().alias().handle.as_raw(); + raw_handle as winapi::ULONG_PTR == completion_key + }) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); // Remove the entry from the set for now -- we will re-add it later, From 6e678a7668c6654c9705beac76f121ac40523588 Mon Sep 17 00:00:00 2001 From: Angel Date: Mon, 24 Jun 2019 12:24:16 -0500 Subject: [PATCH 110/113] fixup! Implement ipc-channel on Windows --- Cargo.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd6f39252..a0e1f6587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,8 @@ edition = "2018" force-inprocess = [] memfd = ["sc"] unstable = [] -<<<<<<< HEAD async = ["futures-preview", "futures-test-preview"] win32-trace = [] -======= -async = ["futures"] -win32-trace = [] ->>>>>>> Implement ipc-channel on Windows [dependencies] bincode = "1" From 6a517440f906e76a993ea856f63e1e38a82efb62 Mon Sep 17 00:00:00 2001 From: Angel Date: Mon, 24 Jun 2019 13:13:05 -0500 Subject: [PATCH 111/113] Update for Rust 2018. --- src/platform/test.rs | 15 +---- src/platform/windows/aliased_cell.rs | 2 +- src/platform/windows/mod.rs | 97 ++++++++++++++-------------- 3 files changed, 50 insertions(+), 64 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 44edc0127..88dfc2157 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -20,17 +20,12 @@ use crate::platform::{OsIpcSender, OsIpcOneShotServer}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -<<<<<<< HEAD use crate::test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] -======= -use test::{fork, Wait}; -#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] ->>>>>>> Implement ipc-channel on Windows -use test::{get_channel_name_arg, spawn_server}; +use crate::test::{get_channel_name_arg, spawn_server}; #[test] fn simple() { @@ -198,15 +193,9 @@ fn with_n_fds(n: usize, size: usize) { let (super_tx, super_rx) = platform::channel().unwrap(); let data: Vec = (0..size).map(|i| (i % 251) as u8).collect(); - let thread = { - let data = data.clone(); - thread::spawn(move || { - super_tx.send(&data[..], sender_fds, vec![]).unwrap(); - }) - }; + super_tx.send(&data[..], sender_fds, vec![]).unwrap(); let (received_data, received_channels, received_shared_memory_regions) = super_rx.recv().unwrap(); - thread.join().unwrap(); assert_eq!(received_data.len(), data.len()); assert_eq!(&received_data[..], &data[..]); diff --git a/src/platform/windows/aliased_cell.rs b/src/platform/windows/aliased_cell.rs index 6488882aa..0bb9d89c9 100644 --- a/src/platform/windows/aliased_cell.rs +++ b/src/platform/windows/aliased_cell.rs @@ -115,7 +115,7 @@ impl AliasedCell { /// while the data might actually be mutated elsewhere /// through some outstanding mutable aliases. pub unsafe fn alias(&self) -> &T { - &self.inner + &self.value // orig &self.inner } /// Move out the wrapped value, making it accessible from safe code again. diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f8585c795..dc66bd8b6 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -14,7 +14,6 @@ use libc::intptr_t; use std::cell::{Cell, RefCell}; use std::cmp::PartialEq; use std::default::Default; -#[cfg(feature = "win32-trace")] use std::env; use std::ffi::CString; use std::io::{Error, ErrorKind}; @@ -36,7 +35,6 @@ lazy_static! { static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); } -#[cfg(feature = "win32-trace")] lazy_static! { static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } @@ -44,7 +42,7 @@ lazy_static! { /// Debug macro to better track what's going on in case of errors. macro_rules! win32_trace { ($($rest:tt)*) => { - #[cfg(feature = "win32-trace")] { + if cfg!(feature = "win32-trace") { if *DEBUG_TRACE_ENABLED { println!($($rest)*); } } } @@ -72,8 +70,8 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { let pipe_id = make_pipe_id(); let pipe_name = make_pipe_name(&pipe_id); - let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); - let sender = try!(OsIpcSender::connect_named(&pipe_name)); + let receiver = OsIpcReceiver::new_named(&pipe_name)?; + let sender = OsIpcSender::connect_named(&pipe_name)?; Ok((sender, receiver)) } @@ -207,7 +205,7 @@ impl<'de> serde::Deserialize<'de> for OutOfBandMessage { where D: serde::Deserializer<'de> { let (target_process_id, channel_handles, shmem_handles, big_data_receiver_handle) = - try!(serde::Deserialize::deserialize(deserializer)); + serde::Deserialize::deserialize(deserializer)?; Ok(OutOfBandMessage { target_process_id: target_process_id, channel_handles: channel_handles, @@ -408,7 +406,7 @@ struct MessageReader { /// We thus wrap it in an `AliasedCell`, /// making sure the data is only accessible from code marked `unsafe`; /// and only move it out when the kernel signals that the async read is done. - async: Option>, + r#async: Option>, /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// @@ -442,7 +440,7 @@ impl MessageReader { MessageReader { handle: handle, read_buf: Vec::new(), - async: None, + r#async: None, entry_id: None, } } @@ -472,8 +470,8 @@ impl MessageReader { /// and the caller should not attempt waiting for completion. fn issue_async_cancel(&mut self) { unsafe { - let status = kernel32::CancelIoEx(self.async.as_ref().unwrap().alias().handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = kernel32::CancelIoEx(self.r#async.as_ref().unwrap().alias().handle.as_raw(), + self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut()); if status == winapi::FALSE { // A cancel operation is not expected to fail. @@ -492,7 +490,7 @@ impl MessageReader { // and the caller should not attempt to wait for completion. assert!(GetLastError() == winapi::ERROR_NOT_FOUND); - let async_data = self.async.take().unwrap().into_inner(); + let async_data = self.r#async.take().unwrap().into_inner(); self.handle = async_data.handle; self.read_buf = async_data.buf; } @@ -500,7 +498,7 @@ impl MessageReader { } fn cancel_io(&mut self) { - if self.async.is_some() { + if self.r#async.is_some() { // This doesn't work for readers in a receiver set. // (`fetch_async_result()` would hang indefinitely.) // Receiver sets have to handle cancellation specially, @@ -516,7 +514,7 @@ impl MessageReader { // if the operation actually completed in the mean time. // We don't really care either way -- // we just want to be certain there is no operation in flight any more. - if self.async.is_some() { + if self.r#async.is_some() { let _ = self.fetch_async_result(BlockingMode::Blocking); } } @@ -532,7 +530,7 @@ impl MessageReader { /// (See documentation of the `read_buf` and `async` fields.) fn start_read(&mut self) -> Result<(),WinError> { // Nothing needs to be done if an async read operation is already in progress. - if self.async.is_some() { + if self.r#async.is_some() { return Ok(()); } @@ -550,14 +548,14 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset - self.async = Some(AliasedCell::new(AsyncData { + self.r#async = Some(AliasedCell::new(AsyncData { handle: self.handle.take(), ov: Box::new(mem::zeroed()), buf: mem::replace(&mut self.read_buf, vec![]), })); let mut bytes_read: u32 = 0; let ok = { - let async_data = self.async.as_mut().unwrap().alias_mut(); + let async_data = self.r#async.as_mut().unwrap().alias_mut(); let remaining_buf = &mut async_data.buf[buf_len..]; kernel32::ReadFile(async_data.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, @@ -579,7 +577,7 @@ impl MessageReader { // which could pose a potential danger in its own right. // Also, it avoids the need to keep a separate state variable -- // which would bear some risk of getting out of sync. - self.async.as_mut().unwrap().alias_mut().buf.set_len(buf_len); + self.r#async.as_mut().unwrap().alias_mut().buf.set_len(buf_len); let result = if ok == winapi::FALSE { Err(GetLastError()) @@ -603,14 +601,14 @@ impl MessageReader { Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - let async_data = self.async.take().unwrap().into_inner(); + let async_data = self.r#async.take().unwrap().into_inner(); self.handle = async_data.handle; self.read_buf = async_data.buf; Err(WinError::ChannelClosed) }, Err(err) => { - let async_data = self.async.take().unwrap().into_inner(); + let async_data = self.r#async.take().unwrap().into_inner(); self.handle = async_data.handle; self.read_buf = async_data.buf; @@ -637,12 +635,12 @@ impl MessageReader { /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), WinError>) -> Result<(), WinError> { - win32_trace!("[$ {:?}] notify_completion", self.async.as_ref().unwrap().alias().handle); + win32_trace!("[$ {:?}] notify_completion", self.r#async.as_ref().unwrap().alias().handle); // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `async` data.) - let async_data = self.async.take().unwrap().into_inner(); + let async_data = self.r#async.take().unwrap().into_inner(); self.handle = async_data.handle; let ov = async_data.ov; self.read_buf = async_data.buf; @@ -694,8 +692,8 @@ impl MessageReader { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, }; - let ok = kernel32::GetOverlappedResult(self.async.as_ref().unwrap().alias().handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut(), + let ok = kernel32::GetOverlappedResult(self.r#async.as_ref().unwrap().alias().handle.as_raw(), + self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { @@ -721,7 +719,7 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! - if self.async.is_some() { + if self.r#async.is_some() { return Ok(None); } @@ -750,7 +748,7 @@ impl MessageReader { if oob.big_data_receiver_handle.is_some() { let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); let receiver = OsIpcReceiver::from_handle(WinHandle::new(handle as HANDLE)); - big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); + big_data = Some(receiver.recv_raw(big_data_size as usize)?); } } @@ -841,7 +839,6 @@ impl MessageReader { /// Get raw handle of the receive port. /// /// This is only for debug tracing purposes, and must not be used for anything else. - #[cfg(feature = "win32-trace")] fn get_raw_handle(&self) -> HANDLE { self.handle.as_raw() } @@ -960,7 +957,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); - assert!(reader.async.is_none()); + assert!(reader.r#async.is_none()); OsIpcReceiver::from_handle(reader.handle.take()) } @@ -977,12 +974,12 @@ impl OsIpcReceiver { loop { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer - if let Some((data, channels, shmems)) = try!(reader.get_message()) { + if let Some((data, channels, shmems)) = reader.get_message()? { return Ok((data, channels, shmems)); } // Then, issue a read if we don't have one already in flight. - try!(reader.start_read()); + reader.start_read()?; // Attempt to complete the read. // @@ -990,7 +987,7 @@ impl OsIpcReceiver { // The async read remains in flight in that case; // and another attempt at getting a result // can be done the next time we are called. - try!(reader.fetch_async_result(blocking_mode)); + reader.fetch_async_result(blocking_mode)?; // If we're not blocking, pretend that we are blocking, since we got part of // a message already. Keep reading until we get a complete message. @@ -1138,7 +1135,7 @@ impl OsIpcSender { fn get_pipe_server_process_handle_and_pid(&self) -> Result<(WinHandle, winapi::ULONG),WinError> { unsafe { - let server_pid = try!(self.get_pipe_server_process_id()); + let server_pid = self.get_pipe_server_process_id()?; if server_pid == *CURRENT_PROCESS_ID { return Ok((WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw()), server_pid)); } @@ -1167,7 +1164,7 @@ impl OsIpcSender { /// An internal-use-only send method that sends just raw data, with no header. fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", self.handle.as_raw(), data.len(), *CURRENT_PROCESS_ID, - try!(self.get_pipe_server_process_id())); + self.get_pipe_server_process_id()?); // Write doesn't need to be atomic, // since the pipe is exclusive for this message, @@ -1187,7 +1184,7 @@ impl OsIpcSender { assert!(data.len() <= u32::max_value() as usize); let (server_h, server_pid) = if !shared_memory_regions.is_empty() || !ports.is_empty() { - try!(self.get_pipe_server_process_handle_and_pid()) + self.get_pipe_server_process_handle_and_pid()? } else { (WinHandle::invalid(), 0) }; @@ -1196,23 +1193,23 @@ impl OsIpcSender { for ref shmem in shared_memory_regions { // shmem.handle, shmem.length - let mut remote_handle = try!(dup_handle_to_process(&shmem.handle, &server_h)); + let mut remote_handle = dup_handle_to_process(&shmem.handle, &server_h)?; oob.shmem_handles.push((remote_handle.take_raw() as intptr_t, shmem.length as u64)); } for port in ports { match port { OsIpcChannel::Sender(s) => { - let mut raw_remote_handle = try!(move_handle_to_process(s.handle, &server_h)); + let mut raw_remote_handle = move_handle_to_process(s.handle, &server_h)?; oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, OsIpcChannel::Receiver(r) => { - if try!(r.prepare_for_transfer()) == false { + if r.prepare_for_transfer()? == false { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } let handle = r.reader.into_inner().handle.take(); - let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); + let mut raw_remote_handle = move_handle_to_process(handle, &server_h)?; oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, } @@ -1222,17 +1219,17 @@ impl OsIpcSender { let big_data_sender: Option = if OsIpcSender::needs_fragmentation(data.len(), &oob) { // We need to create a channel for the big data - let (sender, receiver) = try!(channel()); + let (sender, receiver) = channel()?; let (server_h, server_pid) = if server_h.is_valid() { (server_h, server_pid) } else { - try!(self.get_pipe_server_process_handle_and_pid()) + self.get_pipe_server_process_handle_and_pid()? }; // Put the receiver in the OOB data let handle = receiver.reader.into_inner().handle.take(); - let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); + let mut raw_receiver_handle = move_handle_to_process(handle, &server_h)?; oob.big_data_receiver_handle = Some((raw_receiver_handle.take_raw() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; @@ -1270,13 +1267,13 @@ impl OsIpcSender { // Write needs to be atomic, since otherwise concurrent sending // could result in parts of different messages getting intermixed, // and the receiver would not be able to extract the individual messages. - try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); + write_buf(&self.handle, &*full_message, AtomicMode::Atomic)?; } else { full_message.extend_from_slice(&*oob_data); assert!(full_message.len() == full_in_band_len); - try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); - try!(big_data_sender.unwrap().send_raw(data)); + write_buf(&self.handle, &*full_message, AtomicMode::Atomic)?; + big_data_sender.unwrap().send_raw(data)?; } Ok(()) @@ -1317,7 +1314,7 @@ impl Drop for OsIpcReceiverSet { // Wait for any reads still in flight to complete, // thus freeing the associated async data. - self.readers.retain(|r| r.async.is_some()); + self.readers.retain(|r| r.r#async.is_some()); while !self.readers.is_empty() { // We unwrap the outer result (can't deal with the IOCP call failing here), // but don't care about the actual results of the completed read operations. @@ -1423,7 +1420,7 @@ impl OsIpcReceiverSet { // Find the matching receiver let (reader_index, _) = self.readers.iter().enumerate() .find(|&(_, ref reader)| { - let raw_handle = reader.async.as_ref().unwrap().alias().handle.as_raw(); + let raw_handle = reader.r#async.as_ref().unwrap().alias().handle.as_raw(); raw_handle as winapi::ULONG_PTR == completion_key }) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); @@ -1457,7 +1454,7 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let (mut reader, result) = try!(self.fetch_iocp_result()); + let (mut reader, result) = self.fetch_iocp_result()?; let mut closed = match result { Ok(()) => false, @@ -1467,7 +1464,7 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. - while let Some((data, channels, shmems)) = try!(reader.get_message()) { + while let Some((data, channels, shmems)) = reader.get_message()? { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.entry_id.unwrap(), data, channels, shmems)); } @@ -1640,7 +1637,7 @@ impl OsIpcOneShotServer { pub fn new() -> Result<(OsIpcOneShotServer, String),WinError> { let pipe_id = make_pipe_id(); let pipe_name = make_pipe_name(&pipe_id); - let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); + let receiver = OsIpcReceiver::new_named(&pipe_name)?; Ok(( OsIpcOneShotServer { receiver: receiver, @@ -1654,8 +1651,8 @@ impl OsIpcOneShotServer { Vec, Vec),WinError> { let receiver = self.receiver; - try!(receiver.accept()); - let (data, channels, shmems) = try!(receiver.recv()); + receiver.accept()?; + let (data, channels, shmems) = receiver.recv()?; Ok((receiver, data, channels, shmems)) } } From 237e026484f20219f4440120e62aae1fc723bc29 Mon Sep 17 00:00:00 2001 From: Angel Date: Wed, 26 Jun 2019 13:55:05 -0500 Subject: [PATCH 112/113] Migrated ipc-channel to use winapi version 3. Removed kernel3 --- Cargo.toml | 3 +- src/lib.rs | 3 +- src/platform/windows/mod.rs | 188 ++++++++++++++++++++---------------- 3 files changed, 108 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0e1f6587..b89e939de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,5 +35,4 @@ sc = { version = "0.2.2", optional = true } crossbeam = "0.2" [target.'cfg(target_os = "windows")'.dependencies] -winapi = "0.2" -kernel32-sys = "0.2" +winapi = {version = "0.3.7", features = ["ioapiset", "memoryapi", "namedpipeapi", "handleapi"]} diff --git a/src/lib.rs b/src/lib.rs index 162f62baf..08daa0a5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,8 +82,7 @@ pub mod asynch; #[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] extern crate winapi; -#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] -extern crate kernel32; + pub mod ipc; pub mod platform; diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index dc66bd8b6..65f6af519 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -9,7 +9,7 @@ use serde; use bincode; -use kernel32; + use libc::intptr_t; use std::cell::{Cell, RefCell}; use std::cmp::PartialEq; @@ -24,15 +24,39 @@ use std::ptr; use std::slice; use std::thread; use uuid::Uuid; -use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; +use winapi::um::winnt::{HANDLE}; +use winapi::um::handleapi::{INVALID_HANDLE_VALUE}; +use winapi::shared::minwindef::{LPVOID}; use winapi; +use std::fmt; mod aliased_cell; use self::aliased_cell::AliasedCell; lazy_static! { - static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; - static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); + static ref CURRENT_PROCESS_ID: winapi::shared::ntdef::ULONG = unsafe { winapi::um::processthreadsapi::GetCurrentProcessId() }; + static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { winapi::um::processthreadsapi::GetCurrentProcess() }); +} + +struct NoDebug(T); + +impl Deref for NoDebug { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for NoDebug { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for NoDebug { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Ok(()) + } } lazy_static! { @@ -62,7 +86,7 @@ const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024; #[allow(non_snake_case)] fn GetLastError() -> u32 { unsafe { - kernel32::GetLastError() + winapi::um::errhandlingapi::GetLastError() } } @@ -228,7 +252,7 @@ fn make_pipe_name(pipe_id: &Uuid) -> CString { /// /// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is /// also the pseudohandle for the current process). -fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandle, flags: winapi::DWORD) +fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandle, flags: winapi::shared::minwindef::DWORD) -> Result { if !handle.is_valid() { @@ -237,10 +261,10 @@ fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandl unsafe { let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let ok = kernel32::DuplicateHandle(CURRENT_PROCESS_HANDLE.as_raw(), handle.as_raw(), + let ok = winapi::um::handleapi::DuplicateHandle(CURRENT_PROCESS_HANDLE.as_raw(), handle.as_raw(), other_process.as_raw(), &mut new_handle, - 0, winapi::FALSE, flags); - if ok == winapi::FALSE { + 0, winapi::shared::minwindef::FALSE, flags); + if ok == winapi::shared::minwindef::FALSE { Err(WinError::last("DuplicateHandle")) } else { Ok(WinHandle::new(new_handle)) @@ -255,13 +279,13 @@ fn dup_handle(handle: &WinHandle) -> Result { /// Duplicate a handle to the target process. fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { - dup_handle_to_process_with_flags(handle, other_process, winapi::DUPLICATE_SAME_ACCESS) + dup_handle_to_process_with_flags(handle, other_process, winapi::um::winnt::DUPLICATE_SAME_ACCESS) } /// Duplicate a handle to the target process, closing the source handle. fn move_handle_to_process(handle: WinHandle, other_process: &WinHandle) -> Result { let result = dup_handle_to_process_with_flags(&handle, other_process, - winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS); + winapi::um::winnt::DUPLICATE_CLOSE_SOURCE | winapi::um::winnt::DUPLICATE_SAME_ACCESS); // Since the handle was moved to another process, the original is no longer valid; // so we probably shouldn't try to close it explicitly? mem::forget(handle); @@ -280,7 +304,7 @@ impl Drop for WinHandle { fn drop(&mut self) { unsafe { if self.is_valid() { - let result = kernel32::CloseHandle(self.h); + let result = winapi::um::handleapi::CloseHandle(self.h); assert!(thread::panicking() || result != 0); } } @@ -301,7 +325,7 @@ impl PartialEq for WinHandle { // // On Windows 10, we could use: // ``` - // unsafe { kernel32::CompareObjectHandles(self.h, other.h) == winapi::TRUE } + // unsafe { winapi::um:handleapi::CompareObjectHandles(self.h, other.h) == winapi::shared::minwindef::TRUE } // ``` // // This API call however is not available on older versions. @@ -346,7 +370,7 @@ struct AsyncData { /// This must be on the heap, in order for its memory location -- /// which is registered in the kernel during an async read -- /// to remain stable even when the enclosing structure is passed around. - ov: Box, + ov: NoDebug>, /// Buffer for the kernel to store the results of the async read operation. /// @@ -470,10 +494,10 @@ impl MessageReader { /// and the caller should not attempt waiting for completion. fn issue_async_cancel(&mut self) { unsafe { - let status = kernel32::CancelIoEx(self.r#async.as_ref().unwrap().alias().handle.as_raw(), - self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = winapi::um::ioapiset::CancelIoEx(self.r#async.as_ref().unwrap().alias().handle.as_raw(), + &mut **self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut()); - if status == winapi::FALSE { + if status == winapi::shared::minwindef::FALSE { // A cancel operation is not expected to fail. // If it does, callers are not prepared for that -- so we have to bail. // @@ -488,7 +512,7 @@ impl MessageReader { // // In that case, we can safely free the async data right now; // and the caller should not attempt to wait for completion. - assert!(GetLastError() == winapi::ERROR_NOT_FOUND); + assert!(GetLastError() == winapi::shared::winerror::ERROR_NOT_FOUND); let async_data = self.r#async.take().unwrap().into_inner(); self.handle = async_data.handle; @@ -550,18 +574,18 @@ impl MessageReader { // issue the read to the buffer, at the current length offset self.r#async = Some(AliasedCell::new(AsyncData { handle: self.handle.take(), - ov: Box::new(mem::zeroed()), + ov: NoDebug(Box::new(mem::zeroed())), buf: mem::replace(&mut self.read_buf, vec![]), })); let mut bytes_read: u32 = 0; let ok = { let async_data = self.r#async.as_mut().unwrap().alias_mut(); let remaining_buf = &mut async_data.buf[buf_len..]; - kernel32::ReadFile(async_data.handle.as_raw(), + winapi::um::fileapi::ReadFile(async_data.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, - async_data.ov.deref_mut()) + &mut **async_data.ov.deref_mut()) }; // Reset the vector to only expose the already filled part. @@ -579,7 +603,7 @@ impl MessageReader { // which would bear some risk of getting out of sync. self.r#async.as_mut().unwrap().alias_mut().buf.set_len(buf_len); - let result = if ok == winapi::FALSE { + let result = if ok == winapi::shared::minwindef::FALSE { Err(GetLastError()) } else { Ok(()) @@ -595,10 +619,10 @@ impl MessageReader { // handle is part of, meaning that we don't have to do any // special handling for sync-completed operations. Ok(()) | - Err(winapi::ERROR_IO_PENDING) => { + Err(winapi::shared::winerror::ERROR_IO_PENDING) => { Ok(()) }, - Err(winapi::ERROR_BROKEN_PIPE) => { + Err(winapi::shared::winerror::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); let async_data = self.r#async.take().unwrap().into_inner(); @@ -647,7 +671,7 @@ impl MessageReader { match io_result { Ok(()) => {} - Err(WinError::WindowsResult(winapi::ERROR_BROKEN_PIPE)) => { + Err(WinError::WindowsResult(winapi::shared::winerror::ERROR_BROKEN_PIPE)) => { // Remote end closed the channel. return Err(WinError::ChannelClosed); } @@ -655,7 +679,7 @@ impl MessageReader { } let nbytes = ov.InternalHigh as u32; - let offset = ov.Offset; + let offset = ov.u.s().Offset; assert!(offset == 0); @@ -689,16 +713,16 @@ impl MessageReader { // Get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; let block = match blocking_mode { - BlockingMode::Blocking => winapi::TRUE, - BlockingMode::Nonblocking => winapi::FALSE, + BlockingMode::Blocking => winapi::shared::minwindef::TRUE, + BlockingMode::Nonblocking => winapi::shared::minwindef::FALSE, }; - let ok = kernel32::GetOverlappedResult(self.r#async.as_ref().unwrap().alias().handle.as_raw(), - self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut(), + let ok = winapi::um::ioapiset::GetOverlappedResult(self.r#async.as_ref().unwrap().alias().handle.as_raw(), + &mut **self.r#async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); - let io_result = if ok == winapi::FALSE { + let io_result = if ok == winapi::shared::minwindef::FALSE { let err = GetLastError(); - if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { + if blocking_mode == BlockingMode::Nonblocking && err == winapi::shared::winerror::ERROR_IO_INCOMPLETE { // Async read hasn't completed yet. // Inform the caller, while keeping the read in flight. return Err(WinError::NoData); @@ -782,8 +806,8 @@ impl MessageReader { unsafe { assert!(self.entry_id.is_none()); - let completion_key = self.handle.as_raw() as winapi::ULONG_PTR; - let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), + let completion_key = self.handle.as_raw() as winapi::shared::basetsd::ULONG_PTR; + let ret = winapi::um::ioapiset::CreateIoCompletionPort(self.handle.as_raw(), iocp.as_raw(), completion_key, 0); @@ -819,14 +843,14 @@ impl MessageReader { // (i.e. before supplying the expected amount of data), // don't report that as a "sender closed" condition on the main channel: // rather, fail with the actual raw error code. - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); + return Err(WinError::from_system(winapi::shared::winerror::ERROR_BROKEN_PIPE, "ReadFile")); } Err(err) => return Err(err), Ok(()) => {} }; match self.fetch_async_result(BlockingMode::Blocking) { Err(WinError::ChannelClosed) => { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) + return Err(WinError::from_system(winapi::shared::winerror::ERROR_BROKEN_PIPE, "ReadFile")) } Err(err) => return Err(err), Ok(()) => {} @@ -864,12 +888,12 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), let mut sz: u32 = 0; let bytes_to_write = &bytes[written..]; unsafe { - if kernel32::WriteFile(handle.as_raw(), + if winapi::um::fileapi::WriteFile(handle.as_raw(), bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, &mut sz, ptr::null_mut()) - == winapi::FALSE + == winapi::shared::minwindef::FALSE { return Err(WinError::last("WriteFile")); } @@ -928,9 +952,9 @@ impl OsIpcReceiver { unsafe { // create the pipe server let handle = - kernel32::CreateNamedPipeA(pipe_name.as_ptr(), - winapi::PIPE_ACCESS_INBOUND | winapi::FILE_FLAG_OVERLAPPED, - winapi::PIPE_TYPE_BYTE | winapi::PIPE_READMODE_BYTE | winapi::PIPE_REJECT_REMOTE_CLIENTS, + winapi::um::winbase::CreateNamedPipeA(pipe_name.as_ptr(), + winapi::um::winbase::PIPE_ACCESS_INBOUND | winapi::um::winbase::FILE_FLAG_OVERLAPPED, + winapi::um::winbase::PIPE_TYPE_BYTE | winapi::um::winbase::PIPE_READMODE_BYTE | winapi::um::winbase::PIPE_REJECT_REMOTE_CLIENTS, // 1 max instance of this pipe 1, // out/in buffer sizes @@ -1016,14 +1040,14 @@ impl OsIpcReceiver { let handle = &reader_borrow.handle; // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... - let mut ov = AliasedCell::new(Box::new(mem::zeroed::())); - let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.alias_mut().deref_mut()); + let mut ov = AliasedCell::new(Box::new(mem::zeroed::())); + let ok = winapi::um::namedpipeapi::ConnectNamedPipe(handle.as_raw(), ov.alias_mut().deref_mut()); // we should always get FALSE with async IO - assert!(ok == winapi::FALSE); + assert!(ok == winapi::shared::minwindef::FALSE); let result = match GetLastError() { // did we successfully connect? (it's reported as an error [ok==false]) - winapi::ERROR_PIPE_CONNECTED => { + winapi::shared::winerror::ERROR_PIPE_CONNECTED => { win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle.as_raw()); Ok(()) }, @@ -1033,16 +1057,16 @@ impl OsIpcReceiver { // a Connect here will get ERROR_NO_DATA -- but there may be data in // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. - winapi::ERROR_NO_DATA => { + winapi::shared::winerror::ERROR_NO_DATA => { win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle.as_raw()); Ok(()) }, // the connect is pending; wait for it to complete - winapi::ERROR_IO_PENDING => { + winapi::shared::winerror::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.alias_mut().deref_mut(), &mut nbytes, winapi::TRUE); - if ok == winapi::FALSE { + let ok = winapi::um::ioapiset::GetOverlappedResult(handle.as_raw(), ov.alias_mut().deref_mut(), &mut nbytes, winapi::shared::minwindef::TRUE); + if ok == winapi::shared::minwindef::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } Ok(()) @@ -1106,12 +1130,12 @@ impl OsIpcSender { fn connect_named(pipe_name: &CString) -> Result { unsafe { let handle = - kernel32::CreateFileA(pipe_name.as_ptr(), - winapi::GENERIC_WRITE, + winapi::um::fileapi::CreateFileA(pipe_name.as_ptr(), + winapi::um::winnt::GENERIC_WRITE, 0, ptr::null_mut(), // lpSecurityAttributes - winapi::OPEN_EXISTING, - winapi::FILE_ATTRIBUTE_NORMAL, + winapi::um::fileapi::OPEN_EXISTING, + winapi::um::winnt::FILE_ATTRIBUTE_NORMAL, ptr::null_mut()); if handle == INVALID_HANDLE_VALUE { return Err(WinError::last("CreateFileA")); @@ -1123,26 +1147,26 @@ impl OsIpcSender { } } - fn get_pipe_server_process_id(&self) -> Result { + fn get_pipe_server_process_id(&self) -> Result { unsafe { - let mut server_pid: winapi::ULONG = 0; - if kernel32::GetNamedPipeServerProcessId(self.handle.as_raw(), &mut server_pid) == winapi::FALSE { + let mut server_pid: winapi::shared::ntdef::ULONG = 0; + if winapi::um::winbase::GetNamedPipeServerProcessId(self.handle.as_raw(), &mut server_pid) == winapi::shared::minwindef::FALSE { return Err(WinError::last("GetNamedPipeServerProcessId")); } Ok(server_pid) } } - fn get_pipe_server_process_handle_and_pid(&self) -> Result<(WinHandle, winapi::ULONG),WinError> { + fn get_pipe_server_process_handle_and_pid(&self) -> Result<(WinHandle, winapi::shared::ntdef::ULONG),WinError> { unsafe { let server_pid = self.get_pipe_server_process_id()?; if server_pid == *CURRENT_PROCESS_ID { return Ok((WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw()), server_pid)); } - let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, - winapi::FALSE, - server_pid as winapi::DWORD); + let raw_handle = winapi::um::processthreadsapi::OpenProcess(winapi::um::winnt::PROCESS_DUP_HANDLE, + winapi::shared::minwindef::FALSE, + server_pid as winapi::shared::minwindef::DWORD); if raw_handle.is_null() { return Err(WinError::last("OpenProcess")); } @@ -1326,9 +1350,9 @@ impl Drop for OsIpcReceiverSet { impl OsIpcReceiverSet { pub fn new() -> Result { unsafe { - let iocp = kernel32::CreateIoCompletionPort(INVALID_HANDLE_VALUE, + let iocp = winapi::um::ioapiset::CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), - 0 as winapi::ULONG_PTR, + 0 as winapi::shared::basetsd::ULONG_PTR, 0); if iocp.is_null() { return Err(WinError::last("CreateIoCompletionPort")); @@ -1389,16 +1413,16 @@ impl OsIpcReceiverSet { fn fetch_iocp_result(&mut self) -> Result<(MessageReader, Result<(), WinError>), WinError> { unsafe { let mut nbytes: u32 = 0; - let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; - let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); + let mut completion_key = INVALID_HANDLE_VALUE as winapi::shared::basetsd::ULONG_PTR; + let mut ov_ptr: *mut winapi::um::minwinbase::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! - let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), + let ok = winapi::um::ioapiset::GetQueuedCompletionStatus(self.iocp.as_raw(), &mut nbytes, &mut completion_key, &mut ov_ptr, - winapi::INFINITE); + winapi::um::winbase::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - let io_result = if ok == winapi::FALSE { + let io_result = if ok == winapi::shared::minwindef::FALSE { let err = WinError::last("GetQueuedCompletionStatus"); // If the OVERLAPPED result is NULL, then the @@ -1415,13 +1439,13 @@ impl OsIpcReceiverSet { }; assert!(!ov_ptr.is_null()); - assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); + assert!(completion_key != INVALID_HANDLE_VALUE as winapi::shared::basetsd::ULONG_PTR); // Find the matching receiver let (reader_index, _) = self.readers.iter().enumerate() .find(|&(_, ref reader)| { let raw_handle = reader.r#async.as_ref().unwrap().alias().handle.as_raw(); - raw_handle as winapi::ULONG_PTR == completion_key + raw_handle as winapi::shared::basetsd::ULONG_PTR == completion_key }) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); @@ -1528,7 +1552,7 @@ unsafe impl Sync for OsIpcSharedMemory {} impl Drop for OsIpcSharedMemory { fn drop(&mut self) { unsafe { - let result = kernel32::UnmapViewOfFile(self.ptr as LPVOID); + let result = winapi::um::memoryapi::UnmapViewOfFile(self.ptr as LPVOID); assert!(thread::panicking() || result != 0); } } @@ -1572,9 +1596,9 @@ impl OsIpcSharedMemory { let (lhigh, llow) = (length.checked_shr(32).unwrap_or(0) as u32, (length & 0xffffffff) as u32); let handle = - kernel32::CreateFileMappingA(INVALID_HANDLE_VALUE, + winapi::um::winbase::CreateFileMappingA(INVALID_HANDLE_VALUE, ptr::null_mut(), - winapi::PAGE_READWRITE | winapi::SEC_COMMIT, + winapi::um::winnt::PAGE_READWRITE | winapi::um::winnt::SEC_COMMIT, lhigh, llow, ptr::null_mut()); if handle == INVALID_HANDLE_VALUE { @@ -1593,8 +1617,8 @@ impl OsIpcSharedMemory { // when finished. fn from_handle(handle: WinHandle, length: usize) -> Result { unsafe { - let address = kernel32::MapViewOfFile(handle.as_raw(), - winapi::FILE_MAP_ALL_ACCESS, + let address = winapi::um::memoryapi::MapViewOfFile(handle.as_raw(), + winapi::um::memoryapi::FILE_MAP_ALL_ACCESS, 0, 0, 0); if address.is_null() { return Err(WinError::last("MapViewOfFile")); @@ -1705,21 +1729,21 @@ impl WinError { pub fn error_string(errnum: u32) -> String { // This value is calculated from the macro // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) - let lang_id = 0x0800 as winapi::DWORD; - let mut buf = [0 as winapi::WCHAR; 2048]; + let lang_id = 0x0800 as winapi::shared::minwindef::DWORD; + let mut buf = [0 as winapi::um::winnt::WCHAR; 2048]; unsafe { - let res = kernel32::FormatMessageW(winapi::FORMAT_MESSAGE_FROM_SYSTEM | - winapi::FORMAT_MESSAGE_IGNORE_INSERTS, + let res = winapi::um::winbase::FormatMessageW(winapi::um::winbase::FORMAT_MESSAGE_FROM_SYSTEM | + winapi::um::winbase::FORMAT_MESSAGE_IGNORE_INSERTS, ptr::null_mut(), - errnum as winapi::DWORD, + errnum as winapi::shared::minwindef::DWORD, lang_id, buf.as_mut_ptr(), - buf.len() as winapi::DWORD, + buf.len() as winapi::shared::minwindef::DWORD, ptr::null_mut()) as usize; if res == 0 { // Sometimes FormatMessageW can fail e.g. system doesn't like lang_id, - let fm_err = kernel32::GetLastError(); + let fm_err = winapi::um::errhandlingapi::GetLastError(); return format!("OS Error {} (FormatMessageW() returned error {})", errnum, fm_err); } @@ -1762,7 +1786,7 @@ impl From for Error { // This is the error code we originally got from the Windows API // to signal the "channel closed" (no sender) condition -- // so hand it back to the Windows API to create an appropriate `Error` value. - Error::from_raw_os_error(winapi::ERROR_BROKEN_PIPE as i32) + Error::from_raw_os_error(winapi::shared::winerror::ERROR_BROKEN_PIPE as i32) }, WinError::NoData => { Error::new(ErrorKind::WouldBlock, "Win channel has no data available") From 24a22072d83ded9ed86999bb51eac8038d4628a1 Mon Sep 17 00:00:00 2001 From: Angel Date: Wed, 3 Jul 2019 10:35:07 -0500 Subject: [PATCH 113/113] Test of GetNamedPipeClientSessionId() and GetNamedPipeClientSessionId() apis to verify if self.h and other.h handles point to the same object. --- Cargo.toml | 2 +- src/platform/windows/mod.rs | 64 +++++++++++++++++++++++++++++++++---- src/test.rs | 4 +-- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b89e939de..101f35b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,4 @@ sc = { version = "0.2.2", optional = true } crossbeam = "0.2" [target.'cfg(target_os = "windows")'.dependencies] -winapi = {version = "0.3.7", features = ["ioapiset", "memoryapi", "namedpipeapi", "handleapi"]} +winapi = {version = "0.3.7", features = ["minwindef", "ioapiset", "memoryapi", "namedpipeapi", "handleapi", "fileapi", "impl-default"]} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 65f6af519..9f360396d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -27,6 +27,7 @@ use uuid::Uuid; use winapi::um::winnt::{HANDLE}; use winapi::um::handleapi::{INVALID_HANDLE_VALUE}; use winapi::shared::minwindef::{LPVOID}; +use winapi::um::fileapi::{BY_HANDLE_FILE_INFORMATION}; use winapi; use std::fmt; @@ -38,6 +39,9 @@ lazy_static! { static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { winapi::um::processthreadsapi::GetCurrentProcess() }); } +// Added to overcome build error where Box was used and +// struct had a trait of #[derive(Debug)]. Adding NoDebug<> overrode the Debug() trait. +// e.g. - NoDebug>, struct NoDebug(T); impl Deref for NoDebug { @@ -54,7 +58,7 @@ impl DerefMut for NoDebug { } impl fmt::Debug for NoDebug { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { Ok(()) } } @@ -319,17 +323,65 @@ impl Default for WinHandle { impl PartialEq for WinHandle { fn eq(&self, other: &WinHandle) -> bool { - // FIXME This does not actually implement the desired behaviour: - // we want a way to compare the underlying objects the handles refer to, - // rather than just comparing the handles. + // WPre-Windows 10 API GetFileInformationByHandle() is used to + // to compare the underlying objects the handles refer to, // // On Windows 10, we could use: // ``` - // unsafe { winapi::um:handleapi::CompareObjectHandles(self.h, other.h) == winapi::shared::minwindef::TRUE } + // unsafe { winapi::um::handleapi::CompareObjectHandles(self.h, other.h) == winapi::shared::minwindef::TRUE } // ``` // // This API call however is not available on older versions. - self.h == other.h + + // Initialize variables needed by GetFileInformationByHandle(). + let mut _handle_info1: BY_HANDLE_FILE_INFORMATION = Default::default(); + let mut _handle_info2: BY_HANDLE_FILE_INFORMATION = Default::default(); + let mut _h1: winapi::shared::minwindef::BOOL = Default::default(); + let mut _h2: winapi::shared::minwindef::BOOL = Default::default(); + + // for testing and debugging numerous APIs to determine if shmem_handles + // point to the same shared memory. + let mut _h3: winapi::shared::minwindef::BOOL = Default::default(); + let mut _h4: winapi::shared::minwindef::BOOL = Default::default(); + let mut _svrsessionid: winapi::shared::minwindef::ULONG = Default::default(); + let mut _clntsessionid: winapi::shared::minwindef::ULONG = Default::default(); + + unsafe { + //_h1 = winapi::um::fileapi::GetFileInformationByHandle(self.h, &mut _handle_info1); + //_h2 = winapi::um::fileapi::GetFileInformationByHandle(other.h, &mut _handle_info2); + + // need windowsapp.lib _h3 = winapi::um::handleapi::CompareObjectHandles(self.h, other.h); + + _h1 = winapi::um::winbase::GetNamedPipeServerSessionId(other.h, &mut _svrsessionid); + _h2 = winapi::um::winbase::GetNamedPipeClientSessionId(self.h, &mut _clntsessionid); + + println!("GetNamedPipeServer/ClientSessionID return values and GetLastError Information."); + println!("Getlasterror = {}", GetLastError()); + println!("_h1 = {} andd GetNamedPipeServerSessionID = {}", _h1, _svrsessionid); + println!("_h2 = {} andd GetNamedPipeClientSessionId = {}", _h2, _clntsessionid); + } + + // If the 2 handles self.h and other.h have different values, but point to the same + // memory region/file serial number then the handles are just duplicates. + // + // Per Microsoft you need to check the FileIndexHigh, FileIndexLow, and + // dwVolumeSerialNumber for both handles' BY_HANDLE_FILE_INFORMATION structures + // to verify if the 2 handles point to the same object. + // + // see https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/ns-fileapi-_by_handle_file_information + // println!("GetlastError() = {}", GetLastError()); + // println!("Value of _h3 = {}", _h3); + + //println!("Value of _h1 = {}, Value of _h2 = {}", _h1, _h2); + //println!("Handle1 = {:?}, Handle2 = {:?}", self.h, other.h); + //println!("handle1 serial# = {}, handle2 serial# = {}", _handle_info1.dwVolumeSerialNumber, _handle_info2.dwVolumeSerialNumber); + //println!("FileIndexHigh Vaules: {} and {} ", _handle_info1.nFileIndexHigh, _handle_info2.nFileIndexHigh); + //println!("FileIndexLow Vaules: {} and {} ", _handle_info1.nFileIndexLow, _handle_info2.nFileIndexLow); + + (_h1 > 0) && (_h2 > 0) + //(_handle_info1.dwVolumeSerialNumber == _handle_info2.dwVolumeSerialNumber) && + //(_handle_info1.nFileIndexHigh == _handle_info2.nFileIndexHigh) && + //(_handle_info1.nFileIndexLow == _handle_info2.nFileIndexLow) } } diff --git a/src/test.rs b/src/test.rs index 9d7216eef..db1ef1c5e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -38,7 +38,7 @@ use std::process::{self, Command, Stdio}; target_os = "android", target_os = "ios" )))] -use std::ptr; + use std::sync::Arc; use std::thread; @@ -47,14 +47,12 @@ use std::thread; target_os = "android", target_os = "ios" )))] -use crate::ipc::IpcOneShotServer; #[cfg(not(any( feature = "force-inprocess", target_os = "android", target_os = "ios" )))] -use std::io::Error; #[cfg(not(any( feature = "force-inprocess",