Backwards-compatibly rewrite getdents to use special syscall.

This commit is contained in:
4lDO2
2024-09-08 22:21:24 +02:00
parent 198caa3bc5
commit c0e7af5289
22 changed files with 338 additions and 231 deletions
+12 -3
View File
@@ -1,5 +1,5 @@
use crate::io::Write;
use core::{arch::asm, ptr};
use core::{arch::asm, mem::offset_of, ptr};
use super::{types::*, Pal, ERRNO};
use crate::{
@@ -12,6 +12,7 @@ use crate::header::{
sys_stat::stat,
sys_statvfs::statvfs,
sys_time::{timeval, timezone},
unistd::SEEK_SET,
};
// use header::sys_times::tms;
use crate::{
@@ -281,8 +282,16 @@ impl Pal for Sys {
}
}
fn getdents(fd: c_int, dirents: *mut dirent, bytes: usize) -> c_int {
unsafe { syscall!(GETDENTS64, fd, dirents, bytes) as c_int }
fn getdents(fd: c_int, buf: &mut [u8], _off: u64) -> Result<usize, Errno> {
e_raw(unsafe { syscall!(GETDENTS64, fd, buf.as_mut_ptr(), buf.len()) })
}
fn dir_seek(fd: c_int, off: u64) -> Result<(), Errno> {
e_raw(unsafe { syscall!(LSEEK, fd, off, SEEK_SET) })?;
Ok(())
}
unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)> {
let dent = this_dent.as_ptr().cast::<dirent>();
Some(((*dent).d_reclen, (*dent).d_off as u64))
}
fn getegid() -> gid_t {
+7 -1
View File
@@ -93,7 +93,13 @@ pub trait Pal {
fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char;
fn getdents(fd: c_int, dirents: *mut dirent, bytes: usize) -> c_int;
fn getdents(fd: c_int, buf: &mut [u8], opaque_offset: u64) -> Result<usize, Errno>;
fn dir_seek(fd: c_int, opaque_offset: u64) -> Result<(), Errno>;
// SAFETY: This_dent must satisfy platform-specific size and alignment constraints. On Linux,
// this means the buffer came from a valid getdents64 invocation, whereas on Redox, every
// possible this_dent slice is safe (and will be validated).
unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)>;
fn getegid() -> gid_t;
+74 -89
View File
@@ -1,8 +1,13 @@
use core::{convert::TryFrom, mem, ptr, slice, str};
use core::{
convert::TryFrom,
mem::{self, size_of},
ptr, slice, str,
};
use redox_rt::RtTcb;
use syscall::{
self,
data::{Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec},
dirent::{DirentHeader, DirentKind},
Error, PtraceEvent, Result, EMFILE,
};
@@ -12,7 +17,10 @@ use crate::{
fs::File,
header::{
dirent::dirent,
errno::{EBADF, EBADFD, EBADR, EINVAL, EIO, ENOENT, ENOMEM, ENOSYS, EPERM, ERANGE},
errno::{
EBADF, EBADFD, EBADR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS, EOPNOTSUPP,
EPERM, ERANGE,
},
fcntl, limits,
sys_mman::{MAP_ANONYMOUS, MAP_FAILED, PROT_READ, PROT_WRITE},
sys_random,
@@ -353,97 +361,74 @@ impl Pal for Sys {
buf
}
fn getdents(fd: c_int, mut dirents: *mut dirent, max_bytes: usize) -> c_int {
//TODO: rewrite this code. Originally the *dirents = dirent { ... } stuff below caused
// massive issues. This has been hacked around, but it still isn't perfect
fn getdents(fd: c_int, buf: &mut [u8], opaque: u64) -> Result<usize, Errno> {
//println!("GETDENTS {} into ({:p}+{})", fd, buf.as_ptr(), buf.len());
// Get initial reading position
let mut read = match syscall::lseek(fd as usize, 0, syscall::SEEK_CUR) {
Ok(pos) => pos as isize,
Err(err) => return -err.errno,
};
const HEADER_SIZE: usize = size_of::<DirentHeader>();
let mut written = 0;
let mut buf = [0; 1024];
let mut name = [0; 256];
let mut i = 0;
let mut flush = |written: &mut usize, i: &mut usize, name: &mut [c_char; 256]| {
if *i < name.len() {
// Set NUL byte
name[*i] = 0;
}
// Get size: full size - unused bytes
if *written + mem::size_of::<dirent>() > max_bytes {
// Seek back to after last read entry and return
match syscall::lseek(fd as usize, read, syscall::SEEK_SET) {
Ok(_) => return Some(*written as c_int),
Err(err) => return Some(-err.errno),
}
}
let size = mem::size_of::<dirent>() - name.len().saturating_sub(*i + 1);
unsafe {
//This is the offending code mentioned above
*dirents = dirent {
d_ino: 0,
d_off: read as off_t,
d_reclen: size as c_ushort,
d_type: 0,
d_name: *name,
};
dirents = (dirents as *mut u8).offset(size as isize) as *mut dirent;
}
read += *i as isize + /* newline */ 1;
*written += size;
*i = 0;
None
};
loop {
// Read a chunk from the directory
let len = match syscall::read(fd as usize, &mut buf) {
Ok(0) => {
if i > 0 {
if let Some(value) = flush(&mut written, &mut i, &mut name) {
return value;
}
}
return written as c_int;
}
Ok(n) => n,
Err(err) => return -err.errno,
};
// Handle everything
let mut start = 0;
while start < len {
let buf = &buf[start..len];
// Copy everything up until a newline
let newline = buf.iter().position(|&c| c == b'\n');
let pre_len = newline.unwrap_or(buf.len());
let post_len = newline.map(|i| i + 1).unwrap_or(buf.len());
if i < pre_len {
// Reserve space for NUL byte
let name_len = name.len() - 1;
let name = &mut name[i..name_len];
let copy = pre_len.min(name.len());
let buf = unsafe { slice::from_raw_parts(buf.as_ptr() as *const c_char, copy) };
name[..copy].copy_from_slice(buf);
}
i += pre_len;
start += post_len;
// Write the directory entry
if newline.is_some() {
if let Some(value) = flush(&mut written, &mut i, &mut name) {
return value;
}
}
// Use syscall if it exists.
match unsafe {
syscall::syscall5(
syscall::SYS_GETDENTS,
fd as usize,
buf.as_mut_ptr() as usize,
buf.len(),
HEADER_SIZE,
opaque as usize,
)
} {
Err(Error {
errno: EOPNOTSUPP | ENOSYS,
}) => (),
other => {
//println!("REAL GETDENTS {:?}", other);
return Ok(other?);
}
}
// Otherwise, for legacy schemes, assume the buffer is pre-arranged (all schemes do this in
// practice), and just read the name. If multiple names appear, pretend it didn't happen
// and just use the first entry.
let (header, name) = buf.split_at_mut(size_of::<DirentHeader>());
let bytes_read = Sys::pread(fd, name, opaque as i64)? as usize;
if bytes_read == 0 {
return Ok(0);
}
let (name_len, advance) = match name[..bytes_read].iter().position(|c| *c == b'\n') {
Some(idx) => (idx, idx + 1),
// Insufficient space for NUL byte, or entire entry was not read. Indicate we need a
// larger buffer.
None if bytes_read == name.len() => return Err(Errno(EINVAL)),
None => (bytes_read, name.len()),
};
name[name_len] = b'\0';
let record_len = u16::try_from(size_of::<DirentHeader>() + name_len + 1)
.map_err(|_| Error::new(ENAMETOOLONG))?;
header.copy_from_slice(&DirentHeader {
inode: 0,
next_opaque_id: opaque + advance as u64,
record_len,
kind: DirentKind::Unspecified as u8,
});
//println!("EMULATED GETDENTS");
Ok(record_len.into())
}
fn dir_seek(_fd: c_int, _off: u64) -> Result<(), Errno> {
// Redox getdents takes an explicit (opaque) offset, so this is a no-op.
Ok(())
}
// NOTE: fn is unsafe, but this just means we can assume more things. impl is safe
unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)> {
let mut header = DirentHeader::default();
header.copy_from_slice(&this_dent.get(..size_of::<DirentHeader>())?);
Some((header.record_len, header.next_opaque_id))
}
fn getegid() -> gid_t {