Close

Heart of the program

A project log for Adventures in Rust

I decided to find out what the deal is with the Rust programming language

ken-yapKen Yap 02/06/2024 at 07:370 Comments

Now that we have presented the command line parsing, logging, and daemonization code, we come to the code that does the copying from the network to the printer in src/lib.rs:

extern crate lockfile;
use lockfile::Lockfile;

use std::str::FromStr;
use std::net::{IpAddr,SocketAddr,TcpListener,TcpStream};
use std::fs::{File,OpenOptions};
use std::io::prelude::*;
use std::{thread, time};

extern crate log;
use log::{trace,debug,info,warn,error};
extern crate syslog;

pub mod logger;

macro_rules! lockpathformat {
//    () => ("/var/lock/subsys/p910{}d")
      () => ("/tmp/p910{}d")
}

const BASEPORT:u32 = 9100;
const PRINTER_RETRY:u64 = 4000; // milliseconds
const BUFFERSIZE:usize = 8192;

// Copy network data from inputfile (network) to pfile (printer) until EOS
// If bidir, also copy data from printer to network
fn copy_stream(mut conn: &TcpStream, mut pfile: &File) -> Result<(), std::io::Error>
{
        info!("copy_stream");
        let mut buffer = [0u8; BUFFERSIZE];
        loop {
                debug!("reading...");
                let bytes_read = conn.read(&mut buffer)?;
                debug!("{} bytes read", bytes_read);
                if bytes_read == 0 {
                        break;
                }
                pfile.write_all(&buffer[0..bytes_read])?;
        }
        Ok(())
}

fn handle_client(stream: &TcpStream, device: &String, bidir: bool) -> Result<(), std::io::Error>
{
        // wait until printer is available
        let pfile = loop {
                match OpenOptions::new().read(bidir).write(true).create(false).truncate(true).open(device) {
                Ok(f) => {
                        break f;
                        },
                Err(_) => {
                        thread::sleep(time::Duration::from_millis(PRINTER_RETRY));
                        },
                }
        };
        copy_stream(&stream, &pfile)?;
        pfile.sync_all()?;
        Ok(())
}

pub fn server(pnumber: u32, device: &String, bidir: bool, ba: &String) -> Result<(), std::io::Error>
{
        let bindaddr = IpAddr::from_str(ba).expect(format!("{} not valid bind IP address", ba).as_str());
        let lockfilepath = format!(lockpathformat!(), pnumber);
        let lockfile = Lockfile::create(&lockfilepath).expect(format!("Lockfile {} already present", lockfilepath).as_str());
        let sockaddr = SocketAddr::new(bindaddr, (BASEPORT + pnumber) as u16);
        let listener = TcpListener::bind(sockaddr)?;
        info!("Server listening");
        loop {
                if let Ok((stream, addr)) = listener.accept() {
                        info!("new client: {addr:?}");
                        if let Err(e) = handle_client(&stream, device, bidir) {
                                info!("handle_client: {}", e);
                                break;
                        }
                } else {
                        break;
                }
        };
        lockfile.release()              // or just let it autorelease
}

A few comments. You can see that even constants can be typed, for example the time constant passed to the sleep routine, which needs to be an unsigned 64-bit int. In the port argument to the SocketAddress constructor, an unsigned 16-bit int is needed per IP packet specification. The as u16 truncates, but we know this is safe because the BASEPORT is 9100, and the pnumber is constrained to be in [0..9].

Both the buffer and printer File are mutable references because the buffer is written out and the internal state of the File is modified.

Currently bidirectional copying isn't supported; this would require polling for incoming data available and printer File ready, using a crate that wraps around the epoll or select APIs. Signals are not handled, so the daemon could leave a lockfile behind if interrpted. And the original p910nd elaborate buffer management to maximise throughput hasn't been reimplemented.

Discussions