file-guard 0.1.0

A cross-platform library for simple advisory file locking.
Documentation
use std::fs::File;
use std::io::ErrorKind;
use std::{env, thread, time};

use vmap::{MapMut, Span, SpanMut};

use file_guard::Lock;

macro_rules! error {
    ($($arg:tt)*) => ({
        println!("error: {}", format_args!($($arg)*));
        std::process::exit(1);
    })
}

fn main() {
    let mut args = env::args();

    args.next();

    let path = next(&mut args, "path");
    let (mut map, file) = MapMut::with_options()
        .open(&path)
        .unwrap_or_else(|e| error!("cannot open {:?}: {}", path, e));

    run(&mut args, &mut map, &file);
}

fn run(args: &mut env::Args, map: &mut MapMut, file: &File) {
    let mut guard = None;
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "+lock" => {
                let (lock, off, len) = lock_values(args, map.len());
                if let Some(_) = guard {
                    error!("lock is already held");
                } else {
                    let g = file_guard::lock(file, lock, off, len)
                        .unwrap_or_else(|e| error!("{:?} lock failed: {}", lock, e));
                    println!("{} {:?}", arg, lock);
                    guard = Some(g);
                }
            }
            "+trylock" => {
                let (lock, off, len) = lock_values(args, map.len());
                if let Some(_) = guard {
                    error!("lock is already held");
                } else {
                    match file_guard::try_lock(file, lock, off, len) {
                        Err(e) => {
                            if e.kind() == ErrorKind::WouldBlock {
                                println!("{} None", arg)
                            } else {
                                error!("{:?} try lock failed: {}", lock, e)
                            }
                        }
                        Ok(g) => {
                            println!("{} {:?}", arg, lock);
                            guard = Some(g);
                        }
                    }
                }
            }
            "+lockany" => {
                let (off, len) = lock_size(args, map.len());
                if let Some(_) = guard {
                    error!("lock is already held");
                } else {
                    let g = file_guard::lock_any(file, off, len)
                        .unwrap_or_else(|e| error!("any lock failed: {}", e));
                    println!("{} {:?}", arg, g.lock_type());
                    guard = Some(g);
                }
            }
            "+downgrade" => {
                if let Some(ref mut g) = guard {
                    g.downgrade()
                        .unwrap_or_else(|e| error!("downgrade failed: {}", e));
                    println!("{} {:?}", arg, Lock::Shared);
                } else {
                    error!("lock is not held");
                }
            }
            "+unlock" => {
                if let Some(_) = guard {
                    guard = None;
                    println!("{}", arg);
                } else {
                    error!("lock is not held");
                }
            }
            "+write" => {
                let off = int(args, 0, map.len());
                let val = int(args, 0, usize::MAX);
                map.write_volatile(off, val);
                println!("{} {} {}", arg, off, val);
            }
            "+wait" => {
                let off = int(args, 0, map.len());
                let val = int(args, 0, usize::MAX);
                let mut total = 0;
                while map.read_volatile::<usize>(off) != val {
                    if total == 500 {
                        error!("timed out");
                    }
                    thread::sleep(time::Duration::from_millis(10));
                    total += 1;
                }
                println!("{} {} {}", arg, off, val);
            }
            arg => error!("unknown argument {}", arg),
        }
    }
}

fn next(args: &mut env::Args, what: &'static str) -> String {
    match args.next() {
        Some(v) => v,
        None => error!("{} expected", what),
    }
}

fn int(args: &mut env::Args, min: usize, max: usize) -> usize {
    let val = args.next().unwrap_or_else(|| error!("integer expected"));
    let s = val
        .parse::<usize>()
        .unwrap_or_else(|_| error!("must be a valid integer"));
    if s < min {
        error!("value must be at least {}", min)
    }
    if s > max {
        error!("value must be at most {}", max)
    }
    s
}

fn lock_values(args: &mut env::Args, len: usize) -> (Lock, usize, usize) {
    let (off, len) = lock_size(args, len);
    (lock_type(args), off, len)
}

fn lock_type(args: &mut env::Args) -> Lock {
    match next(args, "lock type").as_str() {
        "sh" | "shared" => Lock::Shared,
        "ex" | "exclusive" => Lock::Exclusive,
        other => error!("unknown lock type {}", other),
    }
}

fn lock_size(args: &mut env::Args, len: usize) -> (usize, usize) {
    let off = int(args, 0, len - 1);
    let len = int(args, 1, len - off);
    (off, len)
}