use alloc::{
boxed::Box,
sync::{Arc, Weak},
vec::Vec,
};
#[cfg(feature = "times")]
use core::sync::atomic::{AtomicU8, Ordering};
use core::{num::NonZeroUsize, ops::Range, task::Context};
use ax_alloc::{UsageKind, global_allocator};
use ax_hal::mem::{PhysAddr, VirtAddr, virt_to_phys};
use ax_io::{SeekFrom, prelude::*};
use ax_sync::Mutex;
use axfs_ng_vfs::{
FileNode, Location, NodeFlags, NodePermission, NodeType, VfsError, VfsResult, path::Path,
};
use axpoll::{IoEvents, Pollable};
use intrusive_collections::{LinkedList, LinkedListAtomicLink, intrusive_adapter};
use lru::LruCache;
use spin::RwLock;
use super::FsContext;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct FileFlags: u8 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
const APPEND = 8;
const PATH = 16;
}
}
pub enum OpenResult {
File(File),
Dir(Location),
}
impl OpenResult {
pub fn into_file(self) -> VfsResult<File> {
match self {
Self::File(file) => Ok(file),
Self::Dir(_) => Err(VfsError::IsADirectory),
}
}
pub fn into_dir(self) -> VfsResult<Location> {
match self {
Self::Dir(dir) => Ok(dir),
Self::File(_) => Err(VfsError::NotADirectory),
}
}
pub fn into_location(self) -> Location {
match self {
Self::File(file) => file.location().clone(),
Self::Dir(dir) => dir,
}
}
}
#[derive(Debug, Clone)]
pub struct OpenOptions {
read: bool,
write: bool,
append: bool,
truncate: bool,
create: bool,
create_new: bool,
directory: bool,
no_follow: bool,
direct: bool,
user: Option<(u32, u32)>,
path: bool,
node_type: NodeType,
mode: u32,
}
impl OpenOptions {
pub fn new() -> Self {
Self {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
directory: false,
no_follow: false,
direct: false,
user: None,
path: false,
node_type: NodeType::RegularFile,
mode: 0o666,
}
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.read = read;
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
self.write = write;
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.truncate = truncate;
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
self.create_new = create_new;
self
}
pub fn directory(&mut self, directory: bool) -> &mut Self {
self.directory = directory;
self
}
pub fn no_follow(&mut self, no_follow: bool) -> &mut Self {
self.no_follow = no_follow;
self
}
pub fn direct(&mut self, direct: bool) -> &mut Self {
self.direct = direct;
self
}
pub fn user(&mut self, uid: u32, gid: u32) -> &mut Self {
self.user = Some((uid, gid));
self
}
pub fn path(&mut self, path: bool) -> &mut Self {
self.path = path;
self
}
pub fn node_type(&mut self, node_type: NodeType) -> &mut Self {
self.node_type = node_type;
self
}
pub fn mode(&mut self, mode: u32) -> &mut Self {
self.mode = mode;
self
}
fn _open(&self, loc: Location) -> VfsResult<OpenResult> {
let flags = self.to_flags()?;
if self.directory {
if flags.contains(FileFlags::WRITE) {
return Err(VfsError::IsADirectory);
}
loc.check_is_dir()?;
}
if self.truncate {
loc.entry().as_file()?.set_len(0)?;
}
Ok(if loc.is_dir() {
OpenResult::Dir(loc)
} else {
let non_cacheable_type = matches!(
loc.metadata()?.node_type,
NodeType::CharacterDevice | NodeType::Fifo | NodeType::Socket
);
let direct = non_cacheable_type
|| self.path
|| self.direct
|| loc.flags().contains(NodeFlags::NON_CACHEABLE);
let backend = if !direct || loc.flags().contains(NodeFlags::ALWAYS_CACHE) {
FileBackend::new_cached(loc)
} else {
FileBackend::new_direct(loc)
};
OpenResult::File(File::new(backend, flags))
})
}
pub fn open_loc(&self, loc: Location) -> VfsResult<OpenResult> {
if !self.is_valid() {
return Err(VfsError::InvalidInput);
}
self._open(loc)
}
pub fn open(&self, context: &FsContext, path: impl AsRef<Path>) -> VfsResult<OpenResult> {
if !self.is_valid() {
return Err(VfsError::InvalidInput);
}
let loc = match context.resolve_parent(path.as_ref()) {
Ok((parent, name)) => {
let mut loc = parent.open_file(
&name,
&axfs_ng_vfs::OpenOptions {
create: self.create,
create_new: self.create_new,
node_type: self.node_type,
permission: NodePermission::from_bits_truncate(self.mode as _),
user: self.user,
},
)?;
if !self.no_follow {
loc = context
.with_current_dir(parent)?
.try_resolve_symlink(loc, &mut 0)?;
}
loc
}
Err(VfsError::InvalidInput) => {
context.root_dir().clone()
}
Err(err) => return Err(err),
};
self._open(loc)
}
pub(crate) fn to_flags(&self) -> VfsResult<FileFlags> {
Ok(match (self.read, self.write, self.append) {
(true, false, false) => FileFlags::READ,
(false, true, false) => FileFlags::WRITE,
(true, true, false) => FileFlags::READ | FileFlags::WRITE,
(false, _, true) => FileFlags::WRITE | FileFlags::APPEND,
(true, _, true) => FileFlags::READ | FileFlags::WRITE | FileFlags::APPEND,
(false, false, false) => return Err(VfsError::InvalidInput),
} | if self.path {
FileFlags::PATH
} else {
FileFlags::empty()
})
}
pub(crate) fn is_valid(&self) -> bool {
if !self.read && !self.write && !self.append {
return true;
}
match (self.write, self.append) {
(true, false) => {}
(false, false) => {
if self.truncate || self.create || self.create_new {
return false;
}
}
(_, true) => {
if self.truncate && !self.create_new {
return false;
}
}
}
true
}
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
const PAGE_SIZE: usize = 4096;
#[derive(Debug)]
pub struct PageCache {
addr: VirtAddr,
dirty: bool,
}
impl PageCache {
fn new() -> VfsResult<Self> {
let addr = global_allocator()
.alloc_pages(1, PAGE_SIZE, UsageKind::PageCache)
.map_err(|err| {
warn!("Failed to allocate page cache: {:?}", err);
VfsError::NoMemory
})?;
Ok(Self {
addr: addr.into(),
dirty: false,
})
}
pub fn paddr(&self) -> PhysAddr {
virt_to_phys(self.addr)
}
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
pub fn data(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.addr.as_mut_ptr(), PAGE_SIZE) }
}
}
impl Drop for PageCache {
fn drop(&mut self) {
if self.dirty {
warn!("dirty page dropped without flushing");
}
global_allocator().dealloc_pages(self.addr.as_usize(), 1, UsageKind::PageCache);
}
}
type EvictListenerFn = Box<dyn Fn(u32, &PageCache) + Send + Sync>;
struct EvictListener {
listener: EvictListenerFn,
link: LinkedListAtomicLink,
}
intrusive_adapter!(EvictListenerAdapter = Box<EvictListener>: EvictListener { link: LinkedListAtomicLink });
struct CachedFileShared {
page_cache: Mutex<LruCache<u32, PageCache>>,
evict_listeners: Mutex<LinkedList<EvictListenerAdapter>>,
}
impl CachedFileShared {
pub fn new() -> Self {
Self {
page_cache: Mutex::new(LruCache::new(NonZeroUsize::new(64).unwrap())),
evict_listeners: Mutex::new(LinkedList::default()),
}
}
pub fn new_unbounded() -> Self {
Self {
page_cache: Mutex::new(LruCache::unbounded()),
evict_listeners: Mutex::new(LinkedList::default()),
}
}
}
pub struct CachedFile {
inner: Location,
shared: Arc<CachedFileShared>,
in_memory: bool,
append_lock: RwLock<()>,
}
impl Clone for CachedFile {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
shared: self.shared.clone(),
in_memory: self.in_memory,
append_lock: RwLock::new(()),
}
}
}
enum FileUserData {
Weak(Weak<CachedFileShared>),
Strong(Arc<CachedFileShared>),
}
impl FileUserData {
pub fn get(&self) -> Option<Arc<CachedFileShared>> {
match self {
FileUserData::Weak(weak) => weak.upgrade(),
FileUserData::Strong(strong) => Some(strong.clone()),
}
}
}
impl CachedFile {
pub fn get_or_create(location: Location) -> Self {
let in_memory = location.filesystem().name() == "tmpfs";
let mut guard = location.user_data();
let shared = if let Some(shared) = guard.get::<FileUserData>().and_then(|it| it.get()) {
shared
} else {
let (shared, user_data) = if in_memory {
let shared = Arc::new(CachedFileShared::new_unbounded());
(shared.clone(), FileUserData::Strong(shared))
} else {
let shared = Arc::new(CachedFileShared::new());
let user_data = FileUserData::Weak(Arc::downgrade(&shared));
(shared, user_data)
};
guard.insert(user_data);
shared
};
drop(guard);
Self {
inner: location,
shared,
in_memory,
append_lock: RwLock::new(()),
}
}
pub fn ptr_eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.shared, &other.shared)
}
pub fn in_memory(&self) -> bool {
self.in_memory
}
pub fn add_evict_listener<F>(&self, listener: F) -> usize
where
F: Fn(u32, &PageCache) + Send + Sync + 'static,
{
let pointer = Box::new(EvictListener {
listener: Box::new(listener),
link: LinkedListAtomicLink::new(),
});
let handle = pointer.as_ref() as *const EvictListener as usize;
self.shared.evict_listeners.lock().push_back(pointer);
handle
}
pub unsafe fn remove_evict_listener(&self, handle: usize) {
let mut guard = self.shared.evict_listeners.lock();
let mut cursor = unsafe { guard.cursor_mut_from_ptr(handle as *const EvictListener) };
cursor.remove();
}
fn evict_cache(&self, file: &FileNode, pn: u32, page: &mut PageCache) -> VfsResult<()> {
for listener in self.shared.evict_listeners.lock().iter() {
(listener.listener)(pn, page);
}
if page.dirty {
let page_start = pn as u64 * PAGE_SIZE as u64;
let len = (file.len()?.saturating_sub(page_start)).min(PAGE_SIZE as u64) as usize;
if len > 0 {
file.write_at(&page.data()[..len], page_start)?;
}
page.dirty = false;
}
Ok(())
}
fn page_or_insert<'a>(
&self,
file: &FileNode,
cache: &'a mut LruCache<u32, PageCache>,
pn: u32,
) -> VfsResult<(&'a mut PageCache, Option<(u32, PageCache)>)> {
if cache.contains(&pn) {
return Ok((cache.get_mut(&pn).unwrap(), None));
}
let mut evicted = None;
if cache.len() == cache.cap().get() {
if let Some((pn, mut page)) = cache.pop_lru() {
self.evict_cache(file, pn, &mut page)?;
evicted = Some((pn, page));
}
}
let mut page = PageCache::new()?;
if self.in_memory {
page.data().fill(0);
} else {
file.read_at(page.data(), pn as u64 * PAGE_SIZE as u64)?;
}
cache.put(pn, page);
Ok((cache.get_mut(&pn).unwrap(), evicted))
}
pub fn with_page<R>(&self, pn: u32, f: impl FnOnce(Option<&mut PageCache>) -> R) -> R {
f(self.shared.page_cache.lock().get_mut(&pn))
}
pub fn with_page_or_insert<R>(
&self,
pn: u32,
f: impl FnOnce(&mut PageCache, Option<(u32, PageCache)>) -> VfsResult<R>,
) -> VfsResult<R> {
let mut guard = self.shared.page_cache.lock();
let (page, evicted) = self.page_or_insert(self.inner.entry().as_file()?, &mut guard, pn)?;
f(page, evicted)
}
fn with_pages<T>(
&self,
range: Range<u64>,
page_initial: impl FnOnce(&FileNode) -> VfsResult<T>,
mut page_each: impl FnMut(T, &mut PageCache, Range<usize>) -> VfsResult<T>,
) -> VfsResult<T> {
let file = self.inner.entry().as_file()?;
let mut initial = page_initial(file)?;
let start_page = (range.start / PAGE_SIZE as u64) as u32;
let end_page = range.end.div_ceil(PAGE_SIZE as u64) as u32;
let mut page_offset = (range.start % PAGE_SIZE as u64) as usize;
for pn in start_page..end_page {
let page_start = pn as u64 * PAGE_SIZE as u64;
let mut guard = self.shared.page_cache.lock();
let page = self.page_or_insert(file, &mut guard, pn)?.0;
initial = page_each(
initial,
page,
page_offset..(range.end - page_start).min(PAGE_SIZE as u64) as usize,
)?;
page_offset = 0;
}
Ok(initial)
}
pub fn read_at(&self, mut dst: impl Write + IoBufMut, offset: u64) -> VfsResult<usize> {
let len = self.inner.len()?;
let end = (offset + dst.remaining_mut() as u64).min(len);
if end <= offset {
return Ok(0);
}
self.with_pages(
offset..end,
|_| Ok(0),
|read, page, range| {
let len = range.end - range.start;
dst.write(&page.data()[range.start..range.end])?;
Ok(read + len)
},
)
}
fn write_at_locked(&self, mut buf: impl Read + IoBuf, offset: u64) -> VfsResult<usize> {
let end = offset + buf.remaining() as u64;
self.with_pages(
offset..end,
|file| {
if end > file.len()? {
file.set_len(end)?;
}
Ok(0)
},
|written, page, range| {
let len = range.end - range.start;
buf.read(&mut page.data()[range.start..range.end])?;
if !self.in_memory {
page.dirty = true;
}
Ok(written + len)
},
)
}
pub fn write_at(&self, buf: impl Read + IoBuf, offset: u64) -> VfsResult<usize> {
let _guard = self.append_lock.read();
self.write_at_locked(buf, offset)
}
pub fn append(&self, buf: impl Read + IoBuf) -> VfsResult<(usize, u64)> {
let _guard = self.append_lock.write();
let file = self.inner.entry().as_file()?;
let len = file.len()?;
self.write_at_locked(buf, len)
.map(|written| (written, len + written as u64))
}
pub fn set_len(&self, len: u64) -> VfsResult<()> {
let file = self.inner.entry().as_file()?;
let old_len = file.len()?;
file.set_len(len)?;
let old_last_page = (old_len / PAGE_SIZE as u64) as u32;
let new_last_page = (len / PAGE_SIZE as u64) as u32;
if old_len < len {
let mut guard = self.shared.page_cache.lock();
if let Some(page) = guard.get_mut(&old_last_page) {
let page_start = old_last_page as u64 * PAGE_SIZE as u64;
let old_page_offset = (old_len - page_start) as usize;
let new_page_offset = (len - page_start).min(PAGE_SIZE as u64) as usize;
page.data()[old_page_offset..new_page_offset].fill(0);
}
} else if old_last_page > new_last_page {
let mut guard = self.shared.page_cache.lock();
let keys = guard
.iter()
.map(|(k, _)| *k)
.filter(|it| *it > new_last_page)
.collect::<Vec<_>>();
for pn in keys {
if let Some(mut page) = guard.pop(&pn)
&& !self.in_memory
{
page.dirty = false;
self.evict_cache(file, pn, &mut page)?;
}
}
}
Ok(())
}
pub fn sync(&self, data_only: bool) -> VfsResult<()> {
if self.in_memory {
return Ok(());
}
let file = self.inner.entry().as_file()?;
let mut guard = self.shared.page_cache.lock();
while let Some((pn, mut page)) = guard.pop_lru() {
self.evict_cache(file, pn, &mut page)?;
}
file.sync(data_only)?;
Ok(())
}
pub fn location(&self) -> &Location {
&self.inner
}
}
impl Drop for CachedFile {
fn drop(&mut self) {
if Arc::strong_count(&self.shared) > 1 {
return;
}
if let Err(err) = self.sync(false) {
warn!("Failed to sync file on drop: {err:?}");
}
}
}
#[derive(Clone)]
pub enum FileBackend {
Cached(CachedFile),
Direct(Location),
}
impl FileBackend {
pub(crate) fn new_direct(location: Location) -> Self {
Self::Direct(location)
}
pub(crate) fn new_cached(location: Location) -> Self {
Self::Cached(CachedFile::get_or_create(location))
}
pub fn read_at(&self, mut dst: impl Write + IoBufMut, mut offset: u64) -> VfsResult<usize> {
match self {
Self::Cached(cached) => cached.read_at(dst, offset),
Self::Direct(loc) => dst.read_from(&mut ax_io::read_fn(|buf| {
loc.entry().as_file()?.read_at(buf, offset).inspect(|read| {
offset += *read as u64;
})
})),
}
}
pub fn write_at(&self, mut src: impl Read + IoBuf, mut offset: u64) -> VfsResult<usize> {
match self {
Self::Cached(cached) => cached.write_at(src, offset),
Self::Direct(loc) => src.write_to(&mut ax_io::write_fn(|buf| {
loc.entry()
.as_file()?
.write_at(buf, offset)
.inspect(|written| {
offset += *written as u64;
})
})),
}
}
pub fn append(&self, mut src: impl Read + IoBuf) -> VfsResult<(usize, u64)> {
match self {
Self::Cached(cached) => cached.append(src),
Self::Direct(loc) => {
let mut end = 0;
src.write_to(&mut ax_io::write_fn(|buf| {
loc.entry().as_file()?.append(buf).map(|(n, offset)| {
end = offset;
n
})
}))
.map(|n| (n, end))
}
}
}
pub fn location(&self) -> &Location {
match self {
Self::Cached(cached) => cached.location(),
Self::Direct(loc) => loc,
}
}
pub fn sync(&self, data_only: bool) -> VfsResult<()> {
match self {
Self::Cached(cached) => cached.sync(data_only),
Self::Direct(loc) => loc.entry().as_file()?.sync(data_only),
}
}
pub fn set_len(&self, len: u64) -> VfsResult<()> {
match self {
Self::Cached(cached) => cached.set_len(len),
Self::Direct(loc) => loc.entry().as_file()?.set_len(len),
}
}
}
pub struct File {
inner: FileBackend,
flags: FileFlags,
position: Option<Mutex<u64>>,
#[cfg(feature = "times")]
access_flags: AtomicU8,
}
impl File {
pub fn new(inner: FileBackend, flags: FileFlags) -> Self {
let position = if inner.location().flags().contains(NodeFlags::STREAM) {
None
} else {
Some(Mutex::new(if flags.contains(FileFlags::APPEND) {
inner.location().len().unwrap_or_default()
} else {
0
}))
};
Self {
inner,
flags,
position,
#[cfg(feature = "times")]
access_flags: AtomicU8::new(0),
}
}
pub fn open(context: &FsContext, path: impl AsRef<Path>) -> VfsResult<Self> {
OpenOptions::new()
.read(true)
.open(context, path.as_ref())
.and_then(OpenResult::into_file)
}
pub fn create(context: &FsContext, path: impl AsRef<Path>) -> VfsResult<Self> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(context, path.as_ref())
.and_then(OpenResult::into_file)
}
pub fn access(&self, flags: FileFlags) -> VfsResult<&FileBackend> {
if self.flags.contains(flags) && !self.is_path() {
Ok(&self.inner)
} else {
Err(VfsError::BadFileDescriptor)
}
}
pub fn is_path(&self) -> bool {
self.flags.contains(FileFlags::PATH)
}
pub fn flags(&self) -> FileFlags {
self.flags
}
pub fn backend(&self) -> VfsResult<&FileBackend> {
self.access(FileFlags::empty())?;
Ok(&self.inner)
}
pub fn location(&self) -> &Location {
self.inner.location()
}
pub fn read_at(&self, dst: impl Write + IoBufMut, offset: u64) -> VfsResult<usize> {
self.access(FileFlags::READ)?.read_at(dst, offset)
}
pub fn write_at(&self, src: impl Read + IoBuf, offset: u64) -> VfsResult<usize> {
self.access(FileFlags::WRITE)?.write_at(src, offset)
}
pub fn sync(&self, data_only: bool) -> VfsResult<()> {
self.access(FileFlags::empty())?;
self.inner.sync(data_only)
}
pub fn read(&self, dst: impl Write + IoBufMut) -> ax_io::Result<usize> {
#[cfg(feature = "times")]
{
self.access_flags.fetch_or(1, Ordering::AcqRel);
}
if let Some(pos) = self.position.as_ref() {
let mut pos = pos.lock();
self.read_at(dst, *pos).inspect(|n| {
*pos += *n as u64;
})
} else {
self.read_at(dst, 0)
}
}
pub fn write(&self, src: impl Read + IoBuf) -> ax_io::Result<usize> {
#[cfg(feature = "times")]
{
self.access_flags.fetch_or(3, Ordering::AcqRel);
}
if let Some(pos) = self.position.as_ref() {
let mut pos = pos.lock();
if let Ok(f) = self.access(FileFlags::APPEND) {
f.append(src).map(|(written, new_size)| {
*pos = new_size;
written
})
} else {
self.write_at(src, *pos).inspect(|n| {
*pos += *n as u64;
})
}
} else {
self.write_at(src, 0)
}
}
pub fn flush(&self) -> ax_io::Result {
self.access(FileFlags::empty())?;
Ok(())
}
}
impl Read for &File {
fn read(&mut self, buf: &mut [u8]) -> ax_io::Result<usize> {
(*self).read(buf)
}
}
impl Write for &File {
fn write(&mut self, buf: &[u8]) -> ax_io::Result<usize> {
(*self).write(buf)
}
fn flush(&mut self) -> ax_io::Result {
(*self).flush()
}
}
impl Seek for &File {
fn seek(&mut self, pos: SeekFrom) -> ax_io::Result<u64> {
self.access(FileFlags::empty())?;
if let Some(guard) = self.position.as_ref() {
let mut guard = guard.lock();
let new_pos = match pos {
SeekFrom::Start(pos) => pos,
SeekFrom::End(off) => {
let size = self.access(FileFlags::empty())?.location().len()?;
size.checked_add_signed(off).ok_or(VfsError::InvalidInput)?
}
SeekFrom::Current(off) => guard
.checked_add_signed(off)
.ok_or(VfsError::InvalidInput)?,
};
*guard = new_pos;
Ok(new_pos)
} else {
Ok(0)
}
}
}
impl Pollable for File {
fn poll(&self) -> IoEvents {
self.inner.location().poll()
}
fn register(&self, context: &mut Context<'_>, events: IoEvents) {
self.inner.location().register(context, events)
}
}
#[cfg(feature = "times")]
impl Drop for File {
fn drop(&mut self) {
let flags = self.access_flags.load(Ordering::Acquire);
if flags != 0 {
let mut update = axfs_ng_vfs::MetadataUpdate::default();
if flags & 1 != 0 {
update.atime = Some(ax_hal::time::wall_time());
}
if flags & 2 != 0 {
update.mtime = Some(ax_hal::time::wall_time());
}
if let Err(err) = self.inner.location().update_metadata(update) {
warn!("Failed to update file times on drop: {err:?}");
}
}
}
}