Add file locking to lqosd

The file locking is "smart": it checks to see if a lock is
valid before refusing to run (and updates the lock if
it can run anyway).

The locking mechanism will fail if you manually create
the lock file and dump random data into it that doesn't
readily convert to an i32.

Affects issue #54 and issue #52

* Add a new structure `FileLock` to `lqosd`.
* FileLock first checks /run/lqos/lqosd.lock. If it exists,
  it opens it and attempts to read a PID from it. If that PID
  is running and the associated name includes "lqosd", the
  FileLock returns an error.
* If no lock exists, then a file is created in
  /run/lqos/lqosd.lock containing the running PID.
* Includes Drop and signal termination support.
This commit is contained in:
Herbert Wolverson 2023-01-20 16:05:55 +00:00
parent 357bec9ad2
commit ed07a4666b
4 changed files with 90 additions and 1 deletions

1
src/rust/Cargo.lock generated
View File

@ -1391,6 +1391,7 @@ dependencies = [
"serde",
"serde_json",
"signal-hook 0.3.14",
"sysinfo",
"tokio",
]

View File

@ -24,3 +24,4 @@ env_logger = "0"
log = "0"
nix = "0"
rayon = "1"
sysinfo = "0"

View File

@ -0,0 +1,76 @@
use std::{ffi::CString, path::Path, fs::{File, remove_file}, io::{Write, Read}};
use anyhow::{Result, Error};
use nix::libc::{mode_t, getpid};
use sysinfo::{System, SystemExt, Pid, ProcessExt};
const LOCK_PATH: &str = "/run/lqos/lqosd.lock";
const LOCK_DIR: &str = "/run/lqos/.";
pub struct FileLock {}
impl FileLock {
pub fn new() -> Result<Self> {
Self::check_directory()?;
let lock_path = Path::new(LOCK_PATH);
if lock_path.exists() {
if Self::is_lock_valid()? {
return Err(Error::msg("lqosd is already running"));
}
// It's a stale pid, so we need to replace it
Self::create_lock()?;
Ok(Self{})
} else {
Self::create_lock()?;
Ok(Self{})
}
}
fn is_lock_valid() -> Result<bool> {
let mut f = File::open(LOCK_PATH)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
let pid: i32 = contents.parse()?;
let sys = System::new_all();
if let Some(process) = sys.processes().get(&Pid::from(pid)) {
if process.name().contains("lqosd") {
return Ok(true)
}
}
Ok(false)
}
fn create_lock() -> Result<()> {
let pid = unsafe { getpid() };
let pid_format = format!("{pid}");
let mut f = File::create(LOCK_PATH)?;
f.write_all(pid_format.as_bytes())?;
Ok(())
}
fn check_directory() -> Result<()> {
let dir_path = std::path::Path::new(LOCK_DIR);
if dir_path.exists() && dir_path.is_dir() {
Ok(())
} else {
std::fs::create_dir(dir_path)?;
let unix_path = CString::new(LOCK_DIR)?;
unsafe {
nix::libc::chmod(unix_path.as_ptr(), mode_t::from_le(666));
}
Ok(())
}
}
pub fn remove_lock() {
let _ = remove_file(LOCK_PATH); // Ignore result
}
}
impl Drop for FileLock {
fn drop(&mut self) {
Self::remove_lock();
}
}

View File

@ -4,7 +4,8 @@ mod lqos_daht_test;
mod program_control;
mod throughput_tracker;
mod tuning;
use crate::{ip_mapping::{clear_ip_flows, del_ip_flow, list_mapped_ips, map_ip_to_flow}};
mod file_lock;
use crate::{ip_mapping::{clear_ip_flows, del_ip_flow, list_mapped_ips, map_ip_to_flow}, file_lock::FileLock};
use anyhow::Result;
use log::{info, warn};
use lqos_bus::{BusResponse, BusRequest, UnixSocketServer};
@ -26,6 +27,15 @@ async fn main() -> Result<()> {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "warn"),
);
let file_lock = FileLock::new();
match file_lock {
Err(e) => {
log::error!("File lock error: {:?}", e);
std::process::exit(0);
}
_ => {}
}
info!("LibreQoS Daemon Starting");
let config = LibreQoSConfig::load()?;
tuning::tune_lqosd_from_config_file(&config)?;
@ -61,6 +71,7 @@ async fn main() -> Result<()> {
}
std::mem::drop(kernels);
UnixSocketServer::signal_cleanup();
std::mem::drop(file_lock);
std::process::exit(0);
}
SIGHUP => {