Skip to content

Commit 7d02194

Browse files
committed
Improve error reporting on sudo execution error
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
1 parent 9fbca1c commit 7d02194

File tree

4 files changed

+86
-34
lines changed

4 files changed

+86
-34
lines changed

crates/common/tedge_config/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod sudo;
22
pub use sudo::SudoCommandBuilder;
3+
pub use sudo::SudoError;
34
pub mod cli;
45
mod system_toml;
56
pub use system_toml::*;

crates/common/tedge_config/src/sudo.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::ffi::OsStr;
22
use std::process::Command;
3+
use std::process::Stdio;
34
use std::sync::Arc;
45
use tracing::warn;
56

@@ -65,4 +66,69 @@ impl SudoCommandBuilder {
6566
}
6667
}
6768
}
69+
70+
/// Check that the command can be executed using sudo
71+
pub fn command_is_executable<S: AsRef<OsStr>>(
72+
&self,
73+
program: &impl AsRef<OsStr>,
74+
args: &Vec<S>,
75+
) -> Result<(), SudoError> {
76+
let status = self
77+
.command(program)
78+
.args(args)
79+
.stdout(Stdio::null())
80+
.stderr(Stdio::null())
81+
.status();
82+
match status {
83+
Ok(status) if status.success() => Ok(()),
84+
Ok(status) => match status.code() {
85+
Some(exit_code) => {
86+
if self.command_is_sudo_enabled(program, args) {
87+
Err(SudoError::CannotSudo)
88+
} else {
89+
Err(SudoError::ExecutionFailed(exit_code))
90+
}
91+
}
92+
None => Err(SudoError::ExecutionInterrupted),
93+
},
94+
Err(err) => Err(SudoError::CannotExecute(err)),
95+
}
96+
}
97+
98+
fn command_is_sudo_enabled<S: AsRef<OsStr>>(
99+
&self,
100+
program: &impl AsRef<OsStr>,
101+
args: &Vec<S>,
102+
) -> bool {
103+
if !self.enabled {
104+
return false;
105+
}
106+
let Ok(sudo) = which::which(self.sudo_program.as_ref()) else {
107+
return false;
108+
};
109+
let status = Command::new(sudo)
110+
.arg("-n")
111+
.arg("--list")
112+
.arg(program)
113+
.args(args)
114+
.stdout(Stdio::null())
115+
.stderr(Stdio::null())
116+
.status();
117+
matches!(status, Ok(status) if status.success())
118+
}
119+
}
120+
121+
#[derive(thiserror::Error, Debug)]
122+
pub enum SudoError {
123+
#[error("The user has not been authorized to run the command with sudo")]
124+
CannotSudo,
125+
126+
#[error(transparent)]
127+
CannotExecute(#[from] std::io::Error),
128+
129+
#[error("The command returned a non-zero exit code: {0}")]
130+
ExecutionFailed(i32),
131+
132+
#[error("The command has been interrupted by a signal")]
133+
ExecutionInterrupted,
68134
}

crates/core/plugin_sm/src/plugin.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pub trait Plugin {
138138
if failed_updates.is_empty() {
139139
let outcome = self.update_list(&updates, command_log.as_deref_mut()).await;
140140
if let Err(err @ SoftwareError::UpdateListNotSupported(_)) = outcome {
141-
info!("{err}");
141+
info!(target: "SM plugins", "{err}");
142142
for update in updates.iter() {
143143
if let Err(error) = self
144144
.apply(update, command_log.as_deref_mut(), download_path)
@@ -228,7 +228,7 @@ pub trait Plugin {
228228
url: url.url().to_string(),
229229
})
230230
{
231-
error!("Download error: {err:#?}");
231+
error!(target: "SM plugins", "Download error: {err:#?}");
232232
if let Some(ref mut logger) = command_log {
233233
logger
234234
.write(format!("error: {}\n", &err).as_bytes())

crates/core/plugin_sm/src/plugin_manager.rs

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ use camino::Utf8PathBuf;
55
use std::borrow::Cow;
66
use std::collections::BTreeMap;
77
use std::fs;
8-
use std::io::ErrorKind;
98
use std::io::{self};
109
use std::path::Path;
1110
use std::path::PathBuf;
12-
use std::process::Stdio;
1311
use tedge_api::commands::CommandStatus;
1412
use tedge_api::commands::SoftwareListCommand;
1513
use tedge_api::commands::SoftwareUpdateCommand;
@@ -18,6 +16,7 @@ use tedge_api::SoftwareError;
1816
use tedge_api::SoftwareType;
1917
use tedge_api::DEFAULT;
2018
use tedge_config::SudoCommandBuilder;
19+
use tedge_config::SudoError;
2120
use tracing::error;
2221
use tracing::info;
2322
use tracing::warn;
@@ -106,7 +105,7 @@ impl ExternalPlugins {
106105
config_dir,
107106
};
108107
if let Err(e) = plugins.load().await {
109-
warn!(
108+
warn!(target: "SM plugins",
110109
"Reading the plugins directory ({:?}): failed with: {e:?}",
111110
&plugins.plugin_dir
112111
);
@@ -119,15 +118,15 @@ impl ExternalPlugins {
119118
.by_software_type(default_plugin_type.as_str())
120119
.is_none()
121120
{
122-
warn!(
121+
warn!(target: "SM plugins",
123122
"The configured default plugin: {} not found",
124123
default_plugin_type
125124
);
126125
}
127-
info!("Default plugin type: {}", default_plugin_type)
126+
info!(target: "SM plugins", "Default plugin type: {}", default_plugin_type)
128127
}
129128
None => {
130-
info!("Default plugin type: Not configured")
129+
info!(target: "SM plugins", "Default plugin type: Not configured")
131130
}
132131
}
133132

@@ -145,42 +144,28 @@ impl ExternalPlugins {
145144
let entry = maybe_entry?;
146145
let path = entry.path();
147146
if path.is_file() {
148-
let mut command = self.sudo.command(&path);
149-
150-
match command
151-
.arg(LIST)
152-
.stdout(Stdio::null())
153-
.stderr(Stdio::null())
154-
.status()
155-
{
156-
Ok(code) if code.success() => {
157-
info!("Plugin activated: {}", path.display());
147+
match self.sudo.command_is_executable(&path, &vec![LIST]) {
148+
Ok(()) => {
149+
info!(target: "SM plugins", "Plugin activated: {}", path.display());
158150
}
159-
160-
// If the file is not executable or returned non 0 status code we assume it is not a valid and skip further processing.
161-
Ok(_) => {
162-
error!(
163-
"File {} in plugin directory does not support list operation and may not be a valid plugin, skipping.",
151+
Err(SudoError::CannotSudo) => {
152+
error!(target: "SM plugins",
153+
"Skipping {}: not properly configured to run with sudo",
164154
path.display()
165155
);
166156
continue;
167157
}
168-
169-
Err(err) if err.kind() == ErrorKind::PermissionDenied => {
170-
error!(
171-
"File {} Permission Denied, is the file an executable?\n
172-
The file will not be registered as a plugin.",
158+
Err(SudoError::ExecutionFailed(_)) => {
159+
error!(target: "SM plugins",
160+
"Skipping {}: does not support list operation and may not be a valid plugin",
173161
path.display()
174162
);
175163
continue;
176164
}
177-
178165
Err(err) => {
179-
error!(
180-
"An error occurred while trying to run: {}: {}\n
181-
The file will not be registered as a plugin.",
182-
path.display(),
183-
err
166+
error!(target: "SM plugins",
167+
"Skipping {}: can not be launched as a plugin: {err}",
168+
path.display()
184169
);
185170
continue;
186171
}

0 commit comments

Comments
 (0)