mirror of
https://github.com/nushell/nushell.git
synced 2025-01-05 05:50:14 +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"
|
name = "nu-plugin-protocol"
|
||||||
version = "0.100.1"
|
version = "0.100.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"nu-engine",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
@ -3871,6 +3872,17 @@ dependencies = [
|
|||||||
"webpage",
|
"webpage",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu_plugin_selinux"
|
||||||
|
version = "0.100.1"
|
||||||
|
dependencies = [
|
||||||
|
"nu-command",
|
||||||
|
"nu-plugin",
|
||||||
|
"nu-plugin-test-support",
|
||||||
|
"nu-protocol",
|
||||||
|
"selinux",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu_plugin_stress_internals"
|
name = "nu_plugin_stress_internals"
|
||||||
version = "0.100.1"
|
version = "0.100.1"
|
||||||
|
@ -52,6 +52,7 @@ members = [
|
|||||||
"crates/nu_plugin_custom_values",
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu_plugin_formats",
|
"crates/nu_plugin_formats",
|
||||||
"crates/nu_plugin_polars",
|
"crates/nu_plugin_polars",
|
||||||
|
"crates/nu_plugin_selinux",
|
||||||
"crates/nu_plugin_stress_internals",
|
"crates/nu_plugin_stress_internals",
|
||||||
"crates/nu-std",
|
"crates/nu-std",
|
||||||
"crates/nu-table",
|
"crates/nu-table",
|
||||||
|
@ -1073,11 +1073,10 @@ fn flag_completions() {
|
|||||||
// Test completions for the 'ls' flags
|
// Test completions for the 'ls' flags
|
||||||
let suggestions = completer.complete("ls -", 4);
|
let suggestions = completer.complete("ls -", 4);
|
||||||
|
|
||||||
assert_eq!(20, suggestions.len());
|
assert_eq!(18, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
"--all".into(),
|
"--all".into(),
|
||||||
"--context".into(),
|
|
||||||
"--directory".into(),
|
"--directory".into(),
|
||||||
"--du".into(),
|
"--du".into(),
|
||||||
"--full-paths".into(),
|
"--full-paths".into(),
|
||||||
@ -1095,7 +1094,6 @@ fn flag_completions() {
|
|||||||
"-m".into(),
|
"-m".into(),
|
||||||
"-s".into(),
|
"-s".into(),
|
||||||
"-t".into(),
|
"-t".into(),
|
||||||
"-Z".into(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
|
@ -21,6 +21,8 @@ use std::{
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Ls;
|
pub struct Ls;
|
||||||
|
|
||||||
|
pub type EntryMapper = (dyn Fn(&std::path::Path, Value) -> Value + Send + Sync);
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct Args {
|
struct Args {
|
||||||
all: bool,
|
all: bool,
|
||||||
@ -31,12 +33,66 @@ struct Args {
|
|||||||
directory: bool,
|
directory: bool,
|
||||||
use_mime_type: bool,
|
use_mime_type: bool,
|
||||||
use_threads: bool,
|
use_threads: bool,
|
||||||
security_context: bool,
|
|
||||||
call_span: Span,
|
call_span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GLOB_CHARS: &[char] = &['*', '?', '['];
|
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 {
|
impl Command for Ls {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"ls"
|
"ls"
|
||||||
@ -50,6 +106,34 @@ impl Command for Ls {
|
|||||||
vec!["dir"]
|
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 {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("ls")
|
Signature::build("ls")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
@ -59,7 +143,7 @@ impl Command for Ls {
|
|||||||
.switch("all", "Show hidden files", Some('a'))
|
.switch("all", "Show hidden files", Some('a'))
|
||||||
.switch(
|
.switch(
|
||||||
"long",
|
"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'),
|
Some('l'),
|
||||||
)
|
)
|
||||||
.switch(
|
.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("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("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)
|
.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> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
@ -234,6 +231,7 @@ fn ls_for_one_pattern(
|
|||||||
args: Args,
|
args: Args,
|
||||||
signals: Signals,
|
signals: Signals,
|
||||||
cwd: PathBuf,
|
cwd: PathBuf,
|
||||||
|
map_entry: &EntryMapper,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
fn create_pool(num_threads: usize) -> Result<rayon::ThreadPool, ShellError> {
|
fn create_pool(num_threads: usize) -> Result<rayon::ThreadPool, ShellError> {
|
||||||
match rayon::ThreadPoolBuilder::new()
|
match rayon::ThreadPoolBuilder::new()
|
||||||
@ -263,7 +261,6 @@ fn ls_for_one_pattern(
|
|||||||
use_mime_type,
|
use_mime_type,
|
||||||
use_threads,
|
use_threads,
|
||||||
call_span,
|
call_span,
|
||||||
security_context,
|
|
||||||
} = args;
|
} = args;
|
||||||
let pattern_arg = {
|
let pattern_arg = {
|
||||||
if let Some(path) = pattern_arg {
|
if let Some(path) = pattern_arg {
|
||||||
@ -450,8 +447,8 @@ fn ls_for_one_pattern(
|
|||||||
du,
|
du,
|
||||||
&signals_clone,
|
&signals_clone,
|
||||||
use_mime_type,
|
use_mime_type,
|
||||||
args.full_paths,
|
full_paths,
|
||||||
security_context,
|
map_entry,
|
||||||
);
|
);
|
||||||
match entry {
|
match entry {
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
@ -570,7 +567,7 @@ pub(crate) fn dir_entry_dict(
|
|||||||
signals: &Signals,
|
signals: &Signals,
|
||||||
use_mime_type: bool,
|
use_mime_type: bool,
|
||||||
full_symlink_target: bool,
|
full_symlink_target: bool,
|
||||||
security_context: bool,
|
map_entry: &EntryMapper,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if metadata.is_none() {
|
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 long {
|
||||||
if let Some(md) = metadata {
|
if let Some(md) = metadata {
|
||||||
record.push("readonly", Value::bool(md.permissions().readonly(), span));
|
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));
|
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
|
// 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)] is just to make Clippy happy, remove if you ever want to use this on other platforms
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn unix_time_to_local_date_time(secs: i64) -> Option<DateTime<Local>> {
|
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 cd::Cd;
|
||||||
pub use du::Du;
|
pub use du::Du;
|
||||||
pub use glob::Glob;
|
pub use glob::Glob;
|
||||||
|
pub use ls::EntryMapper as LsEntryMapper;
|
||||||
pub use ls::Ls;
|
pub use ls::Ls;
|
||||||
pub use mktemp::Mktemp;
|
pub use mktemp::Mktemp;
|
||||||
pub use rm::Rm;
|
pub use rm::Rm;
|
||||||
|
@ -862,17 +862,3 @@ fn consistent_list_order() {
|
|||||||
assert_eq!(no_arg.out, with_arg.out);
|
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::float(1.0, Span::new(0, 10)),
|
||||||
Value::string("something", Span::new(0, 10)),
|
Value::string("something", Span::new(0, 10)),
|
||||||
],
|
],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![(
|
named: vec![(
|
||||||
Spanned {
|
Spanned {
|
||||||
item: "name".to_string(),
|
item: "name".to_string(),
|
||||||
|
@ -809,6 +809,7 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: PipelineData::Value(Value::test_int(-1), Some(metadata0.clone())),
|
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 {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: values
|
input: values
|
||||||
@ -1078,6 +1080,7 @@ fn interface_run() -> Result<(), ShellError> {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: PipelineData::Empty,
|
input: PipelineData::Empty,
|
||||||
@ -1351,6 +1354,7 @@ fn prepare_plugin_call_run() {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: span,
|
head: span,
|
||||||
positional: vec![Value::test_int(4)],
|
positional: vec![Value::test_int(4)],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![("x".to_owned().into_spanned(span), Some(Value::test_int(6)))],
|
named: vec![("x".to_owned().into_spanned(span), Some(Value::test_int(6)))],
|
||||||
},
|
},
|
||||||
input: PipelineData::Empty,
|
input: PipelineData::Empty,
|
||||||
@ -1363,6 +1367,7 @@ fn prepare_plugin_call_run() {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: span,
|
head: span,
|
||||||
positional: vec![cv_ok.clone()],
|
positional: vec![cv_ok.clone()],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![("ok".to_owned().into_spanned(span), Some(cv_ok.clone()))],
|
named: vec![("ok".to_owned().into_spanned(span), Some(cv_ok.clone()))],
|
||||||
},
|
},
|
||||||
input: PipelineData::Empty,
|
input: PipelineData::Empty,
|
||||||
@ -1375,6 +1380,7 @@ fn prepare_plugin_call_run() {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: span,
|
head: span,
|
||||||
positional: vec![cv_bad.clone()],
|
positional: vec![cv_bad.clone()],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: PipelineData::Empty,
|
input: PipelineData::Empty,
|
||||||
@ -1387,6 +1393,7 @@ fn prepare_plugin_call_run() {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: span,
|
head: span,
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![("bad".to_owned().into_spanned(span), Some(cv_bad.clone()))],
|
named: vec![("bad".to_owned().into_spanned(span), Some(cv_bad.clone()))],
|
||||||
},
|
},
|
||||||
input: PipelineData::Empty,
|
input: PipelineData::Empty,
|
||||||
@ -1399,6 +1406,7 @@ fn prepare_plugin_call_run() {
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: span,
|
head: span,
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
// Shouldn't check input - that happens somewhere else
|
// Shouldn't check input - that happens somewhere else
|
||||||
|
@ -14,6 +14,7 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
nu-engine = { path = "../nu-engine", version = "0.100.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
nu-utils = { path = "../nu-utils", version = "0.100.1" }
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{self, Expression},
|
ast::{self, Expression},
|
||||||
engine::{Call, CallImpl, EngineState, Stack},
|
engine::{Call, CallImpl, EngineState, Stack},
|
||||||
@ -22,6 +23,8 @@ pub struct EvaluatedCall {
|
|||||||
pub head: Span,
|
pub head: Span,
|
||||||
/// Values of positional arguments
|
/// Values of positional arguments
|
||||||
pub positional: Vec<Value>,
|
pub positional: Vec<Value>,
|
||||||
|
/// Whether positional arguments were used, including spreads
|
||||||
|
pub has_positional_args: bool,
|
||||||
/// Names and values of named arguments
|
/// Names and values of named arguments
|
||||||
pub named: Vec<(Spanned<String>, Option<Value>)>,
|
pub named: Vec<(Spanned<String>, Option<Value>)>,
|
||||||
}
|
}
|
||||||
@ -32,6 +35,7 @@ impl EvaluatedCall {
|
|||||||
EvaluatedCall {
|
EvaluatedCall {
|
||||||
head,
|
head,
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +53,7 @@ impl EvaluatedCall {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn add_positional(&mut self, value: Value) -> &mut Self {
|
pub fn add_positional(&mut self, value: Value) -> &mut Self {
|
||||||
self.positional.push(value);
|
self.positional.push(value);
|
||||||
|
self.has_positional_args = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +93,7 @@ impl EvaluatedCall {
|
|||||||
/// Builder variant of [`.add_positional()`](Self::add_positional).
|
/// Builder variant of [`.add_positional()`](Self::add_positional).
|
||||||
pub fn with_positional(mut self, value: Value) -> Self {
|
pub fn with_positional(mut self, value: Value) -> Self {
|
||||||
self.add_positional(value);
|
self.add_positional(value);
|
||||||
|
self.has_positional_args = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +150,7 @@ impl EvaluatedCall {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
head: call.head,
|
head: call.head,
|
||||||
positional,
|
positional,
|
||||||
|
has_positional_args: call.has_positional_args(stack, 0),
|
||||||
named,
|
named,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -160,6 +167,7 @@ impl EvaluatedCall {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
head: call.head,
|
head: call.head,
|
||||||
positional,
|
positional,
|
||||||
|
has_positional_args: call.has_positional_args(stack, 0),
|
||||||
named,
|
named,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -178,6 +186,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # None
|
/// # None
|
||||||
@ -194,6 +203,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
||||||
/// # None
|
/// # None
|
||||||
@ -210,6 +220,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::bool(true, Span::unknown()))
|
/// # Some(Value::bool(true, Span::unknown()))
|
||||||
@ -226,6 +237,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::bool(false, Span::unknown()))
|
/// # Some(Value::bool(false, Span::unknown()))
|
||||||
@ -242,6 +254,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: true,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::int(1, Span::unknown()))
|
/// # Some(Value::int(1, Span::unknown()))
|
||||||
@ -289,6 +302,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: true,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::int(123, null_span))
|
/// # Some(Value::int(123, null_span))
|
||||||
@ -310,6 +324,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![],
|
/// # named: vec![],
|
||||||
/// # };
|
/// # };
|
||||||
/// let opt_foo = match call.get_flag_value("foo") {
|
/// let opt_foo = match call.get_flag_value("foo") {
|
||||||
@ -344,6 +359,7 @@ impl EvaluatedCall {
|
|||||||
/// # Value::string("b".to_owned(), null_span),
|
/// # Value::string("b".to_owned(), null_span),
|
||||||
/// # Value::string("c".to_owned(), null_span),
|
/// # Value::string("c".to_owned(), null_span),
|
||||||
/// # ],
|
/// # ],
|
||||||
|
/// # has_positional_args: true,
|
||||||
/// # named: vec![],
|
/// # named: vec![],
|
||||||
/// # };
|
/// # };
|
||||||
/// let arg = match call.nth(1) {
|
/// let arg = match call.nth(1) {
|
||||||
@ -370,6 +386,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::int(123, null_span))
|
/// # Some(Value::int(123, null_span))
|
||||||
@ -387,6 +404,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
/// # Spanned { item: "bar".to_owned(), span: null_span},
|
||||||
/// # Some(Value::int(123, null_span))
|
/// # Some(Value::int(123, null_span))
|
||||||
@ -404,6 +422,7 @@ impl EvaluatedCall {
|
|||||||
/// # let call = EvaluatedCall {
|
/// # let call = EvaluatedCall {
|
||||||
/// # head: null_span,
|
/// # head: null_span,
|
||||||
/// # positional: Vec::new(),
|
/// # positional: Vec::new(),
|
||||||
|
/// # has_positional_args: false,
|
||||||
/// # named: vec![(
|
/// # named: vec![(
|
||||||
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
/// # Spanned { item: "foo".to_owned(), span: null_span},
|
||||||
/// # Some(Value::string("abc".to_owned(), 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("two".to_owned(), null_span),
|
||||||
/// # Value::string("three".to_owned(), null_span),
|
/// # Value::string("three".to_owned(), null_span),
|
||||||
/// # ],
|
/// # ],
|
||||||
|
/// # has_positional_args: true,
|
||||||
/// # named: Vec::new(),
|
/// # named: Vec::new(),
|
||||||
/// # };
|
/// # };
|
||||||
/// let args = call.rest::<String>(0);
|
/// let args = call.rest::<String>(0);
|
||||||
@ -497,6 +517,7 @@ mod test {
|
|||||||
Value::float(1.0, Span::new(0, 10)),
|
Value::float(1.0, Span::new(0, 10)),
|
||||||
Value::string("something", Span::new(0, 10)),
|
Value::string("something", Span::new(0, 10)),
|
||||||
],
|
],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![
|
named: vec![
|
||||||
(
|
(
|
||||||
Spanned {
|
Spanned {
|
||||||
|
@ -372,6 +372,7 @@ fn manager_consume_call_run_forwards_to_receiver_with_context() -> Result<(), Sh
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: PipelineDataHeader::Empty,
|
input: PipelineDataHeader::Empty,
|
||||||
@ -406,6 +407,7 @@ fn manager_consume_call_run_forwards_to_receiver_with_pipeline_data() -> Result<
|
|||||||
call: EvaluatedCall {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
},
|
},
|
||||||
input: PipelineDataHeader::list_stream(ListStreamInfo::new(6, Span::test_data())),
|
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 {
|
call: EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![value.clone()],
|
positional: vec![value.clone()],
|
||||||
|
has_positional_args: true,
|
||||||
named: vec![(
|
named: vec![(
|
||||||
Spanned {
|
Spanned {
|
||||||
item: "flag".into(),
|
item: "flag".into(),
|
||||||
|
@ -139,6 +139,7 @@ mod tests {
|
|||||||
let call = EvaluatedCall {
|
let call = EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
named: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,6 +169,7 @@ mod tests {
|
|||||||
let call = EvaluatedCall {
|
let call = EvaluatedCall {
|
||||||
head: Span::test_data(),
|
head: Span::test_data(),
|
||||||
positional: vec![],
|
positional: vec![],
|
||||||
|
has_positional_args: false,
|
||||||
named: vec![],
|
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_example,
|
||||||
nu_plugin_custom_values,
|
nu_plugin_custom_values,
|
||||||
nu_plugin_formats,
|
nu_plugin_formats,
|
||||||
nu_plugin_polars
|
nu_plugin_polars,
|
||||||
|
nu_plugin_selinux
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
@ -356,6 +356,7 @@ export def build [
|
|||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
nu_plugin_custom_values,
|
nu_plugin_custom_values,
|
||||||
nu_plugin_formats,
|
nu_plugin_formats,
|
||||||
|
nu_plugin_selinux
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
@ -428,6 +429,7 @@ export def install [
|
|||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
nu_plugin_custom_values,
|
nu_plugin_custom_values,
|
||||||
nu_plugin_formats,
|
nu_plugin_formats,
|
||||||
|
nu_plugin_selinux
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
Loading…
Reference in New Issue
Block a user