forked from mirrors/gecko-dev
Bug 1809843 - Make Http/3 server support proxy mode, r=necko-reviewers,valentin,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D166642
This commit is contained in:
parent
25f19106d7
commit
226e8a456f
15 changed files with 1917 additions and 11 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -2601,6 +2601,8 @@ version = "0.1.1"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"bindgen 0.63.0",
|
||||
"http",
|
||||
"hyper",
|
||||
"log",
|
||||
"mio 0.6.23",
|
||||
"mio-extras",
|
||||
|
|
@ -2609,6 +2611,7 @@ dependencies = [
|
|||
"neqo-http3",
|
||||
"neqo-qpack",
|
||||
"neqo-transport",
|
||||
"tokio 1.17.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4972,6 +4975,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
|
|
@ -5467,7 +5479,10 @@ dependencies = [
|
|||
"memchr",
|
||||
"mio 0.8.0",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.999",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ mio = "0.6.17"
|
|||
mio-extras = "2.0.5"
|
||||
log = "0.4.0"
|
||||
base64 = "0.13"
|
||||
http = "0.2.8"
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[dependencies.neqo-crypto]
|
||||
tag = "v0.6.4"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ use neqo_http3::{
|
|||
};
|
||||
use neqo_transport::server::Server;
|
||||
use neqo_transport::{
|
||||
ConnectionEvent, ConnectionParameters, Output, RandomConnectionIdGenerator, StreamType,
|
||||
ConnectionEvent, ConnectionParameters, Output, RandomConnectionIdGenerator, StreamId,
|
||||
StreamType,
|
||||
};
|
||||
use std::env;
|
||||
|
||||
|
|
@ -23,10 +24,14 @@ use std::io;
|
|||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{channel, Receiver, TryRecvError};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use core::fmt::Display;
|
||||
use hyper::body::HttpBody;
|
||||
use hyper::header::{HeaderName, HeaderValue};
|
||||
use hyper::{Body, Client, Method, Request};
|
||||
use mio::net::UdpSocket;
|
||||
use mio::{Events, Poll, PollOpt, Ready, Token};
|
||||
use mio_extras::timer::{Builder, Timeout, Timer};
|
||||
|
|
@ -487,7 +492,7 @@ impl HttpServer for Http3TestServer {
|
|||
Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed {
|
||||
session,
|
||||
reason,
|
||||
headers: _
|
||||
headers: _,
|
||||
}) => {
|
||||
qdebug!(
|
||||
"WebTransportServerEvent::SessionClosed {:?} {:?}",
|
||||
|
|
@ -552,6 +557,301 @@ impl HttpServer for Server {
|
|||
}
|
||||
}
|
||||
|
||||
struct Http3ProxyServer {
|
||||
server: Http3Server,
|
||||
responses: HashMap<Http3OrWebTransportStream, Vec<u8>>,
|
||||
server_port: i32,
|
||||
request_header: HashMap<StreamId, Vec<Header>>,
|
||||
request_body: HashMap<StreamId, Vec<u8>>,
|
||||
stream_map: HashMap<StreamId, Http3OrWebTransportStream>,
|
||||
response_to_send: HashMap<StreamId, Receiver<(Vec<Header>, Vec<u8>)>>,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for Http3ProxyServer {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
write!(f, "{}", self.server)
|
||||
}
|
||||
}
|
||||
|
||||
impl Http3ProxyServer {
|
||||
pub fn new(server: Http3Server, server_port: i32) -> Self {
|
||||
Self {
|
||||
server,
|
||||
responses: HashMap::new(),
|
||||
server_port,
|
||||
request_header: HashMap::new(),
|
||||
request_body: HashMap::new(),
|
||||
stream_map: HashMap::new(),
|
||||
response_to_send: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_response(&mut self, mut stream: Http3OrWebTransportStream, mut data: Vec<u8>) {
|
||||
if data.len() == 0 {
|
||||
let _ = stream.stream_close_send();
|
||||
return;
|
||||
}
|
||||
match stream.send_data(&data) {
|
||||
Ok(sent) => {
|
||||
if sent < data.len() {
|
||||
self.responses.insert(stream, data.split_off(sent));
|
||||
} else {
|
||||
stream.stream_close_send().unwrap();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("error is {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_stream_writable(&mut self, mut stream: Http3OrWebTransportStream) {
|
||||
if let Some(data) = self.responses.get_mut(&stream) {
|
||||
match stream.send_data(&data) {
|
||||
Ok(sent) => {
|
||||
if sent < data.len() {
|
||||
let new_d = (*data).split_off(sent);
|
||||
*data = new_d;
|
||||
} else {
|
||||
stream.stream_close_send().unwrap();
|
||||
self.responses.remove(&stream);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("Unexpected error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_url(
|
||||
request: hyper::Request<Body>,
|
||||
out_header: &mut Vec<Header>,
|
||||
out_body: &mut Vec<u8>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = Client::new();
|
||||
let mut resp = client.request(request).await?;
|
||||
out_header.push(Header::new(":status", resp.status().as_str()));
|
||||
for (key, value) in resp.headers() {
|
||||
out_header.push(Header::new(
|
||||
key.as_str().to_ascii_lowercase(),
|
||||
match value.to_str() {
|
||||
Ok(str) => str,
|
||||
_ => "",
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
while let Some(chunk) = resp.body_mut().data().await {
|
||||
match chunk {
|
||||
Ok(data) => {
|
||||
out_body.append(&mut data.to_vec());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch(
|
||||
&mut self,
|
||||
mut stream: Http3OrWebTransportStream,
|
||||
request_headers: &Vec<Header>,
|
||||
request_body: Vec<u8>,
|
||||
) {
|
||||
let mut request: hyper::Request<Body> = Request::default();
|
||||
let mut path = String::new();
|
||||
for hdr in request_headers.iter() {
|
||||
match hdr.name() {
|
||||
":method" => {
|
||||
*request.method_mut() = Method::from_bytes(hdr.value().as_bytes()).unwrap();
|
||||
}
|
||||
":scheme" => {}
|
||||
":authority" => {
|
||||
request.headers_mut().insert(
|
||||
hyper::header::HOST,
|
||||
HeaderValue::from_str(hdr.value()).unwrap(),
|
||||
);
|
||||
}
|
||||
":path" => {
|
||||
path = String::from(hdr.value());
|
||||
}
|
||||
_ => {
|
||||
if let Ok(hdr_name) = HeaderName::from_lowercase(hdr.name().as_bytes()) {
|
||||
request
|
||||
.headers_mut()
|
||||
.insert(hdr_name, HeaderValue::from_str(hdr.value()).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*request.body_mut() = Body::from(request_body);
|
||||
*request.uri_mut() =
|
||||
match format!("http://127.0.0.1:{}{}", self.server_port.to_string(), path).parse() {
|
||||
Ok(uri) => uri,
|
||||
_ => {
|
||||
eprintln!("invalid uri: {}", path);
|
||||
stream
|
||||
.send_headers(&[
|
||||
Header::new(":status", "400"),
|
||||
Header::new("cache-control", "no-cache"),
|
||||
Header::new("content-length", "0"),
|
||||
])
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
qtrace!("request header: {:?}", request);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut h: Vec<Header> = Vec::new();
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
let _ = rt.block_on(Self::fetch_url(request, &mut h, &mut data));
|
||||
qtrace!("response headers: {:?}", h);
|
||||
qtrace!("res data: {:02X?}", data);
|
||||
|
||||
match sender.send((h, data)) {
|
||||
Ok(()) => {}
|
||||
_ => {
|
||||
eprintln!("sender.send failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.response_to_send.insert(stream.stream_id(), receiver);
|
||||
self.stream_map.insert(stream.stream_id(), stream);
|
||||
}
|
||||
|
||||
fn maybe_process_response(&mut self) {
|
||||
let mut data_to_send = HashMap::new();
|
||||
self.response_to_send
|
||||
.retain(|id, receiver| match receiver.try_recv() {
|
||||
Ok((headers, body)) => {
|
||||
data_to_send.insert(*id, (headers.clone(), body.clone()));
|
||||
false
|
||||
}
|
||||
Err(TryRecvError::Empty) => true,
|
||||
Err(TryRecvError::Disconnected) => false,
|
||||
});
|
||||
while let Some(id) = data_to_send.keys().next().cloned() {
|
||||
let mut stream = self.stream_map.remove(&id).unwrap();
|
||||
let (header, data) = data_to_send.remove(&id).unwrap();
|
||||
qtrace!("response headers: {:?}", header);
|
||||
match stream.send_headers(&header) {
|
||||
Ok(()) => {
|
||||
self.new_response(stream, data);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpServer for Http3ProxyServer {
|
||||
fn process(&mut self, dgram: Option<Datagram>) -> Output {
|
||||
self.server.process(dgram, Instant::now())
|
||||
}
|
||||
|
||||
fn process_events(&mut self) {
|
||||
self.maybe_process_response();
|
||||
while let Some(event) = self.server.next_event() {
|
||||
qtrace!("Event: {:?}", event);
|
||||
match event {
|
||||
Http3ServerEvent::Headers {
|
||||
mut stream,
|
||||
headers,
|
||||
fin: _,
|
||||
} => {
|
||||
qtrace!("Headers {:?}", headers);
|
||||
if self.server_port != -1 {
|
||||
let method_hdr = headers.iter().find(|&h| h.name() == ":method");
|
||||
match method_hdr {
|
||||
Some(method) => match method.value() {
|
||||
"POST" => {
|
||||
let content_length =
|
||||
headers.iter().find(|&h| h.name() == "content-length");
|
||||
if let Some(length_str) = content_length {
|
||||
if let Ok(len) = length_str.value().parse::<u32>() {
|
||||
if len > 0 {
|
||||
self.request_header
|
||||
.insert(stream.stream_id(), headers);
|
||||
self.request_body
|
||||
.insert(stream.stream_id(), Vec::new());
|
||||
} else {
|
||||
self.fetch(stream, &headers, b"".to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.fetch(stream, &headers, b"".to_vec());
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
let path_hdr = headers.iter().find(|&h| h.name() == ":path");
|
||||
match path_hdr {
|
||||
Some(ph) if !ph.value().is_empty() => {
|
||||
let path = ph.value();
|
||||
match &path[..6] {
|
||||
"/port?" => {
|
||||
let port = path[6..].parse::<i32>();
|
||||
if let Ok(port) = port {
|
||||
qtrace!("got port {}", port);
|
||||
self.server_port = port;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
stream
|
||||
.send_headers(&[
|
||||
Header::new(":status", "200"),
|
||||
Header::new("cache-control", "no-cache"),
|
||||
Header::new("content-length", "0"),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Http3ServerEvent::Data {
|
||||
stream,
|
||||
mut data,
|
||||
fin,
|
||||
} => {
|
||||
if let Some(d) = self.request_body.get_mut(&stream.stream_id()) {
|
||||
d.append(&mut data);
|
||||
}
|
||||
if fin {
|
||||
if let Some(d) = self.request_body.remove(&stream.stream_id()) {
|
||||
let headers = self.request_header.remove(&stream.stream_id()).unwrap();
|
||||
self.fetch(stream, &headers, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
Http3ServerEvent::DataWritable { stream } => self.handle_stream_writable(stream),
|
||||
Http3ServerEvent::StateChange { .. } | Http3ServerEvent::PriorityUpdate { .. } => {}
|
||||
Http3ServerEvent::StreamReset { stream, error } => {
|
||||
qtrace!("Http3ServerEvent::StreamReset {:?} {:?}", stream, error);
|
||||
}
|
||||
Http3ServerEvent::StreamStopSending { stream, error } => {
|
||||
qtrace!(
|
||||
"Http3ServerEvent::StreamStopSending {:?} {:?}",
|
||||
stream,
|
||||
error
|
||||
);
|
||||
}
|
||||
Http3ServerEvent::WebTransport(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NonRespondingServer {}
|
||||
|
||||
|
|
@ -605,6 +905,9 @@ fn process(
|
|||
}
|
||||
|
||||
qinfo!("Setting timeout of {:?} for {}", new_timeout, server);
|
||||
if new_timeout > Duration::from_secs(1) {
|
||||
new_timeout = Duration::from_secs(1);
|
||||
}
|
||||
*svr_timeout = Some(timer.set_timeout(new_timeout, inx));
|
||||
false
|
||||
}
|
||||
|
|
@ -646,6 +949,7 @@ enum ServerType {
|
|||
Http3Fail,
|
||||
Http3NoResponse,
|
||||
Http3Ech,
|
||||
Http3Proxy,
|
||||
}
|
||||
|
||||
struct ServersRunner {
|
||||
|
|
@ -677,14 +981,16 @@ impl ServersRunner {
|
|||
self.add_new_socket(0, ServerType::Http3);
|
||||
self.add_new_socket(1, ServerType::Http3Fail);
|
||||
self.add_new_socket(2, ServerType::Http3Ech);
|
||||
self.add_new_socket(4, ServerType::Http3NoResponse);
|
||||
self.add_new_socket(3, ServerType::Http3Proxy);
|
||||
self.add_new_socket(5, ServerType::Http3NoResponse);
|
||||
|
||||
println!(
|
||||
"HTTP3 server listening on ports {}, {}, {} and {}. EchConfig is @{}@",
|
||||
"HTTP3 server listening on ports {}, {}, {}, {} and {}. EchConfig is @{}@",
|
||||
self.hosts[0].port(),
|
||||
self.hosts[1].port(),
|
||||
self.hosts[2].port(),
|
||||
self.hosts[3].port(),
|
||||
self.hosts[4].port(),
|
||||
base64::encode(&self.ech_config)
|
||||
);
|
||||
self.poll
|
||||
|
|
@ -788,6 +1094,34 @@ impl ServersRunner {
|
|||
self.ech_config = Vec::from(unboxed_server.ech_config());
|
||||
server
|
||||
}
|
||||
ServerType::Http3Proxy => {
|
||||
let server_config = if env::var("MOZ_HTTP3_MOCHITEST").is_ok() {
|
||||
("mochitest-cert", 8888)
|
||||
} else {
|
||||
(" HTTP2 Test Cert", -1)
|
||||
};
|
||||
let server = Box::new(Http3ProxyServer::new(
|
||||
Http3Server::new(
|
||||
Instant::now(),
|
||||
&[server_config.0],
|
||||
PROTOCOLS,
|
||||
anti_replay,
|
||||
cid_mgr,
|
||||
Http3Parameters::default()
|
||||
.max_table_size_encoder(MAX_TABLE_SIZE)
|
||||
.max_table_size_decoder(MAX_TABLE_SIZE)
|
||||
.max_blocked_streams(MAX_BLOCKED_STREAMS)
|
||||
.webtransport(true)
|
||||
.connection_parameters(
|
||||
ConnectionParameters::default().datagram_size(1200),
|
||||
),
|
||||
None,
|
||||
)
|
||||
.expect("We cannot make a server!"),
|
||||
server_config.1,
|
||||
));
|
||||
server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -874,10 +1208,10 @@ impl ServersRunner {
|
|||
if event.token() == TIMER_TOKEN {
|
||||
self.process_timeout()?;
|
||||
} else {
|
||||
if !event.readiness().is_readable() {
|
||||
continue;
|
||||
}
|
||||
self.process_datagrams_and_events(event.token().0, true)?;
|
||||
self.process_datagrams_and_events(
|
||||
event.token().0,
|
||||
event.readiness().is_readable(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
self.process_active_conns()?;
|
||||
|
|
|
|||
172
netwerk/test/unit/test_http3_server.js
Normal file
172
netwerk/test/unit/test_http3_server.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* import-globals-from head_servers.js */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
let h2Port;
|
||||
let h3Port;
|
||||
let trrServer;
|
||||
|
||||
const certOverrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
add_setup(async function setup() {
|
||||
h2Port = Services.env.get("MOZHTTP2_PORT");
|
||||
Assert.notEqual(h2Port, null);
|
||||
Assert.notEqual(h2Port, "");
|
||||
|
||||
h3Port = Services.env.get("MOZHTTP3_PORT_PROXY");
|
||||
Assert.notEqual(h3Port, null);
|
||||
Assert.notEqual(h3Port, "");
|
||||
|
||||
trr_test_setup();
|
||||
|
||||
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
|
||||
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
trr_clear_prefs();
|
||||
Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
|
||||
Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
|
||||
Services.prefs.clearUserPref("network.dns.disablePrefetch");
|
||||
await trrServer.stop();
|
||||
});
|
||||
|
||||
if (mozinfo.socketprocess_networking) {
|
||||
Services.dns;
|
||||
await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
|
||||
}
|
||||
});
|
||||
|
||||
function makeChan(url) {
|
||||
let chan = NetUtil.newChannel({
|
||||
uri: url,
|
||||
loadUsingSystemPrincipal: true,
|
||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
||||
}).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
function channelOpenPromise(chan, flags) {
|
||||
return new Promise(resolve => {
|
||||
function finish(req, buffer) {
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
false
|
||||
);
|
||||
resolve([req, buffer]);
|
||||
}
|
||||
let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
internal.setWaitForHTTPSSVCRecord();
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
true
|
||||
);
|
||||
chan.asyncOpen(new ChannelListener(finish, null, flags));
|
||||
});
|
||||
}
|
||||
|
||||
// Use NodeHTTPServer to create an HTTP server and test if the Http/3 server
|
||||
// can act as a reverse proxy.
|
||||
add_task(async function testHttp3ServerAsReverseProxy() {
|
||||
trrServer = new TRRServer();
|
||||
await trrServer.start();
|
||||
Services.dns.clearCache(true);
|
||||
Services.prefs.setIntPref("network.trr.mode", 3);
|
||||
Services.prefs.setCharPref(
|
||||
"network.trr.uri",
|
||||
`https://foo.example.com:${trrServer.port}/dns-query`
|
||||
);
|
||||
|
||||
await trrServer.registerDoHAnswers("test.h3_example.com", "HTTPS", {
|
||||
answers: [
|
||||
{
|
||||
name: "test.h3_example.com",
|
||||
ttl: 55,
|
||||
type: "HTTPS",
|
||||
flush: false,
|
||||
data: {
|
||||
priority: 1,
|
||||
name: "test.h3_example.com",
|
||||
values: [
|
||||
{ key: "alpn", value: "h3-29" },
|
||||
{ key: "port", value: h3Port },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await trrServer.registerDoHAnswers("test.h3_example.com", "A", {
|
||||
answers: [
|
||||
{
|
||||
name: "test.h3_example.com",
|
||||
ttl: 55,
|
||||
type: "A",
|
||||
flush: false,
|
||||
data: "127.0.0.1",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await new TRRDNSListener("test.h3_example.com", {
|
||||
type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
|
||||
});
|
||||
|
||||
let server = new NodeHTTPServer();
|
||||
await server.start();
|
||||
registerCleanupFunction(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
await server.registerPathHandler("/test", (req, resp) => {
|
||||
if (req.method === "GET") {
|
||||
resp.writeHead(200);
|
||||
resp.end("got GET request");
|
||||
} else if (req.method === "POST") {
|
||||
let received = "";
|
||||
req.on("data", function receivePostData(chunk) {
|
||||
received += chunk.toString();
|
||||
});
|
||||
req.on("end", function finishPost() {
|
||||
resp.writeHead(200);
|
||||
resp.end(received);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Tell the Http/3 server which port to forward requests.
|
||||
let chan = makeChan(`https://test.h3_example.com/port?${server.port()}`);
|
||||
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
|
||||
// Test GET method.
|
||||
chan = makeChan(`https://test.h3_example.com/test`);
|
||||
let [req, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
Assert.equal(req.protocolVersion, "h3-29");
|
||||
Assert.equal(buf, "got GET request");
|
||||
|
||||
var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
||||
Ci.nsIStringInputStream
|
||||
);
|
||||
stream.data = "b".repeat(500);
|
||||
|
||||
// Test POST method.
|
||||
chan = makeChan(`https://test.h3_example.com/test`);
|
||||
chan
|
||||
.QueryInterface(Ci.nsIUploadChannel)
|
||||
.setUploadStream(stream, "text/plain", stream.available());
|
||||
chan.requestMethod = "POST";
|
||||
|
||||
[req, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
Assert.equal(req.protocolVersion, "h3-29");
|
||||
Assert.equal(buf, stream.data);
|
||||
|
||||
await trrServer.stop();
|
||||
});
|
||||
|
|
@ -758,3 +758,9 @@ run-sequentially = http3server
|
|||
skip-if = verify
|
||||
run-sequentially = node server exceptions dont replay well
|
||||
[test_trr_noPrefetch.js]
|
||||
[test_http3_server.js]
|
||||
skip-if =
|
||||
verify
|
||||
os == 'android'
|
||||
os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1808049
|
||||
run-sequentially = node server exceptions dont replay well
|
||||
|
|
|
|||
|
|
@ -1095,6 +1095,10 @@ criteria = "safe-to-deploy"
|
|||
version = "1.1.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.signal-hook-registry]]
|
||||
version = "1.4.1"
|
||||
criteria = "safe-to-run"
|
||||
|
||||
[[exemptions.siphasher]]
|
||||
version = "0.3.10"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
|||
|
|
@ -1462,7 +1462,7 @@ class XPCShellTests(object):
|
|||
msg = process.stdout.readline()
|
||||
if "server listening" in msg:
|
||||
searchObj = re.search(
|
||||
r"HTTP3 server listening on ports ([0-9]+), ([0-9]+), ([0-9]+) and ([0-9]+)."
|
||||
r"HTTP3 server listening on ports ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+) and ([0-9]+)."
|
||||
" EchConfig is @([\x00-\x7F]+)@",
|
||||
msg,
|
||||
0,
|
||||
|
|
@ -1471,8 +1471,9 @@ class XPCShellTests(object):
|
|||
self.env["MOZHTTP3_PORT"] = searchObj.group(1)
|
||||
self.env["MOZHTTP3_PORT_FAILED"] = searchObj.group(2)
|
||||
self.env["MOZHTTP3_PORT_ECH"] = searchObj.group(3)
|
||||
self.env["MOZHTTP3_PORT_NO_RESPONSE"] = searchObj.group(4)
|
||||
self.env["MOZHTTP3_ECH"] = searchObj.group(5)
|
||||
self.env["MOZHTTP3_PORT_PROXY"] = searchObj.group(4)
|
||||
self.env["MOZHTTP3_PORT_NO_RESPONSE"] = searchObj.group(5)
|
||||
self.env["MOZHTTP3_ECH"] = searchObj.group(6)
|
||||
except OSError as e:
|
||||
# This occurs if the subprocess couldn't be started
|
||||
self.log.error("Could not run the http3 server: %s" % (str(e)))
|
||||
|
|
|
|||
1
third_party/rust/signal-hook-registry/.cargo-checksum.json
vendored
Normal file
1
third_party/rust/signal-hook-registry/.cargo-checksum.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"42fe4108f3d88108c7d65d951a56842f47644a8a4e2d6932c34b9a9fae098316","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"503558bfefe66ca15e4e3f7955b3cb0ec87fd52f29bf24b336af7bd00e946d5c","README.md":"56d8abee3efdf82aed1791a81feb60d9db987cedfa9c2e2a6b9451ac94bb88f7","src/half_lock.rs":"548fc2e283ef638a9f90e194d112a11c94205c87004d6da2e65eaa98f7a5a670","src/lib.rs":"1ed9cb19bfcde72036f983e2800ffa10c7ef4df0a0500bc697c49374260f1ac8","tests/unregister_signal.rs":"5baeebbde2bd7e63ae9a1f1e476d33f0bfd368c1edcdab3677de6a62b04f5118"},"package":"d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"}
|
||||
40
third_party/rust/signal-hook-registry/Cargo.toml
vendored
Normal file
40
third_party/rust/signal-hook-registry/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
authors = [
|
||||
"Michal 'vorner' Vaner <vorner@vorner.cz>",
|
||||
"Masaki Hara <ackie.h.gmai@gmail.com>",
|
||||
]
|
||||
description = "Backend crate for signal-hook"
|
||||
documentation = "https://docs.rs/signal-hook-registry"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"signal",
|
||||
"unix",
|
||||
"daemon",
|
||||
]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/vorner/signal-hook"
|
||||
|
||||
[dependencies.libc]
|
||||
version = "~0.2"
|
||||
|
||||
[dev-dependencies.signal-hook]
|
||||
version = "~0.3"
|
||||
|
||||
[badges.maintenance]
|
||||
status = "actively-developed"
|
||||
|
||||
[badges.travis-ci]
|
||||
repository = "vorner/signal-hook"
|
||||
201
third_party/rust/signal-hook-registry/LICENSE-APACHE
vendored
Normal file
201
third_party/rust/signal-hook-registry/LICENSE-APACHE
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
25
third_party/rust/signal-hook-registry/LICENSE-MIT
vendored
Normal file
25
third_party/rust/signal-hook-registry/LICENSE-MIT
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2017 tokio-jsonrpc developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
24
third_party/rust/signal-hook-registry/README.md
vendored
Normal file
24
third_party/rust/signal-hook-registry/README.md
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Signal-hook-registry
|
||||
|
||||
[](https://travis-ci.org/vorner/signal-hook)
|
||||
|
||||
This is the backend crate for the
|
||||
[signal-hook](https://crates.io/crates/signal-hook) crate. The general direct use of
|
||||
this crate is discouraged. See the
|
||||
[documentation](https://docs.rs/signal-hook-registry) for further details.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms
|
||||
or conditions.
|
||||
232
third_party/rust/signal-hook-registry/src/half_lock.rs
vendored
Normal file
232
third_party/rust/signal-hook-registry/src/half_lock.rs
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
//! The half-lock structure
|
||||
//!
|
||||
//! We need a way to protect the structure with configured hooks ‒ a signal may happen in arbitrary
|
||||
//! thread and needs to read them while another thread might be manipulating the structure.
|
||||
//!
|
||||
//! Under ordinary circumstances we would be happy to just use `Mutex<HashMap<c_int, _>>`. However,
|
||||
//! as we use it in the signal handler, we are severely limited in what we can or can't use. So we
|
||||
//! choose to implement kind of spin-look thing with atomics.
|
||||
//!
|
||||
//! In the reader it is always simply locked and then unlocked, making sure it doesn't disappear
|
||||
//! while in use.
|
||||
//!
|
||||
//! The writer has a separate mutex (that prevents other writers; this is used outside of the
|
||||
//! signal handler), makes a copy of the data and swaps an atomic pointer to the data structure.
|
||||
//! But it waits until everything is unlocked (no signal handler has the old data) for dropping the
|
||||
//! old instance. There's a generation trick to make sure that new signal locks another instance.
|
||||
//!
|
||||
//! The downside is, this is an active spin lock at the writer end. However, we assume than:
|
||||
//!
|
||||
//! * Signals are one time setup before we actually have threads. We just need to make *sure* we
|
||||
//! are safe even if this is not true.
|
||||
//! * Signals are rare, happening at the same time as the write even rarer.
|
||||
//! * Signals are short, as there is mostly nothing allowed inside them anyway.
|
||||
//! * Our tool box is severely limited.
|
||||
//!
|
||||
//! Therefore this is hopefully reasonable trade-off.
|
||||
//!
|
||||
//! # Atomic orderings
|
||||
//!
|
||||
//! The whole code uses SeqCst conservatively. Atomics are not used because of performance here and
|
||||
//! are the minor price around signals anyway. But the comments state which orderings should be
|
||||
//! enough in practice in case someone wants to get inspired (but do make your own check through
|
||||
//! them anyway).
|
||||
|
||||
use std::isize;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering};
|
||||
use std::sync::{Mutex, MutexGuard, PoisonError};
|
||||
use std::thread;
|
||||
|
||||
use libc;
|
||||
|
||||
const YIELD_EVERY: usize = 16;
|
||||
const MAX_GUARDS: usize = (isize::MAX) as usize;
|
||||
|
||||
pub(crate) struct ReadGuard<'a, T: 'a> {
|
||||
data: &'a T,
|
||||
lock: &'a AtomicUsize,
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for ReadGuard<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for ReadGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
// We effectively unlock; Release would be enough.
|
||||
self.lock.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WriteGuard<'a, T: 'a> {
|
||||
_guard: MutexGuard<'a, ()>,
|
||||
lock: &'a HalfLock<T>,
|
||||
data: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T> WriteGuard<'a, T> {
|
||||
pub(crate) fn store(&mut self, val: T) {
|
||||
// Move to the heap and convert to raw pointer for AtomicPtr.
|
||||
let new = Box::into_raw(Box::new(val));
|
||||
|
||||
self.data = unsafe { &*new };
|
||||
|
||||
// We can just put the new value in here safely, we worry only about dropping the old one.
|
||||
// Release might (?) be enough, to "upload" the data.
|
||||
let old = self.lock.data.swap(new, Ordering::SeqCst);
|
||||
|
||||
// Now we make sure there's no reader having the old data.
|
||||
self.lock.write_barrier();
|
||||
|
||||
drop(unsafe { Box::from_raw(old) });
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for WriteGuard<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
// Protected by that mutex
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct HalfLock<T> {
|
||||
// We conceptually contain an instance of T
|
||||
_t: PhantomData<T>,
|
||||
// The actual data as a pointer.
|
||||
data: AtomicPtr<T>,
|
||||
// The generation of the data. Influences which slot of the lock counter we use.
|
||||
generation: AtomicUsize,
|
||||
// How many active locks are there?
|
||||
lock: [AtomicUsize; 2],
|
||||
// Mutex for the writers; only one writer.
|
||||
write_mutex: Mutex<()>,
|
||||
}
|
||||
|
||||
impl<T> HalfLock<T> {
|
||||
pub(crate) fn new(data: T) -> Self {
|
||||
// Move to the heap so we can safely point there. Then convert to raw pointer as AtomicPtr
|
||||
// operates on raw pointers. The AtomicPtr effectively acts like Box for us semantically.
|
||||
let ptr = Box::into_raw(Box::new(data));
|
||||
Self {
|
||||
_t: PhantomData,
|
||||
data: AtomicPtr::new(ptr),
|
||||
generation: AtomicUsize::new(0),
|
||||
lock: [AtomicUsize::new(0), AtomicUsize::new(0)],
|
||||
write_mutex: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read(&self) -> ReadGuard<T> {
|
||||
// Relaxed should be enough; we only pick one or the other slot and the writer observes
|
||||
// that both were 0 at some time. So the actual value doesn't really matter for safety,
|
||||
// only the changing improves the performance.
|
||||
let gen = self.generation.load(Ordering::SeqCst);
|
||||
let lock = &self.lock[gen % 2];
|
||||
// Effectively locking something, acquire should be enough.
|
||||
let guard_cnt = lock.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// This is to prevent overflowing the counter in some degenerate cases, which could lead to
|
||||
// UB (freeing data while still in use). However, as this data structure is used only
|
||||
// internally and it's not possible to leak the guard and the guard itself takes some
|
||||
// memory, it should be really impossible to trigger this case. Still, we include it from
|
||||
// abundance of caution.
|
||||
//
|
||||
// This technically is not fully correct as enough threads being in between here and the
|
||||
// abort below could still overflow it and it could get freed for some *other* thread, but
|
||||
// that would mean having too many active threads to fit into RAM too and is even more
|
||||
// absurd corner case than the above.
|
||||
if guard_cnt > MAX_GUARDS {
|
||||
unsafe { libc::abort() };
|
||||
}
|
||||
|
||||
// Acquire should be enough; we need to "download" the data, paired with the swap on the
|
||||
// same pointer.
|
||||
let data = self.data.load(Ordering::SeqCst);
|
||||
// Safe:
|
||||
// * It did point to valid data when put in.
|
||||
// * Protected by lock, so still valid.
|
||||
let data = unsafe { &*data };
|
||||
|
||||
ReadGuard { data, lock }
|
||||
}
|
||||
|
||||
fn update_seen(&self, seen_zero: &mut [bool; 2]) {
|
||||
for (seen, slot) in seen_zero.iter_mut().zip(&self.lock) {
|
||||
*seen = *seen || slot.load(Ordering::SeqCst) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_barrier(&self) {
|
||||
// Do a first check of seeing zeroes before we switch the generation. At least one of them
|
||||
// should be zero by now, due to having drained the generation before leaving the previous
|
||||
// writer.
|
||||
let mut seen_zero = [false; 2];
|
||||
self.update_seen(&mut seen_zero);
|
||||
// By switching the generation to the other slot, we make sure the currently active starts
|
||||
// draining while the other will start filling up.
|
||||
self.generation.fetch_add(1, Ordering::SeqCst); // Overflow is fine.
|
||||
|
||||
let mut iter = 0usize;
|
||||
while !seen_zero.iter().all(|s| *s) {
|
||||
iter = iter.wrapping_add(1);
|
||||
|
||||
// Be somewhat less aggressive while looping, switch to the other threads if possible.
|
||||
if cfg!(not(miri)) {
|
||||
if iter % YIELD_EVERY == 0 {
|
||||
thread::yield_now();
|
||||
} else {
|
||||
// Replaced by hint::spin_loop, but we want to support older compiler
|
||||
#[allow(deprecated)]
|
||||
atomic::spin_loop_hint();
|
||||
}
|
||||
}
|
||||
|
||||
self.update_seen(&mut seen_zero);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write(&self) -> WriteGuard<T> {
|
||||
// While it's possible the user code panics, our code in store doesn't and the data gets
|
||||
// swapped atomically. So if it panics, nothing gets changed, therefore poisons are of no
|
||||
// interest here.
|
||||
let guard = self
|
||||
.write_mutex
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
|
||||
// Relaxed should be enough, as we are under the same mutex that was used to get the data
|
||||
// in.
|
||||
let data = self.data.load(Ordering::SeqCst);
|
||||
// Safe:
|
||||
// * Stored as valid data
|
||||
// * Only this method, protected by mutex, can change the pointer, so it didn't go away.
|
||||
let data = unsafe { &*data };
|
||||
|
||||
WriteGuard {
|
||||
data,
|
||||
_guard: guard,
|
||||
lock: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for HalfLock<T> {
|
||||
fn drop(&mut self) {
|
||||
// During drop we are sure there are no other borrows of the data so we are free to just
|
||||
// drop it. Also, the drop impl won't be called in practice in our case, as it is used
|
||||
// solely as a global variable, but we provide it for completeness and tests anyway.
|
||||
//
|
||||
// unsafe: the pointer in there is always valid, we just take the last instance out.
|
||||
unsafe {
|
||||
// Acquire should be enough.
|
||||
let data = Box::from_raw(self.data.load(Ordering::SeqCst));
|
||||
drop(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
789
third_party/rust/signal-hook-registry/src/lib.rs
vendored
Normal file
789
third_party/rust/signal-hook-registry/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,789 @@
|
|||
#![doc(test(attr(deny(warnings))))]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(unknown_lints, renamed_and_remove_lints, bare_trait_objects)]
|
||||
|
||||
//! Backend of the [signal-hook] crate.
|
||||
//!
|
||||
//! The [signal-hook] crate tries to provide an API to the unix signals, which are a global
|
||||
//! resource. Therefore, it is desirable an application contains just one version of the crate
|
||||
//! which manages this global resource. But that makes it impossible to make breaking changes in
|
||||
//! the API.
|
||||
//!
|
||||
//! Therefore, this crate provides very minimal and low level API to the signals that is unlikely
|
||||
//! to have to change, while there may be multiple versions of the [signal-hook] that all use this
|
||||
//! low-level API to provide different versions of the high level APIs.
|
||||
//!
|
||||
//! It is also possible some other crates might want to build a completely different API. This
|
||||
//! split allows these crates to still reuse the same low-level routines in this crate instead of
|
||||
//! going to the (much more dangerous) unix calls.
|
||||
//!
|
||||
//! # What this crate provides
|
||||
//!
|
||||
//! The only thing this crate does is multiplexing the signals. An application or library can add
|
||||
//! or remove callbacks and have multiple callbacks for the same signal.
|
||||
//!
|
||||
//! It handles dispatching the callbacks and managing them in a way that uses only the
|
||||
//! [async-signal-safe] functions inside the signal handler. Note that the callbacks are still run
|
||||
//! inside the signal handler, so it is up to the caller to ensure they are also
|
||||
//! [async-signal-safe].
|
||||
//!
|
||||
//! # What this is for
|
||||
//!
|
||||
//! This is a building block for other libraries creating reasonable abstractions on top of
|
||||
//! signals. The [signal-hook] is the generally preferred way if you need to handle signals in your
|
||||
//! application and provides several safe patterns of doing so.
|
||||
//!
|
||||
//! # Rust version compatibility
|
||||
//!
|
||||
//! Currently builds on 1.26.0 an newer and this is very unlikely to change. However, tests
|
||||
//! require dependencies that don't build there, so tests need newer Rust version (they are run on
|
||||
//! stable).
|
||||
//!
|
||||
//! # Portability
|
||||
//!
|
||||
//! This crate includes a limited support for Windows, based on `signal`/`raise` in the CRT.
|
||||
//! There are differences in both API and behavior:
|
||||
//!
|
||||
//! - Due to lack of `siginfo_t`, we don't provide `register_sigaction` or `register_unchecked`.
|
||||
//! - Due to lack of signal blocking, there's a race condition.
|
||||
//! After the call to `signal`, there's a moment where we miss a signal.
|
||||
//! That means when you register a handler, there may be a signal which invokes
|
||||
//! neither the default handler or the handler you register.
|
||||
//! - Handlers registered by `signal` in Windows are cleared on first signal.
|
||||
//! To match behavior in other platforms, we re-register the handler each time the handler is
|
||||
//! called, but there's a moment where we miss a handler.
|
||||
//! That means when you receive two signals in a row, there may be a signal which invokes
|
||||
//! the default handler, nevertheless you certainly have registered the handler.
|
||||
//!
|
||||
//! [signal-hook]: https://docs.rs/signal-hook
|
||||
//! [async-signal-safe]: http://www.man7.org/linux/man-pages/man7/signal-safety.7.html
|
||||
|
||||
extern crate libc;
|
||||
|
||||
mod half_lock;
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io::Error;
|
||||
use std::mem;
|
||||
#[cfg(not(windows))]
|
||||
use std::ptr;
|
||||
// Once::new is now a const-fn. But it is not stable in all the rustc versions we want to support
|
||||
// yet.
|
||||
#[allow(deprecated)]
|
||||
use std::sync::ONCE_INIT;
|
||||
use std::sync::{Arc, Once};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use libc::{c_int, c_void, sigaction, siginfo_t};
|
||||
#[cfg(windows)]
|
||||
use libc::{c_int, sighandler_t};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use libc::{SIGFPE, SIGILL, SIGKILL, SIGSEGV, SIGSTOP};
|
||||
#[cfg(windows)]
|
||||
use libc::{SIGFPE, SIGILL, SIGSEGV};
|
||||
|
||||
use half_lock::HalfLock;
|
||||
|
||||
// These constants are not defined in the current version of libc, but it actually
|
||||
// exists in Windows CRT.
|
||||
#[cfg(windows)]
|
||||
const SIG_DFL: sighandler_t = 0;
|
||||
#[cfg(windows)]
|
||||
const SIG_IGN: sighandler_t = 1;
|
||||
#[cfg(windows)]
|
||||
const SIG_GET: sighandler_t = 2;
|
||||
#[cfg(windows)]
|
||||
const SIG_ERR: sighandler_t = !0;
|
||||
|
||||
// To simplify implementation. Not to be exposed.
|
||||
#[cfg(windows)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct siginfo_t;
|
||||
|
||||
// # Internal workings
|
||||
//
|
||||
// This uses a form of RCU. There's an atomic pointer to the current action descriptors (in the
|
||||
// form of IndependentArcSwap, to be able to track what, if any, signal handlers still use the
|
||||
// version). A signal handler takes a copy of the pointer and calls all the relevant actions.
|
||||
//
|
||||
// Modifications to that are protected by a mutex, to avoid juggling multiple signal handlers at
|
||||
// once (eg. not calling sigaction concurrently). This should not be a problem, because modifying
|
||||
// the signal actions should be initialization only anyway. To avoid all allocations and also
|
||||
// deallocations inside the signal handler, after replacing the pointer, the modification routine
|
||||
// needs to busy-wait for the reference count on the old pointer to drop to 1 and take ownership ‒
|
||||
// that way the one deallocating is the modification routine, outside of the signal handler.
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
struct ActionId(u128);
|
||||
|
||||
/// An ID of registered action.
|
||||
///
|
||||
/// This is returned by all the registration routines and can be used to remove the action later on
|
||||
/// with a call to [`unregister`].
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct SigId {
|
||||
signal: c_int,
|
||||
action: ActionId,
|
||||
}
|
||||
|
||||
// This should be dyn Fn(...), but we want to support Rust 1.26.0 and that one doesn't allow dyn
|
||||
// yet.
|
||||
#[allow(unknown_lints, bare_trait_objects)]
|
||||
type Action = Fn(&siginfo_t) + Send + Sync;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Slot {
|
||||
prev: Prev,
|
||||
// We use BTreeMap here, because we want to run the actions in the order they were inserted.
|
||||
// This works, because the ActionIds are assigned in an increasing order.
|
||||
actions: BTreeMap<ActionId, Arc<Action>>,
|
||||
}
|
||||
|
||||
impl Slot {
|
||||
#[cfg(windows)]
|
||||
fn new(signal: libc::c_int) -> Result<Self, Error> {
|
||||
let old = unsafe { libc::signal(signal, handler as sighandler_t) };
|
||||
if old == SIG_ERR {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
Ok(Slot {
|
||||
prev: Prev { signal, info: old },
|
||||
actions: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn new(signal: libc::c_int) -> Result<Self, Error> {
|
||||
// C data structure, expected to be zeroed out.
|
||||
let mut new: libc::sigaction = unsafe { mem::zeroed() };
|
||||
#[cfg(not(target_os = "aix"))]
|
||||
{ new.sa_sigaction = handler as usize; }
|
||||
#[cfg(target_os = "aix")]
|
||||
{ new.sa_union.__su_sigaction = handler; }
|
||||
// Android is broken and uses different int types than the rest (and different depending on
|
||||
// the pointer width). This converts the flags to the proper type no matter what it is on
|
||||
// the given platform.
|
||||
let flags = libc::SA_RESTART;
|
||||
#[allow(unused_assignments)]
|
||||
let mut siginfo = flags;
|
||||
siginfo = libc::SA_SIGINFO as _;
|
||||
let flags = flags | siginfo;
|
||||
new.sa_flags = flags as _;
|
||||
// C data structure, expected to be zeroed out.
|
||||
let mut old: libc::sigaction = unsafe { mem::zeroed() };
|
||||
// FFI ‒ pointers are valid, it doesn't take ownership.
|
||||
if unsafe { libc::sigaction(signal, &new, &mut old) } != 0 {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
Ok(Slot {
|
||||
prev: Prev { signal, info: old },
|
||||
actions: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SignalData {
|
||||
signals: HashMap<c_int, Slot>,
|
||||
next_id: u128,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Prev {
|
||||
signal: c_int,
|
||||
#[cfg(windows)]
|
||||
info: sighandler_t,
|
||||
#[cfg(not(windows))]
|
||||
info: sigaction,
|
||||
}
|
||||
|
||||
impl Prev {
|
||||
#[cfg(windows)]
|
||||
fn detect(signal: c_int) -> Result<Self, Error> {
|
||||
let old = unsafe { libc::signal(signal, SIG_GET) };
|
||||
if old == SIG_ERR {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
Ok(Prev { signal, info: old })
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn detect(signal: c_int) -> Result<Self, Error> {
|
||||
// C data structure, expected to be zeroed out.
|
||||
let mut old: libc::sigaction = unsafe { mem::zeroed() };
|
||||
// FFI ‒ pointers are valid, it doesn't take ownership.
|
||||
if unsafe { libc::sigaction(signal, ptr::null(), &mut old) } != 0 {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Prev { signal, info: old })
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute(&self, sig: c_int) {
|
||||
let fptr = self.info;
|
||||
if fptr != 0 && fptr != SIG_DFL && fptr != SIG_IGN {
|
||||
// FFI ‒ calling the original signal handler.
|
||||
unsafe {
|
||||
let action = mem::transmute::<usize, extern "C" fn(c_int)>(fptr);
|
||||
action(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
unsafe fn execute(&self, sig: c_int, info: *mut siginfo_t, data: *mut c_void) {
|
||||
#[cfg(not(target_os = "aix"))]
|
||||
let fptr = self.info.sa_sigaction;
|
||||
#[cfg(target_os = "aix")]
|
||||
let fptr = self.info.sa_union.__su_sigaction as usize;
|
||||
if fptr != 0 && fptr != libc::SIG_DFL && fptr != libc::SIG_IGN {
|
||||
// Android is broken and uses different int types than the rest (and different
|
||||
// depending on the pointer width). This converts the flags to the proper type no
|
||||
// matter what it is on the given platform.
|
||||
//
|
||||
// The trick is to create the same-typed variable as the sa_flags first and then
|
||||
// set it to the proper value (does Rust have a way to copy a type in a different
|
||||
// way?)
|
||||
#[allow(unused_assignments)]
|
||||
let mut siginfo = self.info.sa_flags;
|
||||
siginfo = libc::SA_SIGINFO as _;
|
||||
if self.info.sa_flags & siginfo == 0 {
|
||||
let action = mem::transmute::<usize, extern "C" fn(c_int)>(fptr);
|
||||
action(sig);
|
||||
} else {
|
||||
type SigAction = extern "C" fn(c_int, *mut siginfo_t, *mut c_void);
|
||||
let action = mem::transmute::<usize, SigAction>(fptr);
|
||||
action(sig, info, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lazy-initiated data structure with our global variables.
|
||||
///
|
||||
/// Used inside a structure to cut down on boilerplate code to lazy-initialize stuff. We don't dare
|
||||
/// use anything fancy like lazy-static or once-cell, since we are not sure they are
|
||||
/// async-signal-safe in their access. Our code uses the [Once], but only on the write end outside
|
||||
/// of signal handler. The handler assumes it has already been initialized.
|
||||
struct GlobalData {
|
||||
/// The data structure describing what needs to be run for each signal.
|
||||
data: HalfLock<SignalData>,
|
||||
|
||||
/// A fallback to fight/minimize a race condition during signal initialization.
|
||||
///
|
||||
/// See the comment inside [`register_unchecked_impl`].
|
||||
race_fallback: HalfLock<Option<Prev>>,
|
||||
}
|
||||
|
||||
static mut GLOBAL_DATA: Option<GlobalData> = None;
|
||||
#[allow(deprecated)]
|
||||
static GLOBAL_INIT: Once = ONCE_INIT;
|
||||
|
||||
impl GlobalData {
|
||||
fn get() -> &'static Self {
|
||||
unsafe { GLOBAL_DATA.as_ref().unwrap() }
|
||||
}
|
||||
fn ensure() -> &'static Self {
|
||||
GLOBAL_INIT.call_once(|| unsafe {
|
||||
GLOBAL_DATA = Some(GlobalData {
|
||||
data: HalfLock::new(SignalData {
|
||||
signals: HashMap::new(),
|
||||
next_id: 1,
|
||||
}),
|
||||
race_fallback: HalfLock::new(None),
|
||||
});
|
||||
});
|
||||
Self::get()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
extern "C" fn handler(sig: c_int) {
|
||||
if sig != SIGFPE {
|
||||
// Windows CRT `signal` resets handler every time, unless for SIGFPE.
|
||||
// Reregister the handler to retain maximal compatibility.
|
||||
// Problems:
|
||||
// - It's racy. But this is inevitably racy in Windows.
|
||||
// - Interacts poorly with handlers outside signal-hook-registry.
|
||||
let old = unsafe { libc::signal(sig, handler as sighandler_t) };
|
||||
if old == SIG_ERR {
|
||||
// MSDN doesn't describe which errors might occur,
|
||||
// but we can tell from the Linux manpage that
|
||||
// EINVAL (invalid signal number) is mostly the only case.
|
||||
// Therefore, this branch must not occur.
|
||||
// In any case we can do nothing useful in the signal handler,
|
||||
// so we're going to abort silently.
|
||||
unsafe {
|
||||
libc::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let globals = GlobalData::get();
|
||||
let fallback = globals.race_fallback.read();
|
||||
let sigdata = globals.data.read();
|
||||
|
||||
if let Some(ref slot) = sigdata.signals.get(&sig) {
|
||||
slot.prev.execute(sig);
|
||||
|
||||
for action in slot.actions.values() {
|
||||
action(&siginfo_t);
|
||||
}
|
||||
} else if let Some(prev) = fallback.as_ref() {
|
||||
// In case we get called but don't have the slot for this signal set up yet, we are under
|
||||
// the race condition. We may have the old signal handler stored in the fallback
|
||||
// temporarily.
|
||||
if sig == prev.signal {
|
||||
prev.execute(sig);
|
||||
}
|
||||
// else -> probably should not happen, but races with other threads are possible so
|
||||
// better safe
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
extern "C" fn handler(sig: c_int, info: *mut siginfo_t, data: *mut c_void) {
|
||||
let globals = GlobalData::get();
|
||||
let fallback = globals.race_fallback.read();
|
||||
let sigdata = globals.data.read();
|
||||
|
||||
if let Some(slot) = sigdata.signals.get(&sig) {
|
||||
unsafe { slot.prev.execute(sig, info, data) };
|
||||
|
||||
let info = unsafe { info.as_ref() };
|
||||
let info = info.unwrap_or_else(|| {
|
||||
// The info being null seems to be illegal according to POSIX, but has been observed on
|
||||
// some probably broken platform. We can't do anything about that, that is just broken,
|
||||
// but we are not allowed to panic in a signal handler, so we are left only with simply
|
||||
// aborting. We try to write a message what happens, but using the libc stuff
|
||||
// (`eprintln` is not guaranteed to be async-signal-safe).
|
||||
unsafe {
|
||||
const MSG: &[u8] =
|
||||
b"Platform broken, got NULL as siginfo to signal handler. Aborting";
|
||||
libc::write(2, MSG.as_ptr() as *const _, MSG.len());
|
||||
libc::abort();
|
||||
}
|
||||
});
|
||||
|
||||
for action in slot.actions.values() {
|
||||
action(info);
|
||||
}
|
||||
} else if let Some(prev) = fallback.as_ref() {
|
||||
// In case we get called but don't have the slot for this signal set up yet, we are under
|
||||
// the race condition. We may have the old signal handler stored in the fallback
|
||||
// temporarily.
|
||||
if prev.signal == sig {
|
||||
unsafe { prev.execute(sig, info, data) };
|
||||
}
|
||||
// else -> probably should not happen, but races with other threads are possible so
|
||||
// better safe
|
||||
}
|
||||
}
|
||||
|
||||
/// List of forbidden signals.
|
||||
///
|
||||
/// Some signals are impossible to replace according to POSIX and some are so special that this
|
||||
/// library refuses to handle them (eg. SIGSEGV). The routines panic in case registering one of
|
||||
/// these signals is attempted.
|
||||
///
|
||||
/// See [`register`].
|
||||
pub const FORBIDDEN: &[c_int] = FORBIDDEN_IMPL;
|
||||
|
||||
#[cfg(windows)]
|
||||
const FORBIDDEN_IMPL: &[c_int] = &[SIGILL, SIGFPE, SIGSEGV];
|
||||
#[cfg(not(windows))]
|
||||
const FORBIDDEN_IMPL: &[c_int] = &[SIGKILL, SIGSTOP, SIGILL, SIGFPE, SIGSEGV];
|
||||
|
||||
/// Registers an arbitrary action for the given signal.
|
||||
///
|
||||
/// This makes sure there's a signal handler for the given signal. It then adds the action to the
|
||||
/// ones called each time the signal is delivered. If multiple actions are set for the same signal,
|
||||
/// all are called, in the order of registration.
|
||||
///
|
||||
/// If there was a previous signal handler for the given signal, it is chained ‒ it will be called
|
||||
/// as part of this library's signal handler, before any actions set through this function.
|
||||
///
|
||||
/// On success, the function returns an ID that can be used to remove the action again with
|
||||
/// [`unregister`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the signal is one of (see [`FORBIDDEN`]):
|
||||
///
|
||||
/// * `SIGKILL`
|
||||
/// * `SIGSTOP`
|
||||
/// * `SIGILL`
|
||||
/// * `SIGFPE`
|
||||
/// * `SIGSEGV`
|
||||
///
|
||||
/// The first two are not possible to override (and the underlying C functions simply ignore all
|
||||
/// requests to do so, which smells of possible bugs, or return errors). The rest can be set, but
|
||||
/// generally needs very special handling to do so correctly (direct manipulation of the
|
||||
/// application's address space, `longjmp` and similar). Unless you know very well what you're
|
||||
/// doing, you'll shoot yourself into the foot and this library won't help you with that.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since the library manipulates signals using the low-level C functions, all these can return
|
||||
/// errors. Generally, the errors mean something like the specified signal does not exist on the
|
||||
/// given platform ‒ after a program is debugged and tested on a given OS, it should never return
|
||||
/// an error.
|
||||
///
|
||||
/// However, if an error *is* returned, there are no guarantees if the given action was registered
|
||||
/// or not.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe, because the `action` is run inside a signal handler. The set of
|
||||
/// functions allowed to be called from within is very limited (they are called async-signal-safe
|
||||
/// functions by POSIX). These specifically do *not* contain mutexes and memory
|
||||
/// allocation/deallocation. They *do* contain routines to terminate the program, to further
|
||||
/// manipulate signals (by the low-level functions, not by this library) and to read and write file
|
||||
/// descriptors. Calling program's own functions consisting only of these is OK, as is manipulating
|
||||
/// program's variables ‒ however, as the action can be called on any thread that does not have the
|
||||
/// given signal masked (by default no signal is masked on any thread), and mutexes are a no-go,
|
||||
/// this is harder than it looks like at first.
|
||||
///
|
||||
/// As panicking from within a signal handler would be a panic across FFI boundary (which is
|
||||
/// undefined behavior), the passed handler must not panic.
|
||||
///
|
||||
/// If you find these limitations hard to satisfy, choose from the helper functions in the
|
||||
/// [signal-hook](https://docs.rs/signal-hook) crate ‒ these provide safe interface to use some
|
||||
/// common signal handling patters.
|
||||
///
|
||||
/// # Race condition
|
||||
///
|
||||
/// Upon registering the first hook for a given signal into this library, there's a short race
|
||||
/// condition under the following circumstances:
|
||||
///
|
||||
/// * The program already has a signal handler installed for this particular signal (through some
|
||||
/// other library, possibly).
|
||||
/// * Concurrently, some other thread installs a different signal handler while it is being
|
||||
/// installed by this library.
|
||||
/// * At the same time, the signal is delivered.
|
||||
///
|
||||
/// Under such conditions signal-hook might wrongly "chain" to the older signal handler for a short
|
||||
/// while (until the registration is fully complete).
|
||||
///
|
||||
/// Note that the exact conditions of the race condition might change in future versions of the
|
||||
/// library. The recommended way to avoid it is to register signals before starting any additional
|
||||
/// threads, or at least not to register signals concurrently.
|
||||
///
|
||||
/// Alternatively, make sure all signals are handled through this library.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Even when it is possible to repeatedly install and remove actions during the lifetime of a
|
||||
/// program, the installation and removal is considered a slow operation and should not be done
|
||||
/// very often. Also, there's limited (though huge) amount of distinct IDs (they are `u128`).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate signal_hook_registry;
|
||||
///
|
||||
/// use std::io::Error;
|
||||
/// use std::process;
|
||||
///
|
||||
/// fn main() -> Result<(), Error> {
|
||||
/// let signal = unsafe {
|
||||
/// signal_hook_registry::register(signal_hook::consts::SIGTERM, || process::abort())
|
||||
/// }?;
|
||||
/// // Stuff here...
|
||||
/// signal_hook_registry::unregister(signal); // Not really necessary.
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub unsafe fn register<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn() + Sync + Send + 'static,
|
||||
{
|
||||
register_sigaction_impl(signal, move |_: &_| action())
|
||||
}
|
||||
|
||||
/// Register a signal action.
|
||||
///
|
||||
/// This acts in the same way as [`register`], including the drawbacks, panics and performance
|
||||
/// characteristics. The only difference is the provided action accepts a [`siginfo_t`] argument,
|
||||
/// providing information about the received signal.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See the details of [`register`].
|
||||
#[cfg(not(windows))]
|
||||
pub unsafe fn register_sigaction<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn(&siginfo_t) + Sync + Send + 'static,
|
||||
{
|
||||
register_sigaction_impl(signal, action)
|
||||
}
|
||||
|
||||
unsafe fn register_sigaction_impl<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn(&siginfo_t) + Sync + Send + 'static,
|
||||
{
|
||||
assert!(
|
||||
!FORBIDDEN.contains(&signal),
|
||||
"Attempted to register forbidden signal {}",
|
||||
signal,
|
||||
);
|
||||
register_unchecked_impl(signal, action)
|
||||
}
|
||||
|
||||
/// Register a signal action without checking for forbidden signals.
|
||||
///
|
||||
/// This acts in the same way as [`register_unchecked`], including the drawbacks, panics and
|
||||
/// performance characteristics. The only difference is the provided action doesn't accept a
|
||||
/// [`siginfo_t`] argument.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See the details of [`register`].
|
||||
pub unsafe fn register_signal_unchecked<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn() + Sync + Send + 'static,
|
||||
{
|
||||
register_unchecked_impl(signal, move |_: &_| action())
|
||||
}
|
||||
|
||||
/// Register a signal action without checking for forbidden signals.
|
||||
///
|
||||
/// This acts the same way as [`register_sigaction`], but without checking for the [`FORBIDDEN`]
|
||||
/// signals. All the signals passed are registered and it is up to the caller to make some sense of
|
||||
/// them.
|
||||
///
|
||||
/// Note that you really need to know what you're doing if you change eg. the `SIGSEGV` signal
|
||||
/// handler. Generally, you don't want to do that. But unlike the other functions here, this
|
||||
/// function still allows you to do it.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See the details of [`register`].
|
||||
#[cfg(not(windows))]
|
||||
pub unsafe fn register_unchecked<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn(&siginfo_t) + Sync + Send + 'static,
|
||||
{
|
||||
register_unchecked_impl(signal, action)
|
||||
}
|
||||
|
||||
unsafe fn register_unchecked_impl<F>(signal: c_int, action: F) -> Result<SigId, Error>
|
||||
where
|
||||
F: Fn(&siginfo_t) + Sync + Send + 'static,
|
||||
{
|
||||
let globals = GlobalData::ensure();
|
||||
let action = Arc::from(action);
|
||||
|
||||
let mut lock = globals.data.write();
|
||||
|
||||
let mut sigdata = SignalData::clone(&lock);
|
||||
let id = ActionId(sigdata.next_id);
|
||||
sigdata.next_id += 1;
|
||||
|
||||
match sigdata.signals.entry(signal) {
|
||||
Entry::Occupied(mut occupied) => {
|
||||
assert!(occupied.get_mut().actions.insert(id, action).is_none());
|
||||
}
|
||||
Entry::Vacant(place) => {
|
||||
// While the sigaction/signal exchanges the old one atomically, we are not able to
|
||||
// atomically store it somewhere a signal handler could read it. That poses a race
|
||||
// condition where we could lose some signals delivered in between changing it and
|
||||
// storing it.
|
||||
//
|
||||
// Therefore we first store the old one in the fallback storage. The fallback only
|
||||
// covers the cases where the slot is not yet active and becomes "inert" after that,
|
||||
// even if not removed (it may get overwritten by some other signal, but for that the
|
||||
// mutex in globals.data must be unlocked here - and by that time we already stored the
|
||||
// slot.
|
||||
//
|
||||
// And yes, this still leaves a short race condition when some other thread could
|
||||
// replace the signal handler and we would be calling the outdated one for a short
|
||||
// time, until we install the slot.
|
||||
globals
|
||||
.race_fallback
|
||||
.write()
|
||||
.store(Some(Prev::detect(signal)?));
|
||||
|
||||
let mut slot = Slot::new(signal)?;
|
||||
slot.actions.insert(id, action);
|
||||
place.insert(slot);
|
||||
}
|
||||
}
|
||||
|
||||
lock.store(sigdata);
|
||||
|
||||
Ok(SigId { signal, action: id })
|
||||
}
|
||||
|
||||
/// Removes a previously installed action.
|
||||
///
|
||||
/// This function does nothing if the action was already removed. It returns true if it was removed
|
||||
/// and false if the action wasn't found.
|
||||
///
|
||||
/// It can unregister all the actions installed by [`register`] as well as the ones from downstream
|
||||
/// crates (like [`signal-hook`](https://docs.rs/signal-hook)).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This does *not* currently return the default/previous signal handler if the last action for a
|
||||
/// signal was just unregistered. That means that if you replaced for example `SIGTERM` and then
|
||||
/// removed the action, the program will effectively ignore `SIGTERM` signals from now on, not
|
||||
/// terminate on them as is the default action. This is OK if you remove it as part of a shutdown,
|
||||
/// but it is not recommended to remove termination actions during the normal runtime of
|
||||
/// application (unless the desired effect is to create something that can be terminated only by
|
||||
/// SIGKILL).
|
||||
pub fn unregister(id: SigId) -> bool {
|
||||
let globals = GlobalData::ensure();
|
||||
let mut replace = false;
|
||||
let mut lock = globals.data.write();
|
||||
let mut sigdata = SignalData::clone(&lock);
|
||||
if let Some(slot) = sigdata.signals.get_mut(&id.signal) {
|
||||
replace = slot.actions.remove(&id.action).is_some();
|
||||
}
|
||||
if replace {
|
||||
lock.store(sigdata);
|
||||
}
|
||||
replace
|
||||
}
|
||||
|
||||
// We keep this one here for strict backwards compatibility, but the API is kind of bad. One can
|
||||
// delete actions that don't belong to them, which is kind of against the whole idea of not
|
||||
// breaking stuff for others.
|
||||
#[deprecated(
|
||||
since = "1.3.0",
|
||||
note = "Don't use. Can influence unrelated parts of program / unknown actions"
|
||||
)]
|
||||
#[doc(hidden)]
|
||||
pub fn unregister_signal(signal: c_int) -> bool {
|
||||
let globals = GlobalData::ensure();
|
||||
let mut replace = false;
|
||||
let mut lock = globals.data.write();
|
||||
let mut sigdata = SignalData::clone(&lock);
|
||||
if let Some(slot) = sigdata.signals.get_mut(&signal) {
|
||||
if !slot.actions.is_empty() {
|
||||
slot.actions.clear();
|
||||
replace = true;
|
||||
}
|
||||
}
|
||||
if replace {
|
||||
lock.store(sigdata);
|
||||
}
|
||||
replace
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use libc::{pid_t, SIGUSR1, SIGUSR2};
|
||||
|
||||
#[cfg(windows)]
|
||||
use libc::SIGTERM as SIGUSR1;
|
||||
#[cfg(windows)]
|
||||
use libc::SIGTERM as SIGUSR2;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_forbidden() {
|
||||
let _ = unsafe { register(SIGILL, || ()) };
|
||||
}
|
||||
|
||||
/// Registering the forbidden signals is allowed in the _unchecked version.
|
||||
#[test]
|
||||
#[allow(clippy::redundant_closure)] // Clippy, you're wrong. Because it changes the return value.
|
||||
fn forbidden_raw() {
|
||||
unsafe { register_signal_unchecked(SIGFPE, || std::process::abort()).unwrap() };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_without_pid() {
|
||||
let status = Arc::new(AtomicUsize::new(0));
|
||||
let action = {
|
||||
let status = Arc::clone(&status);
|
||||
move || {
|
||||
status.store(1, Ordering::Relaxed);
|
||||
}
|
||||
};
|
||||
unsafe {
|
||||
register(SIGUSR2, action).unwrap();
|
||||
libc::raise(SIGUSR2);
|
||||
}
|
||||
for _ in 0..10 {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let current = status.load(Ordering::Relaxed);
|
||||
match current {
|
||||
// Not yet
|
||||
0 => continue,
|
||||
// Good, we are done with the correct result
|
||||
_ if current == 1 => return,
|
||||
_ => panic!("Wrong result value {}", current),
|
||||
}
|
||||
}
|
||||
panic!("Timed out waiting for the signal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn signal_with_pid() {
|
||||
let status = Arc::new(AtomicUsize::new(0));
|
||||
let action = {
|
||||
let status = Arc::clone(&status);
|
||||
move |siginfo: &siginfo_t| {
|
||||
// Hack: currently, libc exposes only the first 3 fields of siginfo_t. The pid
|
||||
// comes somewhat later on. Therefore, we do a Really Ugly Hack and define our
|
||||
// own structure (and hope it is correct on all platforms). But hey, this is
|
||||
// only the tests, so we are going to get away with this.
|
||||
#[repr(C)]
|
||||
struct SigInfo {
|
||||
_fields: [c_int; 3],
|
||||
#[cfg(all(target_pointer_width = "64", target_os = "linux"))]
|
||||
_pad: c_int,
|
||||
pid: pid_t,
|
||||
}
|
||||
let s: &SigInfo = unsafe {
|
||||
(siginfo as *const _ as usize as *const SigInfo)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
};
|
||||
status.store(s.pid as usize, Ordering::Relaxed);
|
||||
}
|
||||
};
|
||||
let pid;
|
||||
unsafe {
|
||||
pid = libc::getpid();
|
||||
register_sigaction(SIGUSR2, action).unwrap();
|
||||
libc::raise(SIGUSR2);
|
||||
}
|
||||
for _ in 0..10 {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let current = status.load(Ordering::Relaxed);
|
||||
match current {
|
||||
// Not yet (PID == 0 doesn't happen)
|
||||
0 => continue,
|
||||
// Good, we are done with the correct result
|
||||
_ if current == pid as usize => return,
|
||||
_ => panic!("Wrong status value {}", current),
|
||||
}
|
||||
}
|
||||
panic!("Timed out waiting for the signal");
|
||||
}
|
||||
|
||||
/// Check that registration works as expected and that unregister tells if it did or not.
|
||||
#[test]
|
||||
fn register_unregister() {
|
||||
let signal = unsafe { register(SIGUSR1, || ()).unwrap() };
|
||||
// It was there now, so we can unregister
|
||||
assert!(unregister(signal));
|
||||
// The next time unregistering does nothing and tells us so.
|
||||
assert!(!unregister(signal));
|
||||
}
|
||||
}
|
||||
59
third_party/rust/signal-hook-registry/tests/unregister_signal.rs
vendored
Normal file
59
third_party/rust/signal-hook-registry/tests/unregister_signal.rs
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//! Tests for the [unregister_signal] function.
|
||||
//!
|
||||
//! As a separate integration level test, so it doesn't clash with other tests on the signals.
|
||||
|
||||
// The unregister_signal itself is deprecated. But we still want to test it, so it's not deprecated
|
||||
// and broken at the same time.
|
||||
#![allow(deprecated)]
|
||||
|
||||
extern crate libc;
|
||||
extern crate signal_hook_registry;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libc::{SIGINT, SIGTERM}; // We'll use these here because SIGUSR1 is not available on Windows.
|
||||
use signal_hook_registry::{register, unregister_signal};
|
||||
|
||||
#[test]
|
||||
fn register_unregister() {
|
||||
let called = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let hook = {
|
||||
let called = Arc::clone(&called);
|
||||
move || {
|
||||
called.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
register(SIGTERM, hook.clone()).unwrap();
|
||||
register(SIGTERM, hook.clone()).unwrap();
|
||||
register(SIGINT, hook.clone()).unwrap();
|
||||
|
||||
libc::raise(SIGTERM);
|
||||
}
|
||||
|
||||
// The closure is run twice.
|
||||
assert_eq!(2, called.load(Ordering::Relaxed));
|
||||
|
||||
assert!(unregister_signal(SIGTERM));
|
||||
|
||||
unsafe { libc::raise(SIGTERM) };
|
||||
// Second one unregisters nothing.
|
||||
assert!(!unregister_signal(SIGTERM));
|
||||
|
||||
// After unregistering (both), it is no longer run at all.
|
||||
assert_eq!(2, called.load(Ordering::Relaxed));
|
||||
|
||||
// The SIGINT one is not disturbed.
|
||||
unsafe { libc::raise(SIGINT) };
|
||||
assert_eq!(3, called.load(Ordering::Relaxed));
|
||||
|
||||
// But it's possible to register it again just fine.
|
||||
unsafe {
|
||||
register(SIGTERM, hook).unwrap();
|
||||
libc::raise(SIGTERM);
|
||||
}
|
||||
assert_eq!(4, called.load(Ordering::Relaxed));
|
||||
}
|
||||
Loading…
Reference in a new issue