Return Result from wopen_cloexec

This commit is contained in:
PolyMeilex 2024-01-20 23:13:13 +01:00 committed by Johannes Altmanninger
parent 86afc7832d
commit 23301e4895
12 changed files with 298 additions and 236 deletions

View file

@ -30,7 +30,7 @@ errno = "0.2.8"
lazy_static = "1.4.0"
libc = "0.2.137"
lru = "0.10.0"
nix = { version = "0.25.0", default-features = false, features = ["inotify", "resource"] }
nix = { version = "0.25.0", default-features = false, features = ["inotify", "resource", "fs"] }
num-traits = "0.2.15"
once_cell = "1.17.0"
rand = { version = "0.8.5", features = ["small_rng"] }

View file

@ -336,8 +336,7 @@ fn test_autoload() {
}
fn touch_file(path: &wstr) {
let fd = wopen_cloexec(path, O_RDWR | O_CREAT, 0o666);
assert!(fd >= 0);
let fd = wopen_cloexec(path, O_RDWR | O_CREAT, 0o666).unwrap();
write_loop(&fd, "Hello".as_bytes()).unwrap();
unsafe { libc::close(fd) };
}

View file

@ -9,7 +9,7 @@ use crate::{
};
use errno::{self, Errno};
use libc::{fchdir, EACCES, ELOOP, ENOENT, ENOTDIR, EPERM, O_RDONLY};
use std::sync::Arc;
use std::{os::fd::AsRawFd, sync::Arc};
// The cd builtin. Changes the current directory to the one specified or to $HOME if none is
// specified. The directory can be relative to any directory in the CDPATH variable.
@ -86,32 +86,47 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Optio
errno::set_errno(Errno(0));
// We need to keep around the fd for this directory, in the parser.
let dir_fd = Arc::new(AutoCloseFd::new(wopen_cloexec(&norm_dir, O_RDONLY, 0)));
let res = wopen_cloexec(&norm_dir, O_RDONLY, 0)
.map(AutoCloseFd::new)
.map_err(|err| err as i32);
if !(dir_fd.is_valid() && unsafe { fchdir(dir_fd.fd()) } == 0) {
// Some errors we skip and only report if nothing worked.
// ENOENT in particular is very low priority
// - if in another directory there was a *file* by the correct name
// we prefer *that* error because it's more specific
if errno::errno().0 == ENOENT {
let tmp = wreadlink(&norm_dir);
// clippy doesn't like this is_some/unwrap pair, but using if let is harder to read IMO
#[allow(clippy::unnecessary_unwrap)]
if broken_symlink.is_empty() && tmp.is_some() {
broken_symlink = norm_dir;
broken_symlink_target = tmp.unwrap();
} else if best_errno == 0 {
best_errno = errno::errno().0;
}
continue;
} else if errno::errno().0 == ENOTDIR {
best_errno = errno::errno().0;
continue;
let res = res.and_then(|fd| {
if unsafe { fchdir(fd.as_raw_fd()) } == 0 {
Ok(fd)
} else {
Err(errno::errno().0)
}
best_errno = errno::errno().0;
break;
}
});
let fd = match res {
Ok(raw_fd) => raw_fd,
Err(err) => {
// Some errors we skip and only report if nothing worked.
// ENOENT in particular is very low priority
// - if in another directory there was a *file* by the correct name
// we prefer *that* error because it's more specific
if err == ENOENT {
let tmp = wreadlink(&norm_dir);
// clippy doesn't like this is_some/unwrap pair, but using if let is harder to read IMO
#[allow(clippy::unnecessary_unwrap)]
if broken_symlink.is_empty() && tmp.is_some() {
broken_symlink = norm_dir;
broken_symlink_target = tmp.unwrap();
} else if best_errno == 0 {
best_errno = errno::errno().0;
}
continue;
} else if err == ENOTDIR {
best_errno = err;
continue;
}
best_errno = err;
break;
}
};
// We need to keep around the fd for this directory, in the parser.
let dir_fd = Arc::new(fd);
// Stash the fd for the cwd in the parser.
parser.libdata_mut().cwd_fd = Some(dir_fd);

View file

@ -1,6 +1,8 @@
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use crate::{
common::{escape, scoped_push_replacer, FilenameRef},
fds::{wopen_cloexec, AutoCloseFd},
fds::wopen_cloexec,
nix::isatty,
parser::Block,
reader::reader_read,
@ -48,8 +50,7 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
func_filename = FilenameRef::new(L!("-").to_owned());
fd = streams.stdin_fd;
} else {
opened_fd = AutoCloseFd::new(wopen_cloexec(args[optind], O_RDONLY, 0));
if !opened_fd.is_valid() {
let Ok(raw_fd) = wopen_cloexec(args[optind], O_RDONLY, 0) else {
let esc = escape(args[optind]);
streams.err.append(wgettext_fmt!(
"%ls: Error encountered while sourcing file '%ls':\n",
@ -58,9 +59,10 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
));
builtin_wperror(cmd, streams);
return STATUS_CMD_ERROR;
}
};
opened_fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
fd = opened_fd.fd();
fd = opened_fd.as_raw_fd();
let mut buf: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut buf) } == -1 {
let esc = escape(args[optind]);

View file

@ -19,12 +19,12 @@ use crate::wutil::{
wunlink, FileId, INVALID_FILE_ID,
};
use errno::{errno, Errno};
use libc::{EINTR, ENOTSUP, EOPNOTSUPP, LOCK_EX, O_CREAT, O_RDONLY, O_RDWR};
use libc::{EINTR, LOCK_EX, O_CREAT, O_RDONLY, O_RDWR};
use std::collections::hash_map::Entry;
use std::collections::HashSet;
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::os::fd::RawFd;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::prelude::MetadataExt;
// Pull in the O_EXLOCK constant if it is defined, otherwise set it to 0.
@ -236,14 +236,13 @@ impl EnvUniversal {
// Open the file.
let vars_fd = self.open_and_acquire_lock();
if !vars_fd.is_valid() {
let Some(vars_fd) = vars_fd else {
FLOG!(uvar_file, "universal log open_and_acquire_lock() failed");
return false;
}
};
// Read from it.
assert!(vars_fd.is_valid());
self.load_from_fd(vars_fd.fd(), callbacks);
self.load_from_fd(vars_fd.as_raw_fd(), callbacks);
if self.ok_to_save {
self.save(&directory)
@ -389,18 +388,17 @@ impl EnvUniversal {
return true;
}
let mut result = false;
let fd = AutoCloseFd::new(open_cloexec(&self.narrow_vars_path, O_RDONLY, 0));
if fd.is_valid() {
FLOG!(uvar_file, "universal log reading from file");
self.load_from_fd(fd.fd(), callbacks);
result = true;
}
result
let Ok(raw_fd) = open_cloexec(&self.narrow_vars_path, O_RDONLY, 0) else {
return false;
};
let fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw_fd) };
FLOG!(uvar_file, "universal log reading from file");
self.load_from_fd(fd.as_raw_fd(), callbacks);
true
}
fn load_from_fd(&mut self, fd: RawFd, callbacks: &mut CallbackDataList) {
assert!(fd >= 0);
// Get the dev / inode.
let current_file = file_id_for_fd(fd);
if current_file == self.last_read_file {
@ -426,7 +424,7 @@ impl EnvUniversal {
}
// Functions concerned with saving.
fn open_and_acquire_lock(&mut self) -> AutoCloseFd {
fn open_and_acquire_lock(&mut self) -> Option<OwnedFd> {
// Attempt to open the file for reading at the given path, atomically acquiring a lock. On BSD,
// we can use O_EXLOCK. On Linux, we open the file, take a lock, and then compare fstat() to
// stat(); if they match, it means that the file was not replaced before we acquired the lock.
@ -441,57 +439,62 @@ impl EnvUniversal {
locked_by_open = true;
}
let mut fd = AutoCloseFd::empty();
while !fd.is_valid() {
fd = AutoCloseFd::new(wopen_cloexec(&self.vars_path, flags, 0o644));
if !fd.is_valid() {
let err = errno();
if err.0 == EINTR {
continue; // signaled; try again
}
if O_EXLOCK != 0 {
if (flags & O_EXLOCK) != 0 && [ENOTSUP, EOPNOTSUPP].contains(&err.0) {
// Filesystem probably does not support locking. Give up on locking.
// Note that on Linux the two errno symbols have the same value but on BSD they're
// different.
flags &= !O_EXLOCK;
self.do_flock = false;
locked_by_open = false;
continue;
let mut res_fd = None;
while res_fd.is_none() {
let raw = match wopen_cloexec(&self.vars_path, flags, 0o644) {
Ok(raw) => raw,
Err(err) => {
if err == nix::Error::EINTR {
continue; // signaled; try again
}
}
FLOG!(
error,
wgettext_fmt!(
"Unable to open universal variable file '%s': %s",
&self.vars_path,
err.to_string()
)
);
break;
}
assert!(fd.is_valid(), "Should have a valid fd here");
if O_EXLOCK != 0 {
if (flags & O_EXLOCK) != 0
&& [nix::Error::ENOTSUP, nix::Error::EOPNOTSUPP].contains(&err)
{
// Filesystem probably does not support locking. Give up on locking.
// Note that on Linux the two errno symbols have the same value but on BSD they're
// different.
flags &= !O_EXLOCK;
self.do_flock = false;
locked_by_open = false;
continue;
}
}
FLOG!(
error,
wgettext_fmt!(
"Unable to open universal variable file '%s': %s",
&self.vars_path,
err.to_string()
)
);
break;
}
};
assert!(raw >= 0, "Should have a valid fd here");
let fd = unsafe { OwnedFd::from_raw_fd(raw) };
// Lock if we want to lock and open() didn't do it for us.
// If flock fails, give up on locking forever.
if self.do_flock && !locked_by_open {
if !flock_uvar_file(fd.fd()) {
if !flock_uvar_file(fd.as_raw_fd()) {
self.do_flock = false;
}
}
// Hopefully we got the lock. However, it's possible the file changed out from under us
// while we were waiting for the lock. Make sure that didn't happen.
if file_id_for_fd(fd.fd()) != file_id_for_path(&self.vars_path) {
if file_id_for_fd(fd.as_raw_fd()) != file_id_for_path(&self.vars_path) {
// Oops, it changed! Try again.
fd.close();
drop(fd);
} else {
res_fd = Some(fd);
}
}
fd
res_fd
}
fn open_temporary_file(&mut self, directory: &wstr, out_path: &mut WString) -> AutoCloseFd {

View file

@ -367,7 +367,7 @@ pub fn is_thompson_shell_script(path: &CStr) -> bool {
let e = errno();
let mut res = false;
let fd = open_cloexec(path, O_RDONLY | O_NOCTTY, 0);
if fd != -1 {
if let Ok(fd) = fd {
let mut file = unsafe { std::fs::File::from_raw_fd(fd) };
let mut buf = [b'\0'; 256];
if let Ok(got) = file.read(&mut buf) {

View file

@ -10,7 +10,7 @@ use libc::{
c_int, EINTR, FD_CLOEXEC, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC,
O_NONBLOCK,
};
use nix::unistd;
use nix::{fcntl::OFlag, unistd};
use std::ffi::CStr;
use std::io::{self, Read, Write};
use std::os::unix::prelude::*;
@ -222,12 +222,14 @@ pub fn set_cloexec(fd: RawFd, should_set: bool /* = true */) -> c_int {
/// Wide character version of open() that also sets the close-on-exec flag (atomically when
/// possible).
pub fn wopen_cloexec(pathname: &wstr, flags: i32, mode: libc::c_int) -> RawFd {
pub fn wopen_cloexec(pathname: &wstr, flags: i32, mode: libc::c_int) -> nix::Result<RawFd> {
open_cloexec(wcs2zstring(pathname).as_c_str(), flags, mode)
}
/// Narrow versions of wopen_cloexec.
pub fn open_cloexec(path: &CStr, flags: i32, mode: libc::c_int) -> RawFd {
pub fn open_cloexec(path: &CStr, flags: i32, mode: libc::c_int) -> nix::Result<RawFd> {
let flags = unsafe { OFlag::from_bits_unchecked(flags) };
let mode = unsafe { nix::sys::stat::Mode::from_bits_unchecked(mode as u32) };
// Port note: the C++ version of this function had a fallback for platforms where
// O_CLOEXEC is not supported, using fcntl. In 2023, this is no longer needed.
let saved_errno = errno();
@ -236,15 +238,21 @@ pub fn open_cloexec(path: &CStr, flags: i32, mode: libc::c_int) -> RawFd {
// if we get EINTR and it's not a SIGINIT, we continue.
// If it is that's our cancel signal, so we abort.
loop {
let ret = unsafe { libc::open(path.as_ptr(), flags | O_CLOEXEC, mode) };
if ret >= 0 {
set_errno(saved_errno);
return ret;
let ret = nix::fcntl::open(path, flags | OFlag::O_CLOEXEC, mode);
match ret {
Ok(ret) => {
set_errno(saved_errno);
return Ok(ret);
}
Err(err) => {
if err != nix::Error::EINTR || signal_check_cancel() != 0 {
return ret;
}
}
}
if errno::errno().0 != EINTR || signal_check_cancel() != 0 {
return ret;
}
}
}

View file

@ -25,7 +25,7 @@ use std::{
mem,
num::NonZeroUsize,
ops::ControlFlow,
os::fd::RawFd,
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
sync::{Arc, Mutex, MutexGuard},
time::{Duration, SystemTime, UNIX_EPOCH},
};
@ -480,33 +480,35 @@ impl HistoryImpl {
let _profiler = TimeProfiler::new("load_old");
if let Some(filename) = history_filename(&self.name, L!("")) {
let file = AutoCloseFd::new(wopen_cloexec(&filename, O_RDONLY, 0));
let fd = file.fd();
if fd >= 0 {
// Take a read lock to guard against someone else appending. This is released after
// getting the file's length. We will read the file after releasing the lock, but that's
// not a problem, because we never modify already written data. In short, the purpose of
// this lock is to ensure we don't see the file size change mid-update.
//
// We may fail to lock (e.g. on lockless NFS - see issue #685. In that case, we proceed
// as if it did not fail. The risk is that we may get an incomplete history item; this
// is unlikely because we only treat an item as valid if it has a terminating newline.
let locked = unsafe { Self::maybe_lock_file(fd, LOCK_SH) };
self.file_contents = HistoryFileContents::create(fd);
self.history_file_id = if self.file_contents.is_some() {
file_id_for_fd(fd)
} else {
INVALID_FILE_ID
};
if locked {
unsafe {
Self::unlock_file(fd);
}
}
let Ok(raw_fd) = wopen_cloexec(&filename, O_RDONLY, 0) else {
return;
};
let _profiler = TimeProfiler::new("populate_from_file_contents");
self.populate_from_file_contents();
let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
// Take a read lock to guard against someone else appending. This is released after
// getting the file's length. We will read the file after releasing the lock, but that's
// not a problem, because we never modify already written data. In short, the purpose of
// this lock is to ensure we don't see the file size change mid-update.
//
// We may fail to lock (e.g. on lockless NFS - see issue #685. In that case, we proceed
// as if it did not fail. The risk is that we may get an incomplete history item; this
// is unlikely because we only treat an item as valid if it has a terminating newline.
let locked = unsafe { Self::maybe_lock_file(fd.as_raw_fd(), LOCK_SH) };
self.file_contents = HistoryFileContents::create(fd.as_raw_fd());
self.history_file_id = if self.file_contents.is_some() {
file_id_for_fd(fd.as_raw_fd())
} else {
INVALID_FILE_ID
};
if locked {
unsafe {
Self::unlock_file(fd.as_raw_fd());
}
}
let _profiler = TimeProfiler::new("populate_from_file_contents");
self.populate_from_file_contents();
}
}
@ -670,42 +672,46 @@ impl HistoryImpl {
if done {
break;
}
let target_fd_before =
wopen_cloexec(&target_name, O_RDONLY | O_CREAT, HISTORY_FILE_MODE)
.map(|raw_fd| unsafe { OwnedFd::from_raw_fd(raw_fd) });
let orig_file_id = target_fd_before
.as_ref()
.map(|fd| file_id_for_fd(fd.as_raw_fd()))
.unwrap_or(INVALID_FILE_ID);
// Open any target file, but do not lock it right away
let mut target_fd_before = AutoCloseFd::new(wopen_cloexec(
&target_name,
O_RDONLY | O_CREAT,
HISTORY_FILE_MODE,
));
let orig_file_id = file_id_for_fd(target_fd_before.fd()); // possibly invalid
if !self.rewrite_to_temporary_file(
if target_fd_before.is_valid() {
Some(target_fd_before.fd())
} else {
None
},
target_fd_before.as_ref().map(AsRawFd::as_raw_fd).ok(),
tmp_fd,
) {
// Failed to write, no good
break;
}
target_fd_before.close();
drop(target_fd_before);
// The crux! We rewrote the history file; see if the history file changed while we
// were rewriting it. Make an effort to take the lock before checking, to avoid racing.
// If the open fails, then proceed; this may be because there is no current history
let mut new_file_id = INVALID_FILE_ID;
let target_fd_after = AutoCloseFd::new(wopen_cloexec(&target_name, O_RDONLY, 0));
if target_fd_after.is_valid() {
let target_fd_after = wopen_cloexec(&target_name, O_RDONLY, 0)
.map(|raw_fd| unsafe { OwnedFd::from_raw_fd(raw_fd) });
if let Ok(target_fd_after) = target_fd_after.as_ref() {
// critical to take the lock before checking file IDs,
// and hold it until after we are done replacing.
// Also critical to check the file at the path, NOT based on our fd.
// It's only OK to replace the file while holding the lock.
// Note any lock is released when target_fd_after is closed.
unsafe {
Self::maybe_lock_file(target_fd_after.fd(), LOCK_EX);
Self::maybe_lock_file(target_fd_after.as_raw_fd(), LOCK_EX);
}
new_file_id = file_id_for_path(&target_name);
}
let can_replace_file = new_file_id == orig_file_id || new_file_id == INVALID_FILE_ID;
if !can_replace_file {
// The file has changed, so we're going to re-read it
@ -728,23 +734,23 @@ impl HistoryImpl {
// corresponds to e.g. someone running sudo -E as the very first command. If they
// did, it would be tricky to set the permissions correctly. (bash doesn't get this
// case right either).
let mut sbuf: libc::stat = unsafe { mem::zeroed() };
if target_fd_after.is_valid()
&& unsafe { fstat(target_fd_after.fd(), &mut sbuf) } >= 0
{
if unsafe { fchown(tmp_fd, sbuf.st_uid, sbuf.st_gid) } == -1 {
FLOGF!(
history_file,
"Error %d when changing ownership of history file",
errno::errno().0
);
}
if unsafe { fchmod(tmp_fd, sbuf.st_mode) } == -1 {
FLOGF!(
history_file,
"Error %d when changing mode of history file",
errno::errno().0,
);
if let Ok(target_fd_after) = target_fd_after.as_ref() {
let mut sbuf: libc::stat = unsafe { mem::zeroed() };
if unsafe { fstat(target_fd_after.as_raw_fd(), &mut sbuf) } >= 0 {
if unsafe { fchown(tmp_fd, sbuf.st_uid, sbuf.st_gid) } == -1 {
FLOGF!(
history_file,
"Error %d when changing ownership of history file",
errno::errno().0
);
}
if unsafe { fchmod(tmp_fd, sbuf.st_mode) } == -1 {
FLOGF!(
history_file,
"Error %d when changing mode of history file",
errno::errno().0,
);
}
}
}
@ -762,6 +768,8 @@ impl HistoryImpl {
// We did it
done = true;
}
drop(target_fd_after);
}
// Ensure we never leave the old file around
@ -804,34 +812,36 @@ impl HistoryImpl {
// After locking it, we need to stat the file at the path; if there is a new file there, it
// means the file was replaced and we have to try again.
// Limit our max tries so we don't do this forever.
let mut history_fd = AutoCloseFd::new(-1);
let mut history_fd = None;
for _i in 0..MAX_SAVE_TRIES {
let fd = AutoCloseFd::new(wopen_cloexec(&history_path, O_WRONLY | O_APPEND, 0));
if !fd.is_valid() {
let Ok(fd) = wopen_cloexec(&history_path, O_WRONLY | O_APPEND, 0) else {
// can't open, we're hosed
break;
}
};
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
// Exclusive lock on the entire file. This is released when we close the file (below). This
// may fail on (e.g.) lockless NFS. If so, proceed as if it did not fail; the risk is that
// we may get interleaved history items, which is considered better than no history, or
// forcing everything through the slow copy-move mode. We try to minimize this possibility
// by writing with O_APPEND.
unsafe {
Self::maybe_lock_file(fd.fd(), LOCK_EX);
Self::maybe_lock_file(fd.as_raw_fd(), LOCK_EX);
}
let file_id = file_id_for_fd(fd.fd());
let file_id = file_id_for_fd(fd.as_raw_fd());
if file_id_for_path(&history_path) == file_id {
// File IDs match, so the file we opened is still at that path
// We're going to use this fd
if file_id != self.history_file_id {
file_changed = true;
}
history_fd = fd;
history_fd = Some(fd);
break;
}
}
if history_fd.is_valid() {
if let Some(history_fd) = history_fd {
// We (hopefully successfully) took the exclusive lock. Append to the file.
// Note that this is sketchy for a few reasons:
// - Another shell may have appended its own items with a later timestamp, so our file may
@ -860,7 +870,11 @@ impl HistoryImpl {
let item = &self.new_items[self.first_unwritten_new_item_index];
if item.should_write_to_disk() {
append_history_item_to_buffer(item, &mut buffer);
res = flush_to_fd(&mut buffer, history_fd.fd(), HISTORY_OUTPUT_BUFFER_SIZE);
res = flush_to_fd(
&mut buffer,
history_fd.as_raw_fd(),
HISTORY_OUTPUT_BUFFER_SIZE,
);
if res.is_err() {
break;
}
@ -870,7 +884,7 @@ impl HistoryImpl {
}
if res.is_ok() {
res = flush_to_fd(&mut buffer, history_fd.fd(), 0);
res = flush_to_fd(&mut buffer, history_fd.as_raw_fd(), 0);
}
// Since we just modified the file, update our history_file_id to match its current state
@ -878,11 +892,12 @@ impl HistoryImpl {
// write.
// We don't update the mapping since we only appended to the file, and everything we
// appended remains in our new_items
self.history_file_id = file_id_for_fd(history_fd.fd());
self.history_file_id = file_id_for_fd(history_fd.as_raw_fd());
ok = res.is_ok();
drop(history_fd);
}
history_fd.close();
// If someone has replaced the file, forget our file state.
if file_changed {
@ -1090,28 +1105,33 @@ impl HistoryImpl {
old_file.push_utfstr(&self.name);
old_file.push_str("_history");
let mut src_fd = AutoCloseFd::new(wopen_cloexec(&old_file, O_RDONLY, 0));
if src_fd.is_valid() {
// Clear must come after we've retrieved the new_file name, and before we open
// destination file descriptor, since it destroys the name and the file.
self.clear();
let Ok(src_fd) = wopen_cloexec(&old_file, O_RDONLY, 0) else {
return;
};
let mut dst_fd = AutoCloseFd::new(wopen_cloexec(
&new_file,
O_WRONLY | O_CREAT,
HISTORY_FILE_MODE,
));
let mut src_fd = unsafe { std::fs::File::from_raw_fd(src_fd) };
let mut buf = [0; libc::BUFSIZ as usize];
while let Ok(n) = src_fd.read(&mut buf) {
if n == 0 {
break;
}
if dst_fd.write(&buf[..n]).is_err() {
// This message does not have high enough priority to be shown by default.
FLOG!(history_file, "Error when writing history file");
break;
}
// Clear must come after we've retrieved the new_file name, and before we open
// destination file descriptor, since it destroys the name and the file.
self.clear();
let Ok(dst_fd) = wopen_cloexec(&new_file, O_WRONLY | O_CREAT, HISTORY_FILE_MODE) else {
FLOG!(history_file, "Error when writing history file");
return;
};
let mut dst_fd = unsafe { std::fs::File::from_raw_fd(dst_fd) };
let mut buf = [0; libc::BUFSIZ as usize];
while let Ok(n) = src_fd.read(&mut buf) {
if n == 0 {
break;
}
if dst_fd.write(&buf[..n]).is_err() {
// This message does not have high enough priority to be shown by default.
FLOG!(history_file, "Error when writing history file");
break;
}
}
}

View file

@ -17,7 +17,7 @@ use crate::topic_monitor::topic_t;
use crate::wchar::prelude::*;
use crate::wutil::{perror, perror_io, wdirname, wstat, wwrite_to_fd};
use errno::Errno;
use libc::{EAGAIN, EEXIST, EINTR, ENOENT, ENOTDIR, EPIPE, EWOULDBLOCK, O_EXCL, STDOUT_FILENO};
use libc::{EAGAIN, EINTR, ENOENT, ENOTDIR, EPIPE, EWOULDBLOCK, O_EXCL, STDOUT_FILENO};
use std::cell::{RefCell, UnsafeCell};
use std::os::fd::RawFd;
use std::sync::atomic::{AtomicU64, Ordering};
@ -657,52 +657,61 @@ impl IoChain {
// Mark it as CLO_EXEC because we don't want it to be open in any child.
let path = path_apply_working_directory(&spec.target, pwd);
let oflags = spec.oflags();
let file = AutoCloseFd::new(wopen_cloexec(&path, oflags, OPEN_MASK));
if !file.is_valid() {
if (oflags & O_EXCL) != 0 && errno::errno().0 == EEXIST {
FLOGF!(warning, NOCLOB_ERROR, spec.target);
} else {
if should_flog!(warning) {
let err = errno::errno().0;
// If the error is that the file doesn't exist
// or there's a non-directory component,
// find the first problematic component for a better message.
if [ENOENT, ENOTDIR].contains(&err) {
FLOGF!(warning, FILE_ERROR, spec.target);
let mut dname: &wstr = &spec.target;
while !dname.is_empty() {
let next: &wstr = wdirname(dname);
if let Ok(md) = wstat(next) {
if !md.is_dir() {
FLOGF!(
warning,
"Path '%ls' is not a directory",
next
);
} else {
FLOGF!(warning, "Path '%ls' does not exist", dname);
match wopen_cloexec(&path, oflags, OPEN_MASK) {
Ok(raw_fd) => {
let file = AutoCloseFd::new(raw_fd);
self.push(Arc::new(IoFile::new(spec.fd, file)));
}
Err(err) => {
if (oflags & O_EXCL) != 0 && err == nix::Error::EEXIST {
FLOGF!(warning, NOCLOB_ERROR, spec.target);
} else {
if should_flog!(warning) {
let err = errno::errno().0;
// If the error is that the file doesn't exist
// or there's a non-directory component,
// find the first problematic component for a better message.
if [ENOENT, ENOTDIR].contains(&err) {
FLOGF!(warning, FILE_ERROR, spec.target);
let mut dname: &wstr = &spec.target;
while !dname.is_empty() {
let next: &wstr = wdirname(dname);
if let Ok(md) = wstat(next) {
if !md.is_dir() {
FLOGF!(
warning,
"Path '%ls' is not a directory",
next
);
} else {
FLOGF!(
warning,
"Path '%ls' does not exist",
dname
);
}
break;
}
break;
dname = next;
}
dname = next;
} else if err != EINTR {
// If we get EINTR we had a cancel signal.
// That's expected (ctrl-c on the commandline),
// so no warning.
FLOGF!(warning, FILE_ERROR, spec.target);
perror("open");
}
} else if err != EINTR {
// If we get EINTR we had a cancel signal.
// That's expected (ctrl-c on the commandline),
// so no warning.
FLOGF!(warning, FILE_ERROR, spec.target);
perror("open");
}
}
// If opening a file fails, insert a closed FD instead of the file redirection
// and return false. This lets execution potentially recover and at least gives
// the shell a chance to gracefully regain control of the shell (see #7038).
self.push(Arc::new(IoClose::new(spec.fd)));
have_error = true;
continue;
}
// If opening a file fails, insert a closed FD instead of the file redirection
// and return false. This lets execution potentially recover and at least gives
// the shell a chance to gracefully regain control of the shell (see #7038).
self.push(Arc::new(IoClose::new(spec.fd)));
have_error = true;
continue;
}
self.push(Arc::new(IoFile::new(spec.fd, file)));
}
}
}

View file

@ -357,12 +357,16 @@ impl Parser {
profile_items: RefCell::default(),
global_event_blocks: AtomicU64::new(0),
});
let cwd = open_cloexec(CStr::from_bytes_with_nul(b".\0").unwrap(), O_RDONLY, 0);
if cwd < 0 {
perror("Unable to open the current working directory");
} else {
result.libdata_mut().cwd_fd = Some(Arc::new(AutoCloseFd::new(cwd)));
match open_cloexec(CStr::from_bytes_with_nul(b".\0").unwrap(), O_RDONLY, 0) {
Ok(raw_fd) => {
result.libdata_mut().cwd_fd = Some(Arc::new(AutoCloseFd::new(raw_fd)));
}
Err(_) => {
perror("Unable to open the current working directory");
}
}
result.base.initialize(&result);
result
}

View file

@ -22,7 +22,7 @@ use std::cell::UnsafeCell;
use std::io::BufReader;
use std::num::NonZeroUsize;
use std::ops::Range;
use std::os::fd::RawFd;
use std::os::fd::{FromRawFd, RawFd};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::atomic::Ordering;
@ -4658,10 +4658,12 @@ impl ReaderData {
let mut path =
var.map_or_else(|| L!("~/.bash_history").to_owned(), |var| var.as_string());
expand_tilde(&mut path, self.vars());
let file = AutoCloseFd::new(wopen_cloexec(&path, O_RDONLY, 0));
if !file.is_valid() {
let Ok(raw_fd) = wopen_cloexec(&path, O_RDONLY, 0) else {
return;
}
};
let file = unsafe { std::fs::File::from_raw_fd(raw_fd) };
self.history.populate_from_bash(BufReader::new(file));
}
}

View file

@ -594,8 +594,8 @@ fn test_history_formats() {
"echo foo".into(),
];
let test_history_imported_from_bash = History::with_name(L!("bash_import"));
let file = AutoCloseFd::new(wopen_cloexec(L!("tests/history_sample_bash"), O_RDONLY, 0));
assert!(file.is_valid());
let file =
AutoCloseFd::new(wopen_cloexec(L!("tests/history_sample_bash"), O_RDONLY, 0).unwrap());
test_history_imported_from_bash.populate_from_bash(BufReader::new(file));
assert_eq!(test_history_imported_from_bash.get_history(), expected);
test_history_imported_from_bash.clear();