Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8b87324

Browse files
committed
work around linux not honoring write_at for O_APPEND files
requires a kernel >= 6.9
1 parent 7ba34c7 commit 8b87324

File tree

4 files changed

+102
-5
lines changed

4 files changed

+102
-5
lines changed

‎library/std/src/fs/tests.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,23 @@ fn file_test_io_read_write_at() {
490490
check!(fs::remove_file(&filename));
491491
}
492492

493+
#[test]
494+
#[cfg(unix)]
495+
fn file_test_append_write_at() {
496+
use crate::os::unix::fs::FileExt;
497+
498+
let tmpdir = tmpdir();
499+
let filename = tmpdir.join("file_test_append_write_at.txt");
500+
let msg = b"it's not working!";
501+
check!(fs::write(&filename, &msg));
502+
// write_at should work even in in append mode
503+
let f = check!(fs::File::options().append(true).open(&filename));
504+
assert_eq!(check!(f.write_at(b" ", 5)), 3);
505+
506+
let content = check!(fs::read(&filename));
507+
assert_eq!(&content, b"it's working!");
508+
}
509+
493510
#[test]
494511
#[cfg(unix)]
495512
fn set_get_unix_permissions() {

‎library/std/src/os/unix/fs.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ pub trait FileExt {
147147
///
148148
/// # Bug
149149
/// On some systems, `write_at` utilises [`pwrite64`] to write to files.
150-
/// However, this syscall has a [bug] where files opened with the `O_APPEND`
150+
/// However, on linux this syscall has a [bug] where files opened with the `O_APPEND`
151151
/// flag fail to respect the offset parameter, always appending to the end
152-
/// of the file instead.
152+
/// of the file instead. When available `pwritev2(..., RWF_NOAPPEND)` will
153+
/// be used instead to avoid this bug.
153154
///
154155
/// It is possible to inadvertently set this flag, like in the example below.
155156
/// Therefore, it is important to be vigilant while changing options to mitigate

‎library/std/src/os/unix/fs/tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ fn write_vectored_at() {
3939
file.write_all(msg).unwrap();
4040
}
4141
let expected = {
42-
let file = fs::File::options().write(true).open(&filename).unwrap();
42+
// Open in append mode to test that positioned writes bypass O_APPEND.
43+
let file = fs::File::options().append(true).open(&filename).unwrap();
4344
let buf0 = b" ";
4445
let buf1 = b"great ";
4546

‎library/std/src/sys/fd/unix.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ use crate::cmp;
2222
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read};
2323
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
2424
use crate::sys::cvt;
25-
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
25+
#[cfg(all(any(target_os = "android", target_os = "linux"), target_pointer_width = "64"))]
2626
use crate::sys::pal::weak::syscall;
27-
#[cfg(any(all(target_os = "android", target_pointer_width = "32"), target_vendor = "apple"))]
27+
#[cfg(any(
28+
all(any(target_os = "android", target_os = "linux"), target_pointer_width = "32"),
29+
target_vendor = "apple"
30+
))]
2831
use crate::sys::pal::weak::weak;
2932
use crate::sys_common::{AsInner, FromInner, IntoInner};
3033

@@ -384,6 +387,16 @@ impl FileDesc {
384387
))]
385388
use libc::pwrite64;
386389

390+
// Work around linux deviating from POSIX where it ignores the
391+
// offset of pwrite when the file was opened with O_APPEND.
392+
#[cfg(any(target_os = "linux", target_os = "android"))]
393+
{
394+
let iov = [IoSlice::new(buf)];
395+
if let Some(ret) = self.pwritev2(&iov, offset) {
396+
return ret;
397+
}
398+
}
399+
387400
unsafe {
388401
cvt(pwrite64(
389402
self.as_raw_fd(),
@@ -408,6 +421,13 @@ impl FileDesc {
408421
target_os = "openbsd", // OpenBSD 2.7
409422
))]
410423
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
424+
// Work around linux deviating from POSIX where it ignores the
425+
// offset of pwrite when the file was opened with O_APPEND.
426+
#[cfg(any(target_os = "linux", target_os = "android"))]
427+
if let Some(ret) = self.pwritev2(bufs, offset) {
428+
return ret;
429+
}
430+
411431
let ret = cvt(unsafe {
412432
libc::pwritev(
413433
self.as_raw_fd(),
@@ -611,6 +631,64 @@ impl FileDesc {
611631
pub fn duplicate(&self) -> io::Result<FileDesc> {
612632
Ok(Self(self.0.try_clone()?))
613633
}
634+
635+
#[cfg(any(target_os = "linux", target_os = "android"))]
636+
fn pwritev2(&self, bufs: &[IoSlice<'_>], offset: u64) -> Option<io::Result<usize>> {
637+
#[cfg(target_pointer_width = "64")]
638+
syscall!(
639+
fn pwritev2(
640+
fd: libc::c_int,
641+
iovec: *const libc::iovec,
642+
n_iovec: libc::c_int,
643+
offset: off64_t,
644+
flags: libc::c_int,
645+
) -> isize;
646+
);
647+
#[cfg(target_pointer_width = "32")]
648+
let pwritev2 = {
649+
weak!(
650+
fn pwritev2(
651+
fd: libc::c_int,
652+
iovec: *const libc::iovec,
653+
n_iovec: libc::c_int,
654+
offset: off64_t,
655+
flags: libc::c_int,
656+
) -> isize;
657+
);
658+
let Some(pwritev2) = pwritev2.get() else {
659+
return None;
660+
};
661+
pwritev2
662+
};
663+
664+
use core::sync::atomic::AtomicBool;
665+
666+
static NOAPPEND_SUPPORTED: AtomicBool = AtomicBool::new(true);
667+
if NOAPPEND_SUPPORTED.load(core::sync::atomic::Ordering::Relaxed) {
668+
let r = unsafe {
669+
cvt(pwritev2(
670+
self.as_raw_fd(),
671+
bufs.as_ptr() as *const libc::iovec,
672+
cmp::min(bufs.len(), max_iov()) as libc::c_int,
673+
offset as off64_t,
674+
libc::RWF_NOAPPEND,
675+
))
676+
};
677+
match r {
678+
Ok(ret) => return Some(Ok(ret as usize)),
679+
Err(e)
680+
if let Some(err) = e.raw_os_error()
681+
&& (err == libc::EOPNOTSUPP || err == libc::ENOSYS) =>
682+
{
683+
NOAPPEND_SUPPORTED.store(false, core::sync::atomic::Ordering::Relaxed);
684+
return None;
685+
}
686+
Err(e) => return Some(Err(e)),
687+
}
688+
}
689+
690+
return None;
691+
}
614692
}
615693

616694
impl<'a> Read for &'a FileDesc {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /