Fix some behavior about `seek()`
This commit is contained in:
parent
24502ac3d4
commit
21365dd0bd
|
|
@ -123,7 +123,22 @@ impl HandleInner {
|
|||
}
|
||||
|
||||
pub(self) fn seek(&self, pos: SeekFrom) -> Result<usize> {
|
||||
do_seek_util(self.path.inode().as_ref(), &self.offset, pos)
|
||||
if let Some(ref file_io) = self.file_io {
|
||||
file_io.check_seekable()?;
|
||||
if file_io.is_offset_aware() {
|
||||
// TODO: Figure out whether we need to add support for seeking from the end of
|
||||
// special files.
|
||||
return do_seek_util(&self.offset, pos, None);
|
||||
} else {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
let inode = self.path.inode();
|
||||
if !inode.type_().is_seekable() {
|
||||
return_errno_with_message!(Errno::ESPIPE, "seek is not supported");
|
||||
}
|
||||
do_seek_util(&self.offset, pos, inode.seek_end())
|
||||
}
|
||||
|
||||
pub(self) fn offset(&self) -> usize {
|
||||
|
|
@ -292,6 +307,10 @@ pub trait FileIo: Pollable + InodeIo + Send + Sync + 'static {
|
|||
fn check_seekable(&self) -> Result<()>;
|
||||
|
||||
/// Returns whether the `read()`/`write()` operation should use and advance the offset.
|
||||
///
|
||||
/// If [`is_seekable`] succeeds but this method returns `false`, the offset in the `seek()`
|
||||
/// operation will be ignored. In that case, the `seek()` operation will do nothing but
|
||||
/// succeed.
|
||||
fn is_offset_aware(&self) -> bool;
|
||||
|
||||
// See `FileLike::mappable`.
|
||||
|
|
@ -305,34 +324,33 @@ pub trait FileIo: Pollable + InodeIo + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
pub(super) fn do_seek_util(
|
||||
inode: &dyn Inode,
|
||||
offset: &Mutex<usize>,
|
||||
pos: SeekFrom,
|
||||
end: Option<usize>,
|
||||
) -> Result<usize> {
|
||||
let mut offset = offset.lock();
|
||||
let new_offset: isize = match pos {
|
||||
SeekFrom::Start(off /* as usize */) => {
|
||||
if off > isize::MAX as usize {
|
||||
return_errno_with_message!(Errno::EINVAL, "file offset is too large");
|
||||
|
||||
let new_offset = match pos {
|
||||
SeekFrom::Start(off) => off,
|
||||
SeekFrom::End(diff) => {
|
||||
if let Some(end) = end {
|
||||
end.wrapping_add_signed(diff)
|
||||
} else {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"seeking the file from the end is not supported"
|
||||
);
|
||||
}
|
||||
off as isize
|
||||
}
|
||||
SeekFrom::End(off /* as isize */) => {
|
||||
let file_size = inode.size() as isize;
|
||||
assert!(file_size >= 0);
|
||||
file_size
|
||||
.checked_add(off)
|
||||
.ok_or_else(|| Error::with_message(Errno::EOVERFLOW, "file offset overflow"))?
|
||||
}
|
||||
SeekFrom::Current(off /* as isize */) => (*offset as isize)
|
||||
.checked_add(off)
|
||||
.ok_or_else(|| Error::with_message(Errno::EOVERFLOW, "file offset overflow"))?,
|
||||
SeekFrom::Current(diff) => offset.wrapping_add_signed(diff),
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return_errno_with_message!(Errno::EINVAL, "file offset must not be negative");
|
||||
|
||||
// Invariant: `*offset <= isize::MAX as usize`.
|
||||
// TODO: Investigate whether `read`/`write` can break this invariant.
|
||||
if new_offset.cast_signed() < 0 {
|
||||
return_errno_with_message!(Errno::EINVAL, "the file offset cannot be negative");
|
||||
}
|
||||
// Invariant: 0 <= new_offset <= isize::MAX
|
||||
let new_offset = new_offset as usize;
|
||||
|
||||
*offset = new_offset;
|
||||
Ok(new_offset)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,11 @@ impl<D: DirOps + 'static> Inode for ProcDir<D> {
|
|||
fn is_dentry_cacheable(&self) -> bool {
|
||||
!self.common.is_volatile()
|
||||
}
|
||||
|
||||
fn seek_end(&self) -> Option<usize> {
|
||||
// Seeking directories under `/proc` with `SEEK_END` will start from zero.
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DirOps: Sync + Send + Sized {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
|
|||
fn fs(&self) -> Arc<dyn FileSystem>;
|
||||
|
||||
fn resize(&self, _new_size: usize) -> Result<()> {
|
||||
// Resizing files under `/proc` will succeed, but will do nothing.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +97,11 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
|
|||
fn is_dentry_cacheable(&self) -> bool {
|
||||
!self.common.is_volatile()
|
||||
}
|
||||
|
||||
fn seek_end(&self) -> Option<usize> {
|
||||
// Seeking regular files under `/proc` with `SEEK_END` will fail.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FileOps: Sync + Send {
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ impl FileLike for MemfdFile {
|
|||
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
|
||||
}
|
||||
|
||||
do_seek_util(self.memfd_inode.as_ref(), &self.offset, pos)
|
||||
do_seek_util(&self.offset, pos, Some(self.memfd_inode.size()))
|
||||
}
|
||||
|
||||
fn fallocate(&self, mode: FallocMode, offset: usize, len: usize) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -389,7 +389,23 @@ pub trait Inode: Any + InodeIo + Send + Sync {
|
|||
true
|
||||
}
|
||||
|
||||
/// Get the extension of this inode
|
||||
/// Returns the end position for [`SeekFrom::End`].
|
||||
///
|
||||
/// [`SeekFrom::End`]: super::SeekFrom::End
|
||||
fn seek_end(&self) -> Option<usize> {
|
||||
if self.type_() == InodeType::File {
|
||||
Some(self.size())
|
||||
} else {
|
||||
// This depends on the file system. For example, seeking directories from the end
|
||||
// succeeds under procfs and btrfs but fails under tmpfs. Here, we just choose a
|
||||
// safe default to reject it.
|
||||
// TODO: Carefully check the Linux behavior of each file system and adjust ours
|
||||
// accordingly.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the extension of this inode.
|
||||
fn extension(&self) -> Option<&Extension> {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,20 +12,25 @@ use crate::{
|
|||
pub fn sys_lseek(fd: FileDesc, offset: isize, whence: u32, ctx: &Context) -> Result<SyscallReturn> {
|
||||
debug!("fd = {}, offset = {}, whence = {}", fd, offset, whence);
|
||||
|
||||
let seek_from = match whence {
|
||||
0 => {
|
||||
if offset < 0 {
|
||||
return_errno!(Errno::EINVAL);
|
||||
}
|
||||
SeekFrom::Start(offset as usize)
|
||||
}
|
||||
1 => SeekFrom::Current(offset),
|
||||
2 => SeekFrom::End(offset),
|
||||
_ => return_errno!(Errno::EINVAL),
|
||||
let seek_from = match SeekType::try_from(whence)? {
|
||||
SeekType::SEEK_SET => SeekFrom::Start(offset.cast_unsigned()),
|
||||
SeekType::SEEK_CUR => SeekFrom::Current(offset),
|
||||
SeekType::SEEK_END => SeekFrom::End(offset),
|
||||
};
|
||||
|
||||
let mut file_table = ctx.thread_local.borrow_file_table_mut();
|
||||
let file = get_file_fast!(&mut file_table, fd);
|
||||
|
||||
let offset = file.seek(seek_from)?;
|
||||
Ok(SyscallReturn::Return(offset as _))
|
||||
}
|
||||
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17.7/source/include/uapi/linux/fs.h#L52>
|
||||
#[derive(Clone, Copy, Debug, TryFromInt)]
|
||||
#[repr(u32)]
|
||||
#[expect(non_camel_case_types)]
|
||||
enum SeekType {
|
||||
SEEK_SET = 0,
|
||||
SEEK_CUR = 1,
|
||||
SEEK_END = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ FN_TEST(readable)
|
|||
TEST_SUCC(read(fd, buf, sizeof(buf)));
|
||||
TEST_ERRNO(write(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_SUCC(lseek(fd, 0, SEEK_SET));
|
||||
TEST_SUCC(lseek(fd, 0, SEEK_END));
|
||||
TEST_ERRNO(ioctl(fd, TCGETS), ENOTTY);
|
||||
TEST_ERRNO(ftruncate(fd, 1), EINVAL);
|
||||
TEST_ERRNO(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1), EBADF);
|
||||
|
|
@ -86,6 +87,7 @@ FN_TEST(readable)
|
|||
TEST_ERRNO(read(fd, buf, sizeof(buf)), EISDIR);
|
||||
TEST_ERRNO(write(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_SUCC(lseek(fd, 0, SEEK_SET));
|
||||
TEST_ERRNO(lseek(fd, 0, SEEK_END), EINVAL);
|
||||
TEST_ERRNO(ioctl(fd, TCGETS), ENOTTY);
|
||||
TEST_ERRNO(ftruncate(fd, 1), EINVAL);
|
||||
TEST_ERRNO(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1), EBADF);
|
||||
|
|
@ -126,6 +128,7 @@ FN_TEST(writeable)
|
|||
TEST_ERRNO(read(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_SUCC(write(fd, buf, sizeof(buf)));
|
||||
TEST_SUCC(lseek(fd, 0, SEEK_SET));
|
||||
TEST_SUCC(lseek(fd, 0, SEEK_END));
|
||||
TEST_ERRNO(ioctl(fd, TCGETS), ENOTTY);
|
||||
TEST_SUCC(ftruncate(fd, 1));
|
||||
TEST_SUCC(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1));
|
||||
|
|
@ -171,6 +174,7 @@ FN_TEST(path)
|
|||
TEST_ERRNO(read(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_ERRNO(write(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_ERRNO(lseek(fd, 0, SEEK_SET), EBADF);
|
||||
TEST_ERRNO(lseek(fd, 0, SEEK_END), EBADF);
|
||||
TEST_ERRNO(ioctl(fd, TCGETS), EBADF);
|
||||
TEST_ERRNO(ftruncate(fd, 1), EBADF);
|
||||
TEST_ERRNO(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1), EBADF);
|
||||
|
|
@ -203,6 +207,7 @@ FN_TEST(path)
|
|||
TEST_ERRNO(read(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_ERRNO(write(fd, buf, sizeof(buf)), EBADF);
|
||||
TEST_ERRNO(lseek(fd, 0, SEEK_SET), EBADF);
|
||||
TEST_ERRNO(lseek(fd, 0, SEEK_END), EBADF);
|
||||
TEST_ERRNO(ioctl(fd, TCGETS), EBADF);
|
||||
TEST_ERRNO(ftruncate(fd, 1), EBADF);
|
||||
TEST_ERRNO(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1), EBADF);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ TESTS ?= \
|
|||
access_test \
|
||||
chown_test \
|
||||
creat_test \
|
||||
dev_test \
|
||||
dup_test \
|
||||
epoll_test \
|
||||
eventfd_test \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# TODO: Support `/dev/fuse`
|
||||
DevTest.ReadDevFuseWithoutMount
|
||||
|
|
@ -1,6 +1,2 @@
|
|||
LseekTest.Overflow
|
||||
LseekTest.ProcFile
|
||||
# TODO: Add `/sys/devices` and support `lseek(SEEK_END)`.
|
||||
LseekTest.SysDir
|
||||
LseekTest.SeekCurrentDir
|
||||
LseekTest.ProcStatTwice
|
||||
LseekTest.EtcPasswdDup
|
||||
Loading…
Reference in New Issue