listenfd-0.3.3/.gitignore010064400007650000024000000000371327712247000135410ustar0000000000000000 /target **/*.rs.bk Cargo.lock listenfd-0.3.3/.travis.yml010064400007650000024000000003411327714757400136740ustar0000000000000000os: linux language: rust cache: cargo: true git: depth: 1 if: tag IS blank script: make $SUITE matrix: include: - env: SUITE=test - env: SUITE=format-check install: rustup component add rustfmt-preview listenfd-0.3.3/appveyor.yml010064400007650000024000000004751327730616200141510ustar0000000000000000skip_tags: true cache: - 'target' - '%USERPROFILE%\.cargo' branches: only: - master install: - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init -yv - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV build: false test_script: - cargo test listenfd-0.3.3/Cargo.toml.orig010064400007650000024000000011031330014307400144210ustar0000000000000000[package] name = "listenfd" version = "0.3.3" authors = ["Armin Ronacher "] description = "A simple library to work with listenfds passed from the outside (systemd/catflap socket activation)" keywords = ["socket", "listenfd", "systemd", "socketactivation"] homepage = "https://github.com/mitsuhiko/rust-listenfd" readme = "README.md" license = "MIT/Apache-2.0" [target."cfg(not(windows))".dependencies] libc = "0.2.40" [target."cfg(windows)".dependencies] uuid = "0.6.3" winapi = { version = "0.3.4", features = ["winsock2", "processthreadsapi"] } listenfd-0.3.3/Cargo.toml0000644000000021740000000000000107100ustar00# 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "listenfd" version = "0.3.3" authors = ["Armin Ronacher "] description = "A simple library to work with listenfds passed from the outside (systemd/catflap socket activation)" homepage = "https://github.com/mitsuhiko/rust-listenfd" readme = "README.md" keywords = ["socket", "listenfd", "systemd", "socketactivation"] license = "MIT/Apache-2.0" [target."cfg(not(windows))".dependencies.libc] version = "0.2.40" [target."cfg(windows)".dependencies.uuid] version = "0.6.3" [target."cfg(windows)".dependencies.winapi] version = "0.3.4" features = ["winsock2", "processthreadsapi"] listenfd-0.3.3/Makefile010064400007650000024000000002431327714762500132210ustar0000000000000000all: test doc: @cargo doc test: cargotest cargotest: @cargo test format-check: @cargo fmt -- --write-mode diff .PHONY: all doc test cargotest format-check listenfd-0.3.3/README.md010064400007650000024000000025721330014306300130220ustar0000000000000000# listenfd listenfd is a crate that provides support for working with externally managed and passed file descriptors. This lets you work with systems that support socket activation or similar. Currently this supports systemd and catflap on unix only. catflap is very convenient in combination with cargo-watch for development purposes whereas systemd is useful for production deployments on linux. ## Example ```rust extern crate listenfd; use listenfd::ListenFdManager; let mut manager = ListenFdManager::from_env(); let mut server = make_a_server(); // if we are given a tcp listener on listen fd 0, we use that one server = if let Some(listener) = manager.take_tcp_listener(0)? { server.listen(listener) // otherwise fall back to local listening } else { server.bind("127.0.0.1:3000")? }; ``` You can then use this with cargo watch and systemfd: ``` $ cargo install systemfd cargo-watch systemfd --no-pid -p 3000 -- cargo watch -x run ``` Now systemfd will open the socket and keep it open. cargo watch will recompile the code on demand and the server will pick up the socket that systemfd opened. No more connection resets. listenfd-0.3.3/src/lib.rs010064400007650000024000000043721330014112700134450ustar0000000000000000//! listenfd is a crate that provides support for working with externally //! managed and passed file descriptors. This lets you work with systems //! that support socket activation or similar. //! //! Currently this supports a slightly modified systemd protocl on unix and //! a custom protocol on Windows. If you want to use this for development //! you can use the [systemfd](https://github.com/mitsuhiko/systemfd) //! utility which implements both those protocols. //! //! The systemd extension is that if the `LISTEN_PID` variable is not set or //! empty, the check for the pid is removed. This is useful when binaries //! are proxied in between like cargo-watch. For the windows protocol //! have a look at the systemfd documentation. //! //! ## Example //! //! This example shows how to use this crate with an `actix-web` server: //! //! ```rust //! # use std::io; //! # struct Server; //! # impl Server { //! # fn listen(self, _: X) -> Self { self } //! # fn bind(self, _: X) -> io::Result { Ok(self) } //! # } //! # fn make_a_server() -> Server { Server }; //! # fn test() -> io::Result<()> { //! use listenfd::ListenFd; //! //! let mut listenfd = ListenFd::from_env(); //! let mut server = make_a_server(); //! //! // if we are given a tcp listener on listen fd 0, we use that one //! server = if let Some(listener) = listenfd.take_tcp_listener(0)? { //! server.listen(listener) //! // otherwise fall back to local listening //! } else { //! server.bind("127.0.0.1:3000")? //! }; //! # Ok(()) } //! ``` //! //! You can then use this with cargo watch and systemfd: //! //! ```plain //! $ cargo install systemfd cargo-watch //! systemfd --no-pid -s 3000 -- cargo watch -x run //! ``` //! //! Now systemfd will open the socket and keep it open. cargo watch will //! recompile the code on demand and the server will pick up the socket //! that systemfd opened. No more connection resets. //! //! The `--no-pid` flag is necessary to ensure that the `LISTEN_PID` environment //! variable is not set or the socket passing will be prevented by the pid check. #[cfg(not(windows))] extern crate libc; #[cfg(windows)] extern crate uuid; #[cfg(windows)] extern crate winapi; #[cfg(not(windows))] mod unix; #[cfg(windows)] mod windows; mod manager; pub use manager::*; listenfd-0.3.3/src/manager.rs010064400007650000024000000070471330013761100143160ustar0000000000000000use std::io; use std::net::{TcpListener, UdpSocket}; #[cfg(not(windows))] use std::os::unix::net::UnixListener; #[cfg(not(windows))] use unix as imp; #[cfg(windows)] use windows as imp; /// A helper object that gives access to raw file descriptors. pub struct ListenFd { fds: Vec>, } impl ListenFd { /// Creates the listenfd manager object from the environment. pub fn from_env() -> ListenFd { match imp::get_fds() { Some(fds) => ListenFd { fds: fds.into_iter().map(Some).collect(), }, None => ListenFd::empty(), } } /// Creates an empty listenfd object. /// /// This is helpful when the ability to work with external file /// descriptors should be disabled in certain code paths. This /// way the functions on the object will just never return /// sockets. pub fn empty() -> ListenFd { ListenFd { fds: vec![] } } /// Returns the number of fds in the manager object. /// /// Note that even if fds are taken out of the manager this count /// does not change. pub fn len(&self) -> usize { self.fds.len() } fn with_fd io::Result>( &mut self, idx: usize, f: F, ) -> io::Result> { let bucket = match self.fds.get_mut(idx) { Some(None) | None => return Ok(None), Some(mut bucket) => bucket, }; f(*bucket.as_ref().unwrap()).map(|rv| { bucket.take(); Some(rv) }) } /// Takes the TCP listener at an index. /// /// If the given index has been used before `Ok(None)` is returned, /// otherwise the fd at that index is returned as `TcpListener`. If /// the fd at that position is not a tcp socket then an error is /// returned and the fd is left at its place. pub fn take_tcp_listener(&mut self, idx: usize) -> io::Result> { self.with_fd(idx, imp::make_tcp_listener) } /// Takes the UNIX listener at an index. /// /// If the given index has been used before `Ok(None)` is returned, /// otherwise the fd at that index is returned as `UnixListener`. If /// the fd at that position is not a tcp socket then an error is /// returned and the fd is left at its place. /// /// This function is only available on unix platforms. #[cfg(not(windows))] pub fn take_unix_listener(&mut self, idx: usize) -> io::Result> { self.with_fd(idx, imp::make_unix_listener) } /// Takes the UDP socket at an index. /// /// If the given index has been used before `Ok(None)` is returned, /// otherwise the fd at that index is returned as `UdpSocket`. If /// the fd at that position is not a tcp socket then an error is /// returned and the fd is left at its place. pub fn take_udp_socket(&mut self, idx: usize) -> io::Result> { let _idx = idx; self.with_fd(idx, imp::make_udp_socket) } /// Takes the `RawFd` on unix platforms. #[cfg(not(windows))] pub fn take_raw_fd(&mut self, idx: usize) -> io::Result> { let _idx = idx; self.with_fd(idx, |fd| Ok(fd)) } /// Takes the `RawSocket` on windows platforms. /// /// This will error if the fd at this position is not a socket (but a handle). #[cfg(windows)] pub fn take_raw_socket(&mut self, idx: usize) -> io::Result> { let _idx = idx; Ok(None) } } listenfd-0.3.3/src/unix.rs010064400007650000024000000054721330013761100136670ustar0000000000000000use std::env; use std::io; use std::mem; use std::net::{TcpListener, UdpSocket}; use std::os::unix::io::{FromRawFd, RawFd}; use std::os::unix::net::UnixListener; use libc; pub type FdType = RawFd; fn is_sock(fd: FdType) -> bool { unsafe { let mut stat: libc::stat = mem::zeroed(); libc::fstat(fd as libc::c_int, &mut stat); (stat.st_mode & libc::S_IFMT) == libc::S_IFSOCK } } fn validate_socket( fd: FdType, sock_fam: libc::c_int, sock_type: libc::c_int, hint: &str, ) -> io::Result { if !is_sock(fd) { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("fd {} is not a socket", fd), )); } let is_valid = unsafe { let mut ty: libc::c_int = mem::zeroed(); let mut ty_len = mem::size_of_val(&ty) as libc::c_uint; let mut sockaddr: libc::sockaddr = mem::zeroed(); let mut sockaddr_len = mem::size_of_val(&sockaddr) as libc::c_uint; libc::getsockname(fd, &mut sockaddr, &mut sockaddr_len) == 0 && libc::getsockopt( fd, libc::SOL_SOCKET, libc::SO_TYPE, mem::transmute(&mut ty), &mut ty_len, ) == 0 && ty == sock_type && (sockaddr.sa_family as libc::c_int == sock_fam || (sockaddr.sa_family as libc::c_int == libc::AF_INET6 && sock_fam == libc::AF_INET)) }; if !is_valid { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("fd {} is not a valid {}", fd, hint), )); } Ok(fd) } pub fn make_tcp_listener(fd: FdType) -> io::Result { validate_socket(fd, libc::AF_INET, libc::SOCK_STREAM, "tcp socket") .map(|fd| unsafe { FromRawFd::from_raw_fd(fd) }) } pub fn make_unix_listener(fd: FdType) -> io::Result { validate_socket(fd, libc::AF_UNIX, libc::SOCK_STREAM, "unix socket") .map(|fd| unsafe { FromRawFd::from_raw_fd(fd) }) } pub fn make_udp_socket(fd: FdType) -> io::Result { validate_socket(fd, libc::AF_INET, libc::SOCK_DGRAM, "udp socket") .map(|fd| unsafe { FromRawFd::from_raw_fd(fd) }) } pub fn get_fds() -> Option> { // modified systemd protocol if let Some(count) = env::var("LISTEN_FDS").ok().and_then(|x| x.parse().ok()) { let ok = match env::var("LISTEN_PID").as_ref().map(|x| x.as_str()) { Err(env::VarError::NotPresent) | Ok("") => true, Ok(val) if val.parse().ok() == Some(unsafe { libc::getpid() }) => true, _ => false, }; env::remove_var("LISTEN_PID"); env::remove_var("LISTEN_FDS"); if ok { return Some((0..count).map(|offset| 3 + offset as FdType).collect()); } } None } listenfd-0.3.3/src/windows.rs010064400007650000024000000063541330013761100143760ustar0000000000000000use std::env; use std::io::{self, Read, Write}; use std::mem; use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream, UdpSocket}; use uuid::Uuid; pub use std::os::windows::io::{FromRawSocket, RawSocket}; use winapi::ctypes::{c_uint, c_ulong, c_void}; use winapi::shared::ntdef::{HANDLE, NTSTATUS}; use winapi::um::processthreadsapi::GetCurrentProcessId; use winapi::um::winsock2::{WSASocketW, FROM_PROTOCOL_INFO, WSAPROTOCOL_INFOW, WSA_FLAG_OVERLAPPED}; pub use self::RawSocket as FdType; pub fn make_tcp_listener(fd: FdType) -> io::Result { Ok(unsafe { FromRawSocket::from_raw_socket(fd) }) } pub fn make_udp_socket(fd: FdType) -> io::Result { Ok(unsafe { FromRawSocket::from_raw_socket(fd) }) } #[repr(C)] pub struct IO_STATUS_BLOCK { status: *const c_void, information: *const c_ulong, } #[repr(C)] pub struct FILE_INFORMATION { port: HANDLE, key: *const c_void, } #[link(name = "ntdll")] extern "system" { #[link_name = "NtSetInformationFile"] fn NtSetInformationFile( file_handle: HANDLE, block: *mut IO_STATUS_BLOCK, file_info: *mut FILE_INFORMATION, len: c_ulong, cls: c_uint, ) -> NTSTATUS; } /// This detaches a socket from IOCP. /// /// This only works on windows 8.1 and later. In ealier windows /// versions it's not possible to detach the socket until shutdown /// which means that stuff that uses IOCP (like tokio) cannot be /// used for reloading type scenarios. unsafe fn detach_from_iocp(sock: FdType) { let mut status_block: IO_STATUS_BLOCK = mem::zeroed(); let mut file_info: FILE_INFORMATION = mem::zeroed(); // FileReplaceCompletionInformation NtSetInformationFile( sock as HANDLE, &mut status_block, &mut file_info, mem::size_of::() as c_ulong, 61, ); } pub fn get_fds() -> Option> { let addr: SocketAddr = env::var("SYSTEMFD_SOCKET_SERVER") .ok() .and_then(|x| x.parse().ok())?; let secret: Uuid = env::var("SYSTEMFD_SOCKET_SECRET") .ok() .and_then(|x| x.parse().ok())?; env::remove_var("SYSTEMFD_SOCKET_SERVER"); env::remove_var("SYSTEMFD_SOCKET_SECRET"); let mut data = Vec::new(); let proto_len = mem::size_of::(); let mut listener = TcpStream::connect(addr).ok()?; let pid = unsafe { GetCurrentProcessId() }; listener .write_all(format!("{}|{}", secret, pid).as_bytes()) .ok()?; listener.shutdown(Shutdown::Write).ok()?; listener.read_to_end(&mut data).ok()?; let items = data.len() / proto_len; let mut rv = Vec::new(); for idx in 0..items { let offset = idx * proto_len; let proto_info: &mut WSAPROTOCOL_INFOW = unsafe { mem::transmute(data[offset..offset + proto_len].as_ptr()) }; unsafe { let sock = WSASocketW( FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, proto_info, 0, WSA_FLAG_OVERLAPPED, ) as FdType; if sock == 0 { return None; } detach_from_iocp(sock); rv.push(sock); } } Some(rv) }