Fully check xattr format; fill in user. prefix when absent

This commit is contained in:
Josh Hansen 2023-08-03 13:26:54 -07:00
parent 8450f73fba
commit 3eb3b2b5bb
3 changed files with 159 additions and 12 deletions

73
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.3.2"
@ -169,6 +178,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
@ -187,12 +202,21 @@ version = "0.2.1"
dependencies = [
"clap",
"clap_complete",
"lazy_static",
"regex",
"serde",
"serde_json",
"thiserror",
"walkdir",
"xattr",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.18.0"
@ -217,6 +241,35 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "rustix"
version = "0.38.4"
@ -293,6 +346,26 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"

View file

@ -9,8 +9,11 @@ build = "build.rs"
[dependencies]
clap = { version = "4.3.19", features = ["derive"] }
lazy_static = "1.4.0"
regex = "1.9.1"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
thiserror = "1.0.44"
walkdir = "2.3.3"
xattr = { version = "1.0.0", default-features = false }

View file

@ -6,7 +6,11 @@ use std::{
};
use clap::{Parser, Subcommand};
#[macro_use]
extern crate lazy_static;
use regex::Regex;
use serde::Serialize;
use thiserror::Error;
use walkdir::WalkDir;
const XATTR_KEYNAME: &'static str = "user.mattress.keyname";
@ -25,6 +29,11 @@ struct FileXattrs {
xattrs: HashMap<String, String>,
}
lazy_static! {
static ref RE_XATTR: Regex = Regex::new("^([^.]+[.])([^.]+)$").unwrap();
static ref RE_XATTR_NO_NAMESPACE: Regex = Regex::new("^[^.]+$").unwrap();
}
#[derive(Subcommand)]
enum Commands {
/// Copy xattr values from one path to another.
@ -98,7 +107,70 @@ enum CopyOrMove {
Move,
}
#[derive(Error, Debug)]
enum StandardizeXattrError {
#[error("Extended attribute was empty")]
Empty,
#[error("Illegal format of extended attribute '{0}'")]
BadXattr(String),
#[error("Illegal namespace '{0}'")]
IllegalNamespace(String),
}
/// Checks the form of xattrs
/// They must either be namespace.attr or just plain xattr
/// When the namespace is not provided it will be filled in with "user"
fn standardize_xattr(xattr: &str) -> Result<String, StandardizeXattrError> {
if xattr.is_empty() {
return Err(StandardizeXattrError::Empty);
}
if let Some(captures) = RE_XATTR.captures(xattr) {
debug_assert!(captures.len() > 1);
let (_, [mut namespace, attr]) = captures.extract();
if namespace.len() == 0 {
namespace = "user."; // default to the user namespace
}
if !(namespace == "user."
|| namespace == "trusted."
|| namespace == "system."
|| namespace == "security.")
{
Err(StandardizeXattrError::IllegalNamespace(
namespace.to_string(),
))
} else {
Ok(format!("{}{}", namespace, attr))
}
} else {
if let Some(_captures) = RE_XATTR_NO_NAMESPACE.captures(xattr) {
Ok(format!("user.{}", xattr))
} else {
Err(StandardizeXattrError::BadXattr(xattr.to_string()))
}
}
}
/// Ensure xattr names are prefixed by `user.` and otherwise properly formatted
/// See standardize_xattr`
fn standardize_xattrs(xattrs: &Vec<String>) -> Vec<String> {
xattrs
.iter()
.map(|xattr| xattr.as_str())
.map(|f| {
standardize_xattr(f).unwrap_or_else(|e| {
panic!("xattr {} formatted incorrectly: {}", f, e);
})
})
.collect()
}
fn cp_or_mv(src: &PathBuf, dest: &PathBuf, fields: &Vec<String>, verbose: &bool, kind: CopyOrMove) {
let fields = standardize_xattrs(fields);
let fields: Vec<String> = if fields.is_empty() {
xattr::list(src)
.unwrap_or_else(|e| panic!("Could not list xattrs on {}: {}", src.display(), e))
@ -153,6 +225,9 @@ fn rm(paths: &Vec<PathBuf>, fields: &Vec<String>, verbose: &bool) {
if *verbose {
eprintln!("Removing {} from:", fields.join(", "));
}
let fields = standardize_xattrs(fields);
for path in paths {
let fields: Vec<String> = if fields.is_empty() {
xattr::list(path)
@ -180,6 +255,8 @@ fn rm(paths: &Vec<PathBuf>, fields: &Vec<String>, verbose: &bool) {
}
fn get(paths: &Vec<PathBuf>, fields: &Vec<String>, json: &bool) {
let fields = standardize_xattrs(fields);
for path in paths {
let fields: Vec<String> = if fields.is_empty() {
xattr::list(path)
@ -259,7 +336,11 @@ fn set<P: AsRef<Path>>(paths: &Vec<P>, field_assignments: &Vec<String>, verbose:
);
}
(parts[0].to_string(), parts[1].to_string())
(
standardize_xattr(parts[0])
.unwrap_or_else(|e| panic!("Error standardizing xattr {}: {}", parts[0], e)),
parts[1].to_string(),
)
})
.collect();
@ -287,17 +368,7 @@ fn idx(src: &PathBuf, dest: &PathBuf, keys: &Vec<String>, verbose: &bool) {
panic!("No keys were provided to index by");
}
// Make add user. prefix if missing
let keys: Vec<String> = keys
.iter()
.map(|key| {
if key.starts_with("user.") {
key.clone()
} else {
format!("user.{}", key)
}
})
.collect();
let keys = standardize_xattrs(keys);
for entry in WalkDir::new(src) {
let entry = entry.unwrap_or_else(|e| panic!("Could not read directory: {}", e));