mirror of
https://github.com/nushell/nushell.git
synced 2025-01-03 04:50:28 +01:00
move ls selinux functionality to a plugin
This commit is contained in:
parent
ee513e4630
commit
c4cc6b6a90
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -3615,6 +3615,7 @@ dependencies = [
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.100.1"
|
||||
dependencies = [
|
||||
"nu-engine",
|
||||
"nu-protocol",
|
||||
"nu-utils",
|
||||
"rmp-serde",
|
||||
@ -3871,6 +3872,17 @@ dependencies = [
|
||||
"webpage",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_selinux"
|
||||
version = "0.100.1"
|
||||
dependencies = [
|
||||
"nu-command",
|
||||
"nu-plugin",
|
||||
"nu-plugin-test-support",
|
||||
"nu-protocol",
|
||||
"selinux",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.100.1"
|
||||
|
@ -52,6 +52,7 @@ members = [
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu_plugin_formats",
|
||||
"crates/nu_plugin_polars",
|
||||
"crates/nu_plugin_selinux",
|
||||
"crates/nu_plugin_stress_internals",
|
||||
"crates/nu-std",
|
||||
"crates/nu-table",
|
||||
|
@ -1073,11 +1073,10 @@ fn flag_completions() {
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(20, suggestions.len());
|
||||
assert_eq!(18, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--context".into(),
|
||||
"--directory".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
@ -1095,7 +1094,6 @@ fn flag_completions() {
|
||||
"-m".into(),
|
||||
"-s".into(),
|
||||
"-t".into(),
|
||||
"-Z".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
|
@ -21,6 +21,8 @@ use std::{
|
||||
#[derive(Clone)]
|
||||
pub struct Ls;
|
||||
|
||||
pub type EntryMapper = (dyn Fn(&std::path::Path, Value) -> Value + Send + Sync);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Args {
|
||||
all: bool,
|
||||
@ -31,12 +33,66 @@ struct Args {
|
||||
directory: bool,
|
||||
use_mime_type: bool,
|
||||
use_threads: bool,
|
||||
security_context: bool,
|
||||
call_span: Span,
|
||||
}
|
||||
|
||||
const GLOB_CHARS: &[char] = &['*', '?', '['];
|
||||
|
||||
impl Ls {
|
||||
pub fn run_ls(
|
||||
call_span: Span,
|
||||
get_signals: &(dyn Fn() -> Signals),
|
||||
has_flag: &(dyn Fn(&str) -> Result<bool, ShellError>),
|
||||
has_pattern_arg: bool,
|
||||
pattern_arg: Vec<Option<Spanned<NuGlob>>>,
|
||||
cwd: PathBuf,
|
||||
map_entry: &EntryMapper,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args = Args {
|
||||
all: has_flag("all")?,
|
||||
long: has_flag("long")?,
|
||||
short_names: has_flag("short-names")?,
|
||||
full_paths: has_flag("full-paths")?,
|
||||
du: has_flag("du")?,
|
||||
directory: has_flag("directory")?,
|
||||
use_mime_type: has_flag("mime-type")?,
|
||||
use_threads: has_flag("threads")?,
|
||||
call_span,
|
||||
};
|
||||
|
||||
let input_pattern_arg = match has_pattern_arg {
|
||||
true if pattern_arg.is_empty() => vec![],
|
||||
true => pattern_arg.clone(),
|
||||
false => vec![None],
|
||||
};
|
||||
|
||||
let mut result_iters = vec![];
|
||||
for pat in input_pattern_arg {
|
||||
result_iters.push(ls_for_one_pattern(
|
||||
pat,
|
||||
args,
|
||||
get_signals(),
|
||||
cwd.clone(),
|
||||
map_entry,
|
||||
)?)
|
||||
}
|
||||
|
||||
// Here nushell needs to use
|
||||
// use `flatten` to chain all iterators into one.
|
||||
Ok(result_iters
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
get_signals(),
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
content_type: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for Ls {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
@ -50,6 +106,34 @@ impl Command for Ls {
|
||||
vec!["dir"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let get_signals = &|| engine_state.signals().clone();
|
||||
let has_flag = &|flag: &str| call.has_flag(engine_state, &mut (stack.clone()), flag);
|
||||
let pattern_arg = call
|
||||
.rest::<Spanned<NuGlob>>(engine_state, &mut stack.clone(), 0)?
|
||||
.into_iter()
|
||||
.map(Some)
|
||||
.collect();
|
||||
let has_pattern_arg = call.has_positional_args(stack, 0);
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
Ls::run_ls(
|
||||
call.span(),
|
||||
get_signals,
|
||||
has_flag,
|
||||
has_pattern_arg,
|
||||
pattern_arg,
|
||||
cwd,
|
||||
&|_path, entry| entry,
|
||||
)
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("ls")
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
@ -59,7 +143,7 @@ impl Command for Ls {
|
||||
.switch("all", "Show hidden files", Some('a'))
|
||||
.switch(
|
||||
"long",
|
||||
"Get almost all available columns for each entry (slower; columns are platform-dependent)",
|
||||
"Get all available columns for each entry (slower; columns are platform-dependent)",
|
||||
Some('l'),
|
||||
)
|
||||
.switch(
|
||||
@ -80,96 +164,9 @@ impl Command for Ls {
|
||||
)
|
||||
.switch("mime-type", "Show mime-type in type column instead of 'file' (based on filenames only; files' contents are not examined)", Some('m'))
|
||||
.switch("threads", "Use multiple threads to list contents. Output will be non-deterministic.", Some('t'))
|
||||
.switch(
|
||||
"context",
|
||||
match cfg!(feature = "selinux") {
|
||||
true => "Get the SELinux security context for each entry, if available",
|
||||
false => "Get the SELinux security context for each entry (disabled)"
|
||||
},
|
||||
Some('Z'),
|
||||
)
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let all = call.has_flag(engine_state, stack, "all")?;
|
||||
let long = call.has_flag(engine_state, stack, "long")?;
|
||||
let short_names = call.has_flag(engine_state, stack, "short-names")?;
|
||||
let full_paths = call.has_flag(engine_state, stack, "full-paths")?;
|
||||
let du = call.has_flag(engine_state, stack, "du")?;
|
||||
let directory = call.has_flag(engine_state, stack, "directory")?;
|
||||
let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?;
|
||||
let use_threads = call.has_flag(engine_state, stack, "threads")?;
|
||||
let security_context = call.has_flag(engine_state, stack, "context")?;
|
||||
let call_span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let args = Args {
|
||||
all,
|
||||
long,
|
||||
short_names,
|
||||
full_paths,
|
||||
du,
|
||||
directory,
|
||||
use_mime_type,
|
||||
use_threads,
|
||||
security_context,
|
||||
call_span,
|
||||
};
|
||||
|
||||
let pattern_arg = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
||||
let input_pattern_arg = if !call.has_positional_args(stack, 0) {
|
||||
None
|
||||
} else {
|
||||
Some(pattern_arg)
|
||||
};
|
||||
match input_pattern_arg {
|
||||
None => Ok(
|
||||
ls_for_one_pattern(None, args, engine_state.signals().clone(), cwd)?
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
content_type: None,
|
||||
},
|
||||
),
|
||||
),
|
||||
Some(pattern) => {
|
||||
let mut result_iters = vec![];
|
||||
for pat in pattern {
|
||||
result_iters.push(ls_for_one_pattern(
|
||||
Some(pat),
|
||||
args,
|
||||
engine_state.signals().clone(),
|
||||
cwd.clone(),
|
||||
)?)
|
||||
}
|
||||
|
||||
// Here nushell needs to use
|
||||
// use `flatten` to chain all iterators into one.
|
||||
Ok(result_iters
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
content_type: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
@ -234,6 +231,7 @@ fn ls_for_one_pattern(
|
||||
args: Args,
|
||||
signals: Signals,
|
||||
cwd: PathBuf,
|
||||
map_entry: &EntryMapper,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
fn create_pool(num_threads: usize) -> Result<rayon::ThreadPool, ShellError> {
|
||||
match rayon::ThreadPoolBuilder::new()
|
||||
@ -263,7 +261,6 @@ fn ls_for_one_pattern(
|
||||
use_mime_type,
|
||||
use_threads,
|
||||
call_span,
|
||||
security_context,
|
||||
} = args;
|
||||
let pattern_arg = {
|
||||
if let Some(path) = pattern_arg {
|
||||
@ -450,8 +447,8 @@ fn ls_for_one_pattern(
|
||||
du,
|
||||
&signals_clone,
|
||||
use_mime_type,
|
||||
args.full_paths,
|
||||
security_context,
|
||||
full_paths,
|
||||
map_entry,
|
||||
);
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
@ -570,7 +567,7 @@ pub(crate) fn dir_entry_dict(
|
||||
signals: &Signals,
|
||||
use_mime_type: bool,
|
||||
full_symlink_target: bool,
|
||||
security_context: bool,
|
||||
map_entry: &EntryMapper,
|
||||
) -> Result<Value, ShellError> {
|
||||
#[cfg(windows)]
|
||||
if metadata.is_none() {
|
||||
@ -627,13 +624,6 @@ pub(crate) fn dir_entry_dict(
|
||||
}
|
||||
}
|
||||
|
||||
if security_context {
|
||||
record.push(
|
||||
"security_context",
|
||||
security_context_value(filename, span).unwrap_or(Value::nothing(span)), // TODO: consider report_shell_warning
|
||||
)
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
record.push("readonly", Value::bool(md.permissions().readonly(), span));
|
||||
@ -754,7 +744,7 @@ pub(crate) fn dir_entry_dict(
|
||||
record.push("modified", Value::nothing(span));
|
||||
}
|
||||
|
||||
Ok(Value::record(record, span))
|
||||
Ok(map_entry(filename, Value::record(record, span)))
|
||||
}
|
||||
|
||||
// TODO: can we get away from local times in `ls`? internals might be cleaner if we worked in UTC
|
||||
@ -786,28 +776,6 @@ fn try_convert_to_local_date_time(t: SystemTime) -> Option<DateTime<Local>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn security_context_value(_path: &Path, span: Span) -> Result<Value, ShellError> {
|
||||
#[cfg(not(all(feature = "selinux", target_os = "linux")))]
|
||||
return Ok(Value::nothing(span));
|
||||
|
||||
#[cfg(all(feature = "selinux", target_os = "linux"))]
|
||||
{
|
||||
use selinux;
|
||||
match selinux::SecurityContext::of_path(_path, false, false)
|
||||
.map_err(|e| ShellError::IOError { msg: e.to_string() })?
|
||||
{
|
||||
Some(con) => {
|
||||
let bytes = con.as_bytes();
|
||||
Ok(Value::string(
|
||||
String::from_utf8_lossy(&bytes[0..bytes.len().saturating_sub(1)]),
|
||||
span,
|
||||
))
|
||||
}
|
||||
None => Ok(Value::nothing(span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(windows)] is just to make Clippy happy, remove if you ever want to use this on other platforms
|
||||
#[cfg(windows)]
|
||||
fn unix_time_to_local_date_time(secs: i64) -> Option<DateTime<Local>> {
|
||||
|
@ -19,6 +19,7 @@ pub use self::open::Open;
|
||||
pub use cd::Cd;
|
||||
pub use du::Du;
|
||||
pub use glob::Glob;
|
||||
pub use ls::EntryMapper as LsEntryMapper;
|
||||
pub use ls::Ls;
|
||||
pub use mktemp::Mktemp;
|
||||
pub use rm::Rm;
|
||||
|
@ -862,17 +862,3 @@ fn consistent_list_order() {
|
||||
assert_eq!(no_arg.out, with_arg.out);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "selinux", target_os = "linux"))]
|
||||
#[test]
|
||||
fn returns_correct_security_context() {
|
||||
use nu_test_support::nu_with_std;
|
||||
|
||||
let input = "
|
||||
use std assert
|
||||
^ls -Z / | lines | each { |e| $e | str trim | split column ' ' 'coreutils_scontext' 'name' | first } \
|
||||
| join (ls -Z / | each { default '?' security_context }) name \
|
||||
| each { |e| assert equal $e.security_context $e.coreutils_scontext $'For entry ($e.name) expected ($e.coreutils_scontext), got ($e.security_context)' }
|
||||
";
|
||||
assert_eq!(nu_with_std!(input).err, "");
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ macro_rules! generate_tests {
|
||||
Value::float(1.0, Span::new(0, 10)),
|
||||
Value::string("something", Span::new(0, 10)),
|
||||
],
|
||||
has_positional_args: true,
|
||||
named: vec![(
|
||||
Spanned {
|
||||
item: "name".to_string(),
|
||||
|
@ -809,6 +809,7 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineData::Value(Value::test_int(-1), Some(metadata0.clone())),
|
||||
@ -850,6 +851,7 @@ fn interface_write_plugin_call_writes_run_with_stream_input() -> Result<(), Shel
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
input: values
|
||||
@ -1078,6 +1080,7 @@ fn interface_run() -> Result<(), ShellError> {
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
@ -1351,6 +1354,7 @@ fn prepare_plugin_call_run() {
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![Value::test_int(4)],
|
||||
has_positional_args: true,
|
||||
named: vec![("x".to_owned().into_spanned(span), Some(Value::test_int(6)))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
@ -1363,6 +1367,7 @@ fn prepare_plugin_call_run() {
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![cv_ok.clone()],
|
||||
has_positional_args: true,
|
||||
named: vec![("ok".to_owned().into_spanned(span), Some(cv_ok.clone()))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
@ -1375,6 +1380,7 @@ fn prepare_plugin_call_run() {
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![cv_bad.clone()],
|
||||
has_positional_args: true,
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
@ -1387,6 +1393,7 @@ fn prepare_plugin_call_run() {
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![("bad".to_owned().into_spanned(span), Some(cv_bad.clone()))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
@ -1399,6 +1406,7 @@ fn prepare_plugin_call_run() {
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
// Shouldn't check input - that happens somewhere else
|
||||
|
@ -14,6 +14,7 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.100.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{self, Expression},
|
||||
engine::{Call, CallImpl, EngineState, Stack},
|
||||
@ -22,6 +23,8 @@ pub struct EvaluatedCall {
|
||||
pub head: Span,
|
||||
/// Values of positional arguments
|
||||
pub positional: Vec<Value>,
|
||||
/// Whether positional arguments were used, including spreads
|
||||
pub has_positional_args: bool,
|
||||
/// Names and values of named arguments
|
||||
pub named: Vec<(Spanned<String>, Option<Value>)>,
|
||||
}
|
||||
@ -32,6 +35,7 @@ impl EvaluatedCall {
|
||||
EvaluatedCall {
|
||||
head,
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
}
|
||||
}
|
||||
@ -49,6 +53,7 @@ impl EvaluatedCall {
|
||||
/// ```
|
||||
pub fn add_positional(&mut self, value: Value) -> &mut Self {
|
||||
self.positional.push(value);
|
||||
self.has_positional_args = true;
|
||||
self
|
||||
}
|
||||
|
||||
@ -88,6 +93,7 @@ impl EvaluatedCall {
|
||||
/// Builder variant of [`.add_positional()`](Self::add_positional).
|
||||
pub fn with_positional(mut self, value: Value) -> Self {
|
||||
self.add_positional(value);
|
||||
self.has_positional_args = true;
|
||||
self
|
||||
}
|
||||
|
||||
@ -144,6 +150,7 @@ impl EvaluatedCall {
|
||||
Ok(Self {
|
||||
head: call.head,
|
||||
positional,
|
||||
has_positional_args: call.has_positional_args(stack, 0),
|
||||
named,
|
||||
})
|
||||
}
|
||||
@ -160,6 +167,7 @@ impl EvaluatedCall {
|
||||
Ok(Self {
|
||||
head: call.head,
|
||||
positional,
|
||||
has_positional_args: call.has_positional_args(stack, 0),
|
||||
named,
|
||||
})
|
||||
}
|
||||
@ -178,6 +186,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # None
|
||||
@ -194,6 +203,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
||||
/// # None
|
||||
@ -210,6 +220,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::bool(true, Span::unknown()))
|
||||
@ -226,6 +237,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::bool(false, Span::unknown()))
|
||||
@ -242,6 +254,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: true,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::int(1, Span::unknown()))
|
||||
@ -289,6 +302,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: true,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::int(123, null_span))
|
||||
@ -310,6 +324,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![],
|
||||
/// # };
|
||||
/// let opt_foo = match call.get_flag_value("foo") {
|
||||
@ -344,6 +359,7 @@ impl EvaluatedCall {
|
||||
/// # Value::string("b".to_owned(), null_span),
|
||||
/// # Value::string("c".to_owned(), null_span),
|
||||
/// # ],
|
||||
/// # has_positional_args: true,
|
||||
/// # named: vec![],
|
||||
/// # };
|
||||
/// let arg = match call.nth(1) {
|
||||
@ -370,6 +386,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::int(123, null_span))
|
||||
@ -387,6 +404,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
||||
/// # Some(Value::int(123, null_span))
|
||||
@ -404,6 +422,7 @@ impl EvaluatedCall {
|
||||
/// # let call = EvaluatedCall {
|
||||
/// # head: null_span,
|
||||
/// # positional: Vec::new(),
|
||||
/// # has_positional_args: false,
|
||||
/// # named: vec![(
|
||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||
/// # Some(Value::string("abc".to_owned(), null_span))
|
||||
@ -436,6 +455,7 @@ impl EvaluatedCall {
|
||||
/// # Value::string("two".to_owned(), null_span),
|
||||
/// # Value::string("three".to_owned(), null_span),
|
||||
/// # ],
|
||||
/// # has_positional_args: true,
|
||||
/// # named: Vec::new(),
|
||||
/// # };
|
||||
/// let args = call.rest::<String>(0);
|
||||
@ -497,6 +517,7 @@ mod test {
|
||||
Value::float(1.0, Span::new(0, 10)),
|
||||
Value::string("something", Span::new(0, 10)),
|
||||
],
|
||||
has_positional_args: true,
|
||||
named: vec![
|
||||
(
|
||||
Spanned {
|
||||
|
@ -372,6 +372,7 @@ fn manager_consume_call_run_forwards_to_receiver_with_context() -> Result<(), Sh
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineDataHeader::Empty,
|
||||
@ -406,6 +407,7 @@ fn manager_consume_call_run_forwards_to_receiver_with_pipeline_data() -> Result<
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineDataHeader::list_stream(ListStreamInfo::new(6, Span::test_data())),
|
||||
@ -450,6 +452,7 @@ fn manager_consume_call_run_deserializes_custom_values_in_args() -> Result<(), S
|
||||
call: EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![value.clone()],
|
||||
has_positional_args: true,
|
||||
named: vec![(
|
||||
Spanned {
|
||||
item: "flag".into(),
|
||||
|
@ -139,6 +139,7 @@ mod tests {
|
||||
let call = EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
};
|
||||
|
||||
@ -168,6 +169,7 @@ mod tests {
|
||||
let call = EvaluatedCall {
|
||||
head: Span::test_data(),
|
||||
positional: vec![],
|
||||
has_positional_args: false,
|
||||
named: vec![],
|
||||
};
|
||||
|
||||
|
27
crates/nu_plugin_selinux/Cargo.toml
Normal file
27
crates/nu_plugin_selinux/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Nushell commands with SELinux functionality."
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_selinux"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_selinux"
|
||||
version = "0.100.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_selinux"
|
||||
bench = false
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.100.1" }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.100.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.100.1" }
|
||||
|
||||
selinux = "0.4.6"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.100.1" }
|
21
crates/nu_plugin_selinux/LICENSE
Normal file
21
crates/nu_plugin_selinux/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
152
crates/nu_plugin_selinux/src/commands/ls.rs
Normal file
152
crates/nu_plugin_selinux/src/commands/ls.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use std::path::Path;
|
||||
|
||||
use nu_command::{Ls, LsEntryMapper};
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
||||
use nu_protocol::{engine::Command, Example, LabeledError, PipelineData, ShellError, Span, Value};
|
||||
|
||||
use crate::SELinuxPlugin;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SELinuxLs {
|
||||
pub ls: Ls,
|
||||
}
|
||||
|
||||
impl PluginCommand for SELinuxLs {
|
||||
type Plugin = SELinuxPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"selinux ls"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
self.ls.description()
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
self.ls.search_terms()
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
let mut signature = self.ls.signature().switch(
|
||||
"context",
|
||||
"Get the SELinux security context for each entry, if available",
|
||||
Some('Z'),
|
||||
);
|
||||
signature.name = "selinux ls".into();
|
||||
signature
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &SELinuxPlugin,
|
||||
engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, LabeledError> {
|
||||
let security_context = call.has_flag("context")?;
|
||||
|
||||
let get_signals = &|| engine.signals().clone();
|
||||
let has_flag = &|flag: &str| call.has_flag(flag);
|
||||
let has_pattern_arg = call.has_positional_args;
|
||||
let pattern_arg = call.rest(0)?;
|
||||
let cwd = engine.get_current_dir()?.into();
|
||||
let call_head = call.head;
|
||||
let map_entry: &LsEntryMapper = &move |path, record| {
|
||||
match record {
|
||||
Value::Record { val, internal_span } if security_context => {
|
||||
let mut val = val.into_owned();
|
||||
val.push(
|
||||
"security_context",
|
||||
security_context_value(path, call_head)
|
||||
.unwrap_or(Value::nothing(call_head)), // TODO: consider report_shell_warning
|
||||
);
|
||||
|
||||
Value::record(val, internal_span)
|
||||
}
|
||||
_ => record,
|
||||
}
|
||||
};
|
||||
let data = Ls::run_ls(
|
||||
call_head,
|
||||
get_signals,
|
||||
has_flag,
|
||||
has_pattern_arg,
|
||||
pattern_arg,
|
||||
cwd,
|
||||
map_entry,
|
||||
)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
self.ls.examples()
|
||||
}
|
||||
}
|
||||
|
||||
fn security_context_value(_path: &Path, span: Span) -> Result<Value, ShellError> {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return Ok(Value::nothing(span));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use selinux;
|
||||
match selinux::SecurityContext::of_path(_path, false, false)
|
||||
.map_err(|e| ShellError::IOError { msg: e.to_string() })?
|
||||
{
|
||||
Some(con) => {
|
||||
let bytes = con.as_bytes();
|
||||
Ok(Value::string(
|
||||
String::from_utf8_lossy(&bytes[0..bytes.len().saturating_sub(1)]),
|
||||
span,
|
||||
))
|
||||
}
|
||||
None => Ok(Value::nothing(span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod test {
|
||||
use crate::SELinuxPlugin;
|
||||
|
||||
use nu_command::{All, Each, External, First, Join, Lines, SplitColumn, StrTrim};
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
use nu_protocol::{engine::Command, ShellError, Value};
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
#[test]
|
||||
fn returns_correct_security_context() -> Result<(), ShellError> {
|
||||
let plugin: Arc<SELinuxPlugin> = Arc::new(SELinuxPlugin {});
|
||||
let mut plugin_test = PluginTest::new("selinux", plugin)?;
|
||||
|
||||
let engine_state = plugin_test.engine_state_mut();
|
||||
engine_state.add_env_var("PWD".to_owned(), Value::test_string("/"));
|
||||
engine_state.add_env_var("PATH".into(), Value::test_string(env::var("PATH").unwrap()));
|
||||
|
||||
let deps: Vec<Box<dyn Command>> = vec![
|
||||
Box::new(External),
|
||||
Box::new(Lines),
|
||||
Box::new(Each),
|
||||
Box::new(StrTrim),
|
||||
Box::new(SplitColumn),
|
||||
Box::new(First),
|
||||
Box::new(Join),
|
||||
Box::new(All),
|
||||
];
|
||||
for decl in deps {
|
||||
plugin_test.add_decl(decl)?;
|
||||
}
|
||||
let input = "
|
||||
^ls -Z / | lines | each { |e| $e | str trim | split column ' ' 'coreutils_scontext' 'name' | first } \
|
||||
| join (selinux ls -sZ /) name \
|
||||
| all { |e|
|
||||
let valid = $e.coreutils_scontext == '?' or $e.security_context == $e.coreutils_scontext
|
||||
if not $valid {
|
||||
error make { msg: $'For entry ($e.name) expected ($e.coreutils_scontext), got ($e.security_context)' }
|
||||
}
|
||||
}";
|
||||
plugin_test.eval(input)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
3
crates/nu_plugin_selinux/src/commands/mod.rs
Normal file
3
crates/nu_plugin_selinux/src/commands/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod ls;
|
||||
|
||||
pub use ls::SELinuxLs;
|
15
crates/nu_plugin_selinux/src/lib.rs
Normal file
15
crates/nu_plugin_selinux/src/lib.rs
Normal file
@ -0,0 +1,15 @@
|
||||
mod commands;
|
||||
pub use commands::*;
|
||||
use nu_command::Ls;
|
||||
use nu_plugin::{Plugin, PluginCommand};
|
||||
pub struct SELinuxPlugin;
|
||||
|
||||
impl Plugin for SELinuxPlugin {
|
||||
fn version(&self) -> String {
|
||||
env!("CARGO_PKG_VERSION").into()
|
||||
}
|
||||
|
||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
vec![Box::new(SELinuxLs { ls: Ls })]
|
||||
}
|
||||
}
|
6
crates/nu_plugin_selinux/src/main.rs
Normal file
6
crates/nu_plugin_selinux/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use nu_plugin::{serve_plugin, MsgPackSerializer};
|
||||
use nu_plugin_selinux::SELinuxPlugin;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&SELinuxPlugin {}, MsgPackSerializer {})
|
||||
}
|
@ -33,7 +33,8 @@ let plugins = [
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
nu_plugin_formats,
|
||||
nu_plugin_polars
|
||||
nu_plugin_polars,
|
||||
nu_plugin_selinux
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
|
@ -356,6 +356,7 @@ export def build [
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
nu_plugin_formats,
|
||||
nu_plugin_selinux
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
@ -428,6 +429,7 @@ export def install [
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
nu_plugin_formats,
|
||||
nu_plugin_selinux
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
|
Loading…
Reference in New Issue
Block a user