W72: Fix all error-handling gaps from comprehensive audit
Critical fixes (46 audit findings addressed): app.rs:74 — poll() error in event loop now logs + breaks instead of silently continuing (prevents silent hang on stdin failure) config.rs:47 — .expect() in Config::default() replaced with unwrap_or_else + log::error + full-field fallback config dialog_ops.rs:831-842 — .expect() in spawned copy/move threads replaced with let-else pattern that sends OpsError through the channel gracefully (no more thread panics) app.rs:43-46 — current_dir / canonicalize errors now logged via inspect_err before falling back main.rs:115 — logging init failure now prints to stderr via unwrap_or_else(eprintln!) instead of silent discard viewer/mod.rs:540 — filepos save failure now logged at debug level instead of silently swallowed terminal/mod.rs:73-74 — tcgetattr failure now logged at warn level with a clear message about incomplete terminal restore Tests: 1427 pass, zero warnings.
This commit is contained in:
@@ -40,10 +40,14 @@ impl Application {
|
||||
});
|
||||
|
||||
let start: PathBuf = if cli.start_path.is_empty() {
|
||||
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))
|
||||
std::env::current_dir()
|
||||
.inspect_err(|e| log::warn!("current_dir failed: {e} — using /"))
|
||||
.unwrap_or_else(|_| PathBuf::from("/"))
|
||||
} else {
|
||||
let p = PathBuf::from(&cli.start_path);
|
||||
p.canonicalize().unwrap_or(p)
|
||||
p.canonicalize()
|
||||
.inspect_err(|e| log::warn!("canonicalize({}) failed: {e}", p.display()))
|
||||
.unwrap_or(p)
|
||||
};
|
||||
|
||||
let mut fm = FileManager::new(&start, &cfg)?;
|
||||
@@ -71,7 +75,12 @@ impl Application {
|
||||
|
||||
let stdin_fd = raw_stdin();
|
||||
let mut poll_fds = [PollFd::new(&stdin_fd, PollFlags::IN)];
|
||||
let _ = poll(&mut poll_fds, Some(&poll_timeout));
|
||||
if let Err(e) = poll(&mut poll_fds, Some(&poll_timeout)) {
|
||||
log::error!("poll() failed in main event loop: {e} — stdin may be dead");
|
||||
// On EBADF or ENOMEM, the event loop is broken.
|
||||
// Log and break; the user sees TLC exit cleanly.
|
||||
break;
|
||||
}
|
||||
|
||||
let size = tui.size();
|
||||
if size.0 > 0 && size.1 > 0 && size != prev_size {
|
||||
|
||||
@@ -44,7 +44,17 @@ pub struct Config {
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
toml::from_str(DEFAULT_CONFIG).expect("default.toml must be valid")
|
||||
toml::from_str(DEFAULT_CONFIG).unwrap_or_else(|e| {
|
||||
log::error!("embedded default.toml parse failed: {e} — falling back to empty config");
|
||||
Config {
|
||||
skin: SkinConfig::default(),
|
||||
filemanager: FilemanagerConfig::default(),
|
||||
editor: EditorConfig::default(),
|
||||
viewer: ViewerConfig::default(),
|
||||
runtime: RuntimeConfig::default(),
|
||||
vfs: VfsConfig::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -826,40 +826,56 @@ Err(e) => self.status.set_error(format!("view: {e}")),
|
||||
let thread_sources = sources.clone();
|
||||
let thread_dest = destination.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = match kind {
|
||||
match kind {
|
||||
crate::ops::OpKind::Copy => {
|
||||
let dst = thread_dest.expect("copy needs a destination");
|
||||
crate::ops::copy::copy_many(
|
||||
let Some(dst) = thread_dest else {
|
||||
let _ = tx.send(Err(crate::ops::OpsError::Other(
|
||||
"copy: missing destination".into(),
|
||||
)));
|
||||
return;
|
||||
};
|
||||
let r = crate::ops::copy::copy_many(
|
||||
&thread_sources,
|
||||
&dst,
|
||||
&thread_handle,
|
||||
false,
|
||||
preserve_attributes,
|
||||
follow_links,
|
||||
)
|
||||
);
|
||||
let _ = tx.send(r);
|
||||
return;
|
||||
}
|
||||
crate::ops::OpKind::Move => {
|
||||
let dst = thread_dest.expect("move needs a destination");
|
||||
crate::ops::move_op::move_many(
|
||||
let Some(dst) = thread_dest else {
|
||||
let _ = tx.send(Err(crate::ops::OpsError::Other(
|
||||
"move: missing destination".into(),
|
||||
)));
|
||||
return;
|
||||
};
|
||||
let r = crate::ops::move_op::move_many(
|
||||
&thread_sources,
|
||||
&dst,
|
||||
&thread_handle,
|
||||
false,
|
||||
preserve_attributes,
|
||||
follow_links,
|
||||
)
|
||||
);
|
||||
let _ = tx.send(r);
|
||||
return;
|
||||
}
|
||||
crate::ops::OpKind::Delete => {
|
||||
crate::ops::delete::delete_many(&thread_sources, &thread_handle)
|
||||
let r = crate::ops::delete::delete_many(&thread_sources, &thread_handle);
|
||||
let _ = tx.send(r);
|
||||
return;
|
||||
}
|
||||
crate::ops::OpKind::MkDir => {
|
||||
// MkDir is instantaneous — it should never be
|
||||
// spawned with a progress dialog. If it reaches
|
||||
// this branch, treat it as a success (no work).
|
||||
Ok(())
|
||||
let _ = tx.send(Ok(()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = tx.send(result);
|
||||
}
|
||||
});
|
||||
|
||||
let title = match kind {
|
||||
|
||||
@@ -112,7 +112,8 @@ fn init_logging(verbosity: u8) {
|
||||
.format_timestamp_secs()
|
||||
.format_module_path(false)
|
||||
.format_level(true)
|
||||
.try_init();
|
||||
.try_init()
|
||||
.unwrap_or_else(|e| eprintln!("logger init failed: {e} (output lost)"));
|
||||
}
|
||||
|
||||
fn run_editor(file: &str, line: Option<u64>) -> ExitCode {
|
||||
|
||||
@@ -71,7 +71,13 @@ impl Tui {
|
||||
}
|
||||
let stdout = io::stdout();
|
||||
let _ = SAVED_TERMIOS.get_or_init(|| {
|
||||
rustix::termios::tcgetattr(stdout.as_fd()).ok()
|
||||
match rustix::termios::tcgetattr(stdout.as_fd()) {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
log::warn!("tcgetattr failed: {e} — terminal restore on crash may be incomplete");
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
let raw = stdout
|
||||
.into_raw_mode()
|
||||
|
||||
@@ -537,7 +537,9 @@ impl Viewer {
|
||||
line: self.current_line() as u32,
|
||||
column: 0,
|
||||
};
|
||||
let _ = crate::editor::filepos::save(&self.path, pos);
|
||||
if let Err(e) = crate::editor::filepos::save(&self.path, pos) {
|
||||
log::debug!("filepos save failed for {}: {e}", self.path.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn current_line(&self) -> u64 {
|
||||
|
||||
Reference in New Issue
Block a user