-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
suricatactl: rust version of suricatactl
- Loading branch information
Showing
9 changed files
with
201 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
name = "suricatactl" | ||
version = "@PACKAGE_VERSION@" | ||
edition = "2021" | ||
license = "GPL-2.0-only" | ||
|
||
[[bin]] | ||
name = "suricatactl" | ||
path = "@e_rustdir@/suricatactl/src/main.rs" | ||
|
||
[dependencies] | ||
regex = "~1.5.5" | ||
env_logger = "0.10.0" | ||
log = "0.4.20" | ||
|
||
# 4.0 is the newest version that builds with Rust 1.63.0. | ||
clap = { version = "~4.0.0", features = ["derive"] } | ||
|
||
# This dependency is not used directly, but we have to pin this back | ||
# to 0.3.0 for Rust 1.63.0. | ||
clap_lex = "=0.3.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
all-local: Cargo.toml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
|
||
pub(crate) mod prune; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
|
||
use log::{debug, error, info}; | ||
use std::path::PathBuf; | ||
|
||
use crate::FilestorePruneArgs; | ||
|
||
pub(crate) fn prune(args: FilestorePruneArgs) -> Result<(), Box<dyn std::error::Error>> { | ||
let age = parse_age(&args.age).unwrap(); | ||
info!("Pruning files older than {} seconds", age); | ||
|
||
let mut total_bytes = 0; | ||
let mut file_count = 0; | ||
|
||
let mut stack = vec![PathBuf::from(&args.directory)]; | ||
while let Some(dir) = stack.pop() { | ||
for entry in std::fs::read_dir(dir)? { | ||
let path = entry?.path(); | ||
if path.is_dir() { | ||
stack.push(path); | ||
} else { | ||
let metadata = path.metadata()?; | ||
let modified = metadata.modified()?; | ||
let elapsed = modified.elapsed()?; | ||
let elapsed_secs = elapsed.as_secs(); | ||
if elapsed_secs > age { | ||
debug!("Deleting {:?}", path); | ||
file_count += 1; | ||
total_bytes += metadata.len(); | ||
if !args.dry_run { | ||
if let Err(err) = std::fs::remove_file(&path) { | ||
error!("Failed to delete {}: {}", path.display(), err); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
info!("Removed {} files; {} bytes", file_count, total_bytes); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Given input like "1s", "1m", "1h" or "1d" return the number of | ||
/// seconds | ||
fn parse_age(age: &str) -> Result<u64, String> { | ||
// Use a regex to separate the value from the unit. | ||
let re = regex::Regex::new(r"^(\d+)([smhd])$").unwrap(); | ||
let caps = re.captures(age).ok_or_else(|| { | ||
format!( | ||
"Invalid age: {}. Must be a number followed by one of s, m, h, d", | ||
age | ||
) | ||
})?; | ||
let value = caps | ||
.get(1) | ||
.unwrap() | ||
.as_str() | ||
.parse::<u64>() | ||
.map_err(|e| format!("Invalid age: {}: {}", age, e))?; | ||
let unit = caps.get(2).unwrap().as_str(); | ||
|
||
match unit { | ||
"s" => Ok(value), | ||
"m" => Ok(value * 60), | ||
"h" => Ok(value * 60 * 60), | ||
"d" => Ok(value * 60 * 60 * 24), | ||
_ => unreachable!(), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_parse_age() { | ||
assert!(parse_age("1").is_err()); | ||
assert!(parse_age("s").is_err()); | ||
assert!(parse_age("1a").is_err()); | ||
|
||
// Valid tests | ||
assert_eq!(parse_age("1s").unwrap(), 1); | ||
assert_eq!(parse_age("3s").unwrap(), 3); | ||
assert_eq!(parse_age("1m").unwrap(), 60); | ||
assert_eq!(parse_age("3m").unwrap(), 180); | ||
assert_eq!(parse_age("3h").unwrap(), 10800); | ||
assert_eq!(parse_age("1d").unwrap(), 86400); | ||
assert_eq!(parse_age("3d").unwrap(), 86400 * 3); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
|
||
use clap::Parser; | ||
use clap::Subcommand; | ||
|
||
mod filestore; | ||
|
||
#[derive(Parser, Debug)] | ||
struct Cli { | ||
#[arg(long, short, global = true, action = clap::ArgAction::Count)] | ||
verbose: u8, | ||
|
||
#[arg( | ||
long, | ||
short, | ||
global = true, | ||
help = "Quiet mode, only warnings and errors will be logged" | ||
)] | ||
quiet: bool, | ||
|
||
#[command(subcommand)] | ||
command: Commands, | ||
} | ||
|
||
#[derive(Subcommand, Debug)] | ||
enum Commands { | ||
/// Filestore management commands | ||
Filestore(FilestoreCommand), | ||
} | ||
|
||
#[derive(Parser, Debug)] | ||
struct FilestoreCommand { | ||
#[command(subcommand)] | ||
command: FilestoreCommands, | ||
} | ||
|
||
#[derive(Subcommand, Debug)] | ||
enum FilestoreCommands { | ||
/// Remove files by age | ||
Prune(FilestorePruneArgs), | ||
} | ||
|
||
#[derive(Parser, Debug)] | ||
struct FilestorePruneArgs { | ||
#[arg(long, short = 'n', help = "only print what would happen")] | ||
dry_run: bool, | ||
#[arg(long, short, help = "file-store directory")] | ||
directory: String, | ||
#[arg(long, help = "prune files older than age, units: s, m, h, d")] | ||
age: String, | ||
} | ||
|
||
fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
let cli = Cli::parse(); | ||
|
||
let log_level = if cli.quiet { | ||
"warn" | ||
} else if cli.verbose > 0 { | ||
"debug" | ||
} else { | ||
"info" | ||
}; | ||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level)).init(); | ||
|
||
match cli.command { | ||
Commands::Filestore(filestore) => match filestore.command { | ||
FilestoreCommands::Prune(args) => crate::filestore::prune::prune(args), | ||
}, | ||
} | ||
} |