Skip to content

Instantly share code, notes, and snippets.

@pirogoeth
Created October 14, 2018 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pirogoeth/75672dbd4c2936b1cf836b90a68159b8 to your computer and use it in GitHub Desktop.
Save pirogoeth/75672dbd4c2936b1cf836b90a68159b8 to your computer and use it in GitHub Desktop.
//! This is a sample named pipe server.
//! The server expects a file path to be passed on the command line as the first arg.
//! The server will create a pipe at the given path if it doesn't already exist.
//! The server will read pairs of bytes at a time and print the randomly generated number
//! to stdout.
extern crate ctrlc;
extern crate unix_named_pipe;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use unix_named_pipe::FileFIFOExt;
struct GuardedFile<P: AsRef<Path> + Clone + std::fmt::Debug> {
inner: fs::File,
path: P,
}
impl<P> GuardedFile<P>
where
P: AsRef<Path> + Clone + std::fmt::Debug,
{
pub fn new(inner: fs::File, path: P) -> GuardedFile<P> {
GuardedFile { inner, path }
}
pub fn borrow_inner(&mut self) -> &mut fs::File {
&mut self.inner
}
}
impl<P> std::ops::Drop for GuardedFile<P>
where
P: AsRef<Path> + Clone + std::fmt::Debug,
{
fn drop(&mut self) {
println!("dropping triggers removal of file: {:?}", &self.path);
fs::remove_file(&self.path).unwrap();
}
}
fn main() {
let pipe_path = env::args()
.nth(1)
.expect("named pipe path required but not provided");
println!("server opening pipe: {}", pipe_path);
// Set up a keyboard interrupt handler so we can remove the pipe when
// the process is shut down.
let running = if cfg!(feature = "ctrlc_handler") {
make_loop_flag()
} else {
Arc::new(AtomicBool::new(true))
};
// Open the pipe file for reading
let mut file = GuardedFile::new(
try_open(&pipe_path).expect("could not open pipe for reading"),
&pipe_path,
);
// Loop reading from the pipe until a keyboard interrupt is received
while running.load(Ordering::SeqCst) {
// Borrow the wrapped file out of the GuardedFile
let file = file.borrow_inner();
let mut payload: [u8; 2] = [0; 2];
// If an error occurs during read, panic
let res = file.read(&mut payload);
if let Err(err) = res {
// Named pipes, by design, only support nonblocking reads and writes.
// If a read would block, an error is thrown, but we can safely ignore it.
match err.kind() {
io::ErrorKind::WouldBlock => continue,
_ => panic!(format!("error while reading from pipe: {:?}", err)),
}
} else if let Ok(count) = res {
if count != payload.len() {
// If there is no data yet, just `continue` and try again.
continue;
} else {
let rand_num = payload[0];
println!("got data from client: {}", rand_num);
}
}
}
if cfg!(feature = "ctrlc_handler") {
fs::remove_file(&pipe_path).expect("could not remove pipe during shutdown");
}
}
fn make_loop_flag() -> Arc<AtomicBool> {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
println!("keyboard interrupted: stopping read loop");
r.store(false, Ordering::SeqCst);
})
.expect("could not set up keyboard interrupt handler");
return running;
}
/// Tries to open the pipe at `pipe_path`.
/// 1. Attempt to open the path for writing
/// a. If `open_write()` fails with `io::ErrorKind::NotFound`, create the pipe and try again
/// b. If `open_write()` fails with any other error, raise the error.
/// 2. Now that the file is opened for writing, ensure that it is a named pipe
/// a. If `is_fifo()` fails, panic.
/// b. If `is_fifo()` returns `false`, panic.
/// 3. Return the newly opened pipe file wrapped in an `io::Result`
fn try_open<P: AsRef<Path> + Clone>(pipe_path: P) -> io::Result<fs::File> {
let pipe = unix_named_pipe::open_read(&pipe_path);
if let Err(err) = pipe {
match err.kind() {
io::ErrorKind::NotFound => {
println!("creating pipe at: {:?}", pipe_path.clone().as_ref());
unix_named_pipe::create(&pipe_path, Some(0o660))?;
// Note that this has the possibility to recurse forever if creation `open_write`
// fails repeatedly with `io::ErrorKind::NotFound`, which is certainly not nice behaviour.
return try_open(pipe_path);
}
_ => {
return Err(err);
}
}
}
let pipe_file = pipe.unwrap();
let is_fifo = pipe_file
.is_fifo()
.expect("could not read type of file at pipe path");
if !is_fifo {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"expected file at {:?} to be fifo, is actually {:?}",
&pipe_path.clone().as_ref(),
pipe_file.metadata()?.file_type(),
),
));
}
Ok(pipe_file)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment