Fully check xattr format; fill in user. prefix when absent
This commit is contained in:
parent
8450f73fba
commit
3eb3b2b5bb
3 changed files with 159 additions and 12 deletions
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue