use crate::{
    driver::base::{
        block::gendisk::{GenDisk, GenDiskMountGuard},
        device::device_number::DeviceNumber,
    },
    exception::workqueue::{Work, WorkQueue},
    filesystem::{
        ext4::inode::{
            Ext4Inode, Ext4InodeLifecycle, Ext4InodeLifecycleState, Ext4InodeTimes, InodeDirtyState,
        },
        page_cache::PageCacheWritebackDomain,
        vfs::{
            self,
            fcntl::AtFlags,
            mount::MountFlags,
            utils::{user_path_at, DName},
            vcore::try_find_gendisk,
            EvictionEpoch, FileSystem, FileSystemMakerData, FsReconfigureRequest, IndexNode,
            InodeRetentionKind, Magic, MountableFileSystem, FSMAKER, VFS_MAX_FOLLOW_SYMLINK_TIMES,
        },
    },
    libs::{
        mutex::Mutex,
        rwsem::{RwSem, RwSemReadGuard, RwSemWriteGuard},
        spinlock::SpinLock,
        wait_queue::WaitQueue,
    },
    mm::{
        fault::{PageFaultHandler, PageFaultMessage},
        VmFaultReason,
    },
    process::ProcessManager,
    register_mountable_fs,
};
use alloc::{
    collections::BTreeMap,
    string::String,
    sync::{Arc, Weak},
    vec::Vec,
};
use core::{
    fmt::Write,
    sync::atomic::{AtomicBool, Ordering},
};
use kdepends::another_ext4;
use lazy_static::lazy_static;
use linkme::distributed_slice;
use system_error::SystemError;

use super::inode::LockedExt4Inode;

#[derive(Debug)]
struct CanonicalInodeEntry {
    inode: Weak<LockedExt4Inode>,
    lifecycle: Arc<Ext4InodeLifecycle>,
}

#[derive(Debug, Default)]
struct Ext4EvictionQueueState {
    next_epoch: u64,
    completed_epoch: u64,
    sealed: bool,
    error: Option<SystemError>,
}

lazy_static! {
    static ref EXT4_EVICTION_WQ: Arc<WorkQueue> = WorkQueue::new("ext4_evict");
    static ref EXT4_STATS_REGISTRY: SpinLock<Vec<Weak<Ext4FileSystem>>> = SpinLock::new(Vec::new());
    /// Locates the mount-serialization domain for each block device.  The
    /// global lock covers only this in-memory lookup; recovery and writeback
    /// are serialized by the selected device domain.
    static ref EXT4_DELALLOC_MOUNT_REGISTRY:
        Mutex<BTreeMap<usize, Weak<Ext4MountDomain>>> = Mutex::new(BTreeMap::new());
}

#[derive(Debug)]
struct Ext4MountDomain {
    /// Multiple mounts remain allowed; until VFS shares one canonical
    /// superblock, publishing a second writable instance permanently disables
    /// delayed admission on both sides after draining the first.
    instances: Mutex<Vec<Weak<Ext4FileSystem>>>,
}

impl Default for Ext4MountDomain {
    fn default() -> Self {
        Self {
            instances: Mutex::new(Vec::new()),
        }
    }
}

fn ext4_mount_domain(device: usize) -> Arc<Ext4MountDomain> {
    let mut registry = EXT4_DELALLOC_MOUNT_REGISTRY.lock();
    registry.retain(|_, domain| domain.strong_count() != 0);
    if let Some(domain) = registry.get(&device).and_then(Weak::upgrade) {
        return domain;
    }
    let domain = Arc::new(Ext4MountDomain::default());
    registry.insert(device, Arc::downgrade(&domain));
    domain
}

#[derive(Default)]
struct DelallocAdmissionRollback {
    reopen: Vec<Arc<Ext4FileSystem>>,
    committed: bool,
}

impl DelallocAdmissionRollback {
    fn record(&mut self, fs: Arc<Ext4FileSystem>) {
        self.reopen.push(fs);
    }

    fn commit(&mut self) {
        self.committed = true;
    }
}

impl Drop for DelallocAdmissionRollback {
    fn drop(&mut self) {
        if self.committed {
            return;
        }
        for fs in &self.reopen {
            fs.reopen_delalloc_admission_after_failed_mount();
        }
    }
}

pub(crate) fn prepare_stats_report() -> String {
    let mut report = String::new();
    let mut registry = EXT4_STATS_REGISTRY.lock();
    registry.retain(|entry| entry.strong_count() != 0);
    for entry in registry.iter().filter_map(Weak::upgrade) {
        let snapshot = entry.fs.prepare_stats_snapshot();
        let _ = writeln!(
            report,
            "device={} generation={} enabled={} journal={} reliable_flush={} write_barrier={} calls={} requested_blocks={} mapped_blocks={} missing_blocks={} failures={} elapsed_cycles={} bitmap_io={} gdt_io={} superblock_io={} inode_io={} extent_io={} zero_io={}",
            entry.raw_dev.data(),
            snapshot.generation,
            snapshot.enabled as usize,
            entry.fs.uses_journal() as usize,
            entry.fs.supports_reliable_flush() as usize,
            entry._mount_options.write_barrier as usize,
            snapshot.calls,
            snapshot.requested_blocks,
            snapshot.mapped_blocks,
            snapshot.missing_blocks,
            snapshot.failures,
            snapshot.elapsed_cycles,
            snapshot.bitmap_io,
            snapshot.gdt_io,
            snapshot.superblock_io,
            snapshot.inode_io,
            snapshot.extent_io,
            snapshot.zero_io,
        );
    }
    report
}

#[must_use]
pub(super) struct Ext4InodeTombstone {
    fs: Weak<Ext4FileSystem>,
    inode_num: u32,
    lifecycle: Arc<Ext4InodeLifecycle>,
    resolved: bool,
}

#[derive(Debug)]
struct Ext4MetadataMutationWait {
    wait_queue: WaitQueue,
}

impl Ext4MetadataMutationWait {
    fn new() -> Self {
        Self {
            wait_queue: WaitQueue::default(),
        }
    }
}

impl another_ext4::MetadataMutationWaker for Ext4MetadataMutationWait {
    fn wake_all(&self) {
        self.wait_queue.wake_all();
    }
}

pub struct Ext4FileSystem {
    pub(super) writeback_domain: Arc<PageCacheWritebackDomain>,
    /// 对应 another_ext4 中的实际文件系统
    pub(super) fs: another_ext4::Ext4,
    /// 当前文件系统对应的设备号
    pub(super) raw_dev: DeviceNumber,
    /// Prevents loop clear/remove for the complete filesystem lifetime.
    _device_mount_holder: GenDiskMountGuard,

    /// 根 inode
    root_inode: Arc<LockedExt4Inode>,

    /// 元数据（size/mtime）脏但尚未刷盘的 inode 列表。
    dirty_inodes: Mutex<Vec<Arc<LockedExt4Inode>>>,

    /// Per-superblock canonical VFS inode identity, keyed by the on-disk inode number.
    inode_table: Mutex<BTreeMap<u32, CanonicalInodeEntry>>,

    /// Keeps this device's mount-serialization domain alive for the complete
    /// filesystem lifetime.
    _mount_domain: Arc<Ext4MountDomain>,

    /// Unique lower authority and strong owners for the Stage-3b single-head
    /// delayed mapper.  An inode is present exactly while it owns a live
    /// capability or mapper token.
    pub(super) delalloc_mapper_authority: Option<another_ext4::DelallocAppendMapperAuthority>,
    delalloc_inodes: Mutex<BTreeMap<u32, Arc<LockedExt4Inode>>>,
    delalloc_wait: WaitQueue,
    delalloc_admission_open: AtomicBool,

    /// Serializes only delayed-allocation transaction submission for this
    /// mount. The lower transactional metadata gate has filesystem scope, so
    /// per-inode producers must wait in the same ownership domain instead of
    /// repeatedly restoring and reclaiming their PageCache batch on EAGAIN.
    ///
    /// Lock order:
    /// delalloc_submit_lock -> inode.metadata_commit_lock
    ///     -> inode.delalloc_pool -> lower transactional gate.
    ///
    /// PageCache, delalloc registry, and admission guards must never be held
    /// while acquiring this sleeping lock.
    pub(super) delalloc_submit_lock: Mutex<()>,

    /// Scheduler-specific side of another_ext4's metadata-gate progress
    /// bridge. It owns only a wait queue and deliberately has no filesystem
    /// back-reference, so the lower install-once Arc cannot form a cycle.
    metadata_mutation_wait: Arc<Ext4MetadataMutationWait>,

    /// Allocations hold a read guard through canonical publication. Physical reclaim
    /// holds a write guard through tombstone completion so an inode number cannot be
    /// reused in the handoff window.
    inode_reuse_barrier: RwSem<()>,

    /// Fail-stop allocation after an indeterminate physical reclaim.
    lifecycle_error: Mutex<Option<SystemError>>,

    /// Own capabilities whose canonical handoff cannot be reconstructed after
    /// an impossible namespace outcome. A fail-stopped mount keeps them alive;
    /// mount recovery consumes the durable orphan chain after teardown.
    pub(super) quarantined_reclaims: SpinLock<Vec<another_ext4::InodeReclaimHandle>>,

    eviction_queue: SpinLock<Ext4EvictionQueueState>,
    eviction_wait: WaitQueue,

    /// Mount-time ext4 options parsed from user/kernel mount data.
    _mount_options: Ext4MountOptions,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ext4DaxMode {
    Never,
    Always,
    Inode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ext4ErrorsBehavior {
    Continue,
    RemountRo,
    Panic,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ext4MountOptions {
    pub dax: Option<Ext4DaxMode>,
    pub errors: Ext4ErrorsBehavior,
    pub read_only: bool,
    /// Whether explicit durability boundaries issue a device cache flush.
    pub write_barrier: bool,
}

impl Default for Ext4MountOptions {
    fn default() -> Self {
        Self {
            dax: None,
            errors: Ext4ErrorsBehavior::Continue,
            read_only: false,
            write_barrier: true,
        }
    }
}

impl Ext4MountOptions {
    fn validate_error_behavior(&self) -> Result<(), SystemError> {
        match self.errors {
            Ext4ErrorsBehavior::Continue => Ok(()),
            // A read-only mount already satisfies the externally observable
            // result of errors=remount-ro. Dynamic writable-to-read-only
            // transitions are not supported yet and must not be advertised.
            Ext4ErrorsBehavior::RemountRo if self.read_only => Ok(()),
            Ext4ErrorsBehavior::RemountRo | Ext4ErrorsBehavior::Panic => {
                Err(SystemError::EOPNOTSUPP_OR_ENOTSUP)
            }
        }
    }

    fn parse(raw_data: Option<&str>) -> Result<Self, SystemError> {
        let mut options = Self::default();
        let Some(raw_data) = raw_data else {
            return Ok(options);
        };

        for raw_token in raw_data.split(',') {
            let token = raw_token.trim();
            if token.is_empty() {
                continue;
            }

            match token {
                "barrier" | "barrier=1" => options.write_barrier = true,
                "nobarrier" | "barrier=0" => options.write_barrier = false,
                "errors=continue" => options.errors = Ext4ErrorsBehavior::Continue,
                "errors=remount-ro" => options.errors = Ext4ErrorsBehavior::RemountRo,
                "errors=panic" => options.errors = Ext4ErrorsBehavior::Panic,
                // Do not silently accept an option whose semantics ext4 does
                // not implement. In particular, values other than 0/1 and
                // journal-only options on a nojournal mount must fail here or
                // in a future capability-aware parser.
                _ => return Err(SystemError::EINVAL),
            }
        }

        Ok(options)
    }
}

impl FileSystem for Ext4FileSystem {
    fn page_cache_writeback_domain(&self) -> Option<&Arc<PageCacheWritebackDomain>> {
        Some(&self.writeback_domain)
    }

    fn quiesce_page_cache_producers(&self) -> Result<(), SystemError> {
        if self._mount_options.read_only {
            return Ok(());
        }
        self.close_delalloc_admission();
        if let Err(error) = self.drain_registered_delalloc() {
            self.fail_stop_lifecycle();
            self.terminalize_idle_delalloc_after_fail_stop();
            return Err(error);
        }
        Ok(())
    }
    fn reconfigure(&self, request: FsReconfigureRequest<'_>) -> Result<MountFlags, SystemError> {
        if request.raw_data.is_some_and(|raw| !raw.trim().is_empty()) {
            return Err(SystemError::EINVAL);
        }
        let requested_read_only = request.sb_flags.contains(MountFlags::RDONLY);
        if request.sb_flags_mask.contains(MountFlags::RDONLY)
            && requested_read_only != self._mount_options.read_only
        {
            return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP);
        }
        Ok(request.sb_flags & request.sb_flags_mask)
    }

    fn supports_reliable_flush(&self) -> bool {
        self.fs.supports_reliable_flush()
    }

    fn root_inode(&self) -> Arc<dyn IndexNode> {
        self.root_inode.clone()
    }

    fn info(&self) -> vfs::FsInfo {
        vfs::FsInfo {
            blk_dev_id: self.raw_dev.data() as usize,
            max_name_len: 255,
        }
    }

    fn as_any_ref(&self) -> &dyn core::any::Any {
        self
    }

    fn name(&self) -> &str {
        "ext4"
    }

    fn super_block(&self) -> vfs::SuperBlock {
        vfs::SuperBlock::new(Magic::EXT4_MAGIC, another_ext4::BLOCK_SIZE as u64, 255)
    }

    fn statfs(&self, _inode: &Arc<dyn IndexNode>) -> Result<vfs::SuperBlock, SystemError> {
        self.read_statfs_from_superblock()
    }

    unsafe fn fault(&self, pfm: &mut PageFaultMessage) -> VmFaultReason {
        PageFaultHandler::filemap_fault(pfm)
    }

    unsafe fn page_mkwrite(&self, pfm: &mut PageFaultMessage) -> VmFaultReason {
        let file = pfm.vma().lock().vm_file();
        let Some(file) = file else {
            return VmFaultReason::VM_FAULT_SIGBUS;
        };
        let Some(page_index) = pfm.backing_pgoff() else {
            return VmFaultReason::VM_FAULT_SIGBUS;
        };
        let inode_ref = file.inode();
        let Some(inode) = inode_ref.as_any_ref().downcast_ref::<LockedExt4Inode>() else {
            return VmFaultReason::VM_FAULT_SIGBUS;
        };
        let Ok(_prepare_guard) = inode.prepare_mmap_write(page_index) else {
            return VmFaultReason::VM_FAULT_SIGBUS;
        };
        PageFaultHandler::filemap_page_mkwrite(pfm)
    }

    unsafe fn map_pages(
        &self,
        pfm: &mut PageFaultMessage,
        start_pgoff: usize,
        end_pgoff: usize,
    ) -> VmFaultReason {
        PageFaultHandler::filemap_map_pages(pfm, start_pgoff, end_pgoff)
    }

    fn sync_fs(&self, wait: bool) -> Result<(), SystemError> {
        let flush_result = self.flush_dirty_inodes();
        let eviction_epoch = wait.then(|| {
            // Capture after dirty metadata flush: releasing the last AsyncWork
            // owner can publish reclaim, and this sync boundary must include it.
            EvictionEpoch::new(self.eviction_queue.lock().next_epoch)
        });
        let result = if let Some(epoch) = eviction_epoch {
            // Finish the snapshotted asynchronous metadata work even if inode
            // writeback failed, while preserving that earlier error for the
            // caller and the superblock writeback error sequence.
            let eviction_result = self.drain_evictions_through(epoch);
            flush_result.and(eviction_result)
        } else {
            flush_result
        };
        if wait {
            if self._mount_options.write_barrier {
                result.and_then(|_| self.fs.flush_device().map_err(SystemError::from))
            } else {
                result
            }
        } else {
            result
        }
    }

    fn on_umount(&self) {
        if self._mount_options.read_only {
            return;
        }
        if let Err(error) = self.flush_dirty_inodes() {
            log::error!(
                "ext4: failed final metadata sync after delayed-map drain: {:?}",
                error
            );
            self.fail_stop_lifecycle();
            self.terminalize_idle_delalloc_after_fail_stop();
            return;
        }
        if let Err(error) = self.fs.shutdown_writable() {
            log::error!("ext4: failed to mark journal clean on unmount: {:?}", error);
        }
    }

    fn seal_eviction_queue(&self) -> EvictionEpoch {
        let mut queue = self.eviction_queue.lock();
        queue.sealed = true;
        EvictionEpoch::new(queue.next_epoch)
    }

    fn drain_evictions_through(&self, epoch: EvictionEpoch) -> Result<(), SystemError> {
        self.eviction_wait
            .wait_until(|| {
                let queue = self.eviction_queue.lock();
                (queue.completed_epoch >= epoch.value()).then(|| queue.error.clone())
            })
            .map_or(Ok(()), Err)
    }
}

impl Ext4FileSystem {
    pub(super) fn wait_metadata_mutation_progress(&self, observed: u64) -> Result<(), SystemError> {
        if self.fs.metadata_mutations_terminal() || self.lifecycle_error.lock().is_some() {
            return Err(SystemError::EIO);
        }
        if self.fs.metadata_mutation_generation() != observed {
            return Ok(());
        }

        self.metadata_mutation_wait.wait_queue.wait_until_io(|| {
            if self.fs.metadata_mutations_terminal() || self.lifecycle_error.lock().is_some() {
                Some(Err(SystemError::EIO))
            } else if self.fs.metadata_mutation_generation() != observed {
                Some(Ok(()))
            } else {
                None
            }
        })
    }

    pub(super) fn retry_metadata_contention<T>(
        &self,
        mut operation: impl FnMut() -> core::result::Result<T, another_ext4::Ext4Error>,
    ) -> Result<T, SystemError> {
        loop {
            let observed = self.fs.metadata_mutation_generation();
            match operation() {
                Ok(value) => return Ok(value),
                Err(error) if error.code() == another_ext4::ErrCode::EAGAIN => {
                    self.wait_metadata_mutation_progress(observed)?;
                }
                Err(error) => return Err(error.into()),
            }
        }
    }

    pub(super) fn delalloc_admission_open(&self) -> bool {
        self.delalloc_mapper_authority.is_some()
            && self.delalloc_admission_open.load(Ordering::Acquire)
            && self.lifecycle_error.lock().is_none()
    }

    pub(super) fn register_delalloc_inode(
        &self,
        inode_num: u32,
        inode: Arc<LockedExt4Inode>,
    ) -> Result<(), SystemError> {
        let mut owners = self.delalloc_inodes.lock();
        if !self.delalloc_admission_open() {
            return Err(SystemError::EROFS);
        }
        if owners.contains_key(&inode_num) {
            return Err(SystemError::EIO);
        }
        owners.insert(inode_num, inode);
        Ok(())
    }

    pub(super) fn unregister_delalloc_inode(&self, inode_num: u32) {
        self.delalloc_inodes.lock().remove(&inode_num);
        self.delalloc_wait.wake_all();
    }

    fn close_delalloc_admission(&self) {
        // Serialize the open->registry publication with close.  Storing the
        // fence while holding the registry lock guarantees that a producer is
        // either fully visible to the subsequent drain or observes closed
        // admission; it cannot publish into an already-emptied registry.
        let _owners = self.delalloc_inodes.lock();
        self.delalloc_admission_open.store(false, Ordering::Release);
    }

    /// Restore admission on a healthy mapper-owning filesystem after a
    /// prospective sibling writable mount failed.
    ///
    /// The caller holds the per-device mount-domain lock, so a successful
    /// reopen cannot race publication of another writable instance.
    fn reopen_delalloc_admission_after_failed_mount(&self) {
        let lifecycle_error = self.lifecycle_error.lock();
        if self.delalloc_mapper_authority.is_some() && lifecycle_error.is_none() {
            self.delalloc_admission_open.store(true, Ordering::Release);
        }
    }

    fn drain_registered_delalloc(&self) -> Result<(), SystemError> {
        loop {
            let owners: Vec<_> = self.delalloc_inodes.lock().values().cloned().collect();
            if owners.is_empty() {
                return Ok(());
            }
            for inode in owners {
                inode.drain_delalloc_before_eager()?;
            }
            self.delalloc_wait
                .wait_until(|| self.delalloc_inodes.lock().is_empty().then_some(()));
        }
    }

    pub(super) fn terminalize_idle_delalloc_after_fail_stop(&self) {
        loop {
            let owners: Vec<_> = self.delalloc_inodes.lock().values().cloned().collect();
            let mut progressed = false;
            for inode in owners {
                progressed |= inode.terminalize_idle_delalloc_after_fail_stop(self);
            }
            if !progressed {
                return;
            }
        }
    }

    /// Complete an explicit fsync/fdatasync/O_SYNC durability boundary.
    ///
    /// Linux ext4 performs the nojournal barrier after data and metadata
    /// writeback, rather than flushing every ordinary metadata transaction.
    /// Read-only and `nobarrier` mounts do not issue a device flush.
    pub(super) fn finish_sync_durability_boundary(&self) -> Result<(), SystemError> {
        if self._mount_options.read_only || !self._mount_options.write_barrier {
            return Ok(());
        }
        self.fs.flush_device().map_err(SystemError::from)
    }

    pub(super) fn schedule_inode_eviction(
        self: &Arc<Self>,
        inode: Arc<LockedExt4Inode>,
    ) -> Result<(), SystemError> {
        let fs = self.clone();
        {
            // Linearize epoch allocation with FIFO publication so concurrent
            // producers cannot invert completion epochs.
            let mut queue = self.eviction_queue.lock();
            if queue.sealed {
                return Err(SystemError::EBUSY);
            }
            queue.next_epoch = queue
                .next_epoch
                .checked_add(1)
                .ok_or(SystemError::EOVERFLOW)?;
            let epoch = queue.next_epoch;
            EXT4_EVICTION_WQ.enqueue(Work::new(move || {
                let result = inode.run_deferred_eviction();
                let mut queue = fs.eviction_queue.lock();
                if let Err(error) = result {
                    queue.error.get_or_insert(error);
                }
                queue.completed_epoch = epoch;
                drop(queue);
                fs.eviction_wait.wake_all();
            }));
        }
        Ok(())
    }

    pub(super) fn begin_allocation(&self) -> Result<RwSemReadGuard<'_, ()>, SystemError> {
        let guard = self.inode_reuse_barrier.read();
        if let Some(error) = self.lifecycle_error.lock().clone() {
            return Err(error);
        }
        Ok(guard)
    }

    pub(super) fn begin_reclaim(&self) -> RwSemWriteGuard<'_, ()> {
        self.inode_reuse_barrier.write()
    }

    pub(super) fn fail_stop_lifecycle(&self) {
        self.fs.fail_stop_mutations();
        *self.lifecycle_error.lock() = Some(SystemError::EIO);
        self.close_delalloc_admission();
        // lower fail-stop already broadcasts its generation. This second
        // broadcast closes the upper lifecycle transition as well and is
        // harmless because every waiter rechecks the predicate.
        self.metadata_mutation_wait.wait_queue.wake_all();
    }

    pub(super) fn get_or_create_inode(
        self: &Arc<Self>,
        inode_num: u32,
        dname: DName,
        parent: Option<Weak<LockedExt4Inode>>,
    ) -> Result<Arc<LockedExt4Inode>, SystemError> {
        self.get_or_create_inode_inner(inode_num, dname, parent, false, None)
    }

    pub(super) fn publish_allocated_inode(
        self: &Arc<Self>,
        attr: another_ext4::FileAttr,
        dname: DName,
        parent: Option<Weak<LockedExt4Inode>>,
        _reuse_guard: &RwSemReadGuard<'_, ()>,
    ) -> Result<Arc<LockedExt4Inode>, SystemError> {
        self.get_or_create_inode_inner(attr.ino, dname, parent, true, Some(attr))
    }

    fn get_or_create_inode_inner(
        self: &Arc<Self>,
        inode_num: u32,
        dname: DName,
        parent: Option<Weak<LockedExt4Inode>>,
        reuse_guard_held: bool,
        allocated_attr: Option<another_ext4::FileAttr>,
    ) -> Result<Arc<LockedExt4Inode>, SystemError> {
        let mut candidate = None;
        let mut admission_guard = None;
        loop {
            let wait_lifecycle = {
                let mut table = self.inode_table.lock();
                match table.get(&inode_num) {
                    Some(entry) => match entry.lifecycle.state() {
                        Ext4InodeLifecycleState::Live => {
                            if let Some(inode) = entry.inode.upgrade() {
                                return Ok(inode);
                            }
                            entry.lifecycle.set_state(Ext4InodeLifecycleState::Retired);
                            table.remove(&inode_num);
                            None
                        }
                        Ext4InodeLifecycleState::Freeing => {
                            candidate = None;
                            drop(admission_guard.take());
                            Some(entry.lifecycle.clone())
                        }
                        Ext4InodeLifecycleState::Retired => {
                            candidate = None;
                            drop(admission_guard.take());
                            table.remove(&inode_num);
                            None
                        }
                        Ext4InodeLifecycleState::Poisoned(error) => return Err(error),
                    },
                    None => {
                        if candidate.is_none() {
                            drop(table);
                            if !reuse_guard_held {
                                admission_guard = Some(self.inode_reuse_barrier.read());
                            }
                            candidate = Some(if let Some(attr) = allocated_attr.as_ref() {
                                LockedExt4Inode::new_with_attr(
                                    inode_num,
                                    Arc::downgrade(self),
                                    dname.clone(),
                                    parent.clone(),
                                    attr,
                                )?
                            } else {
                                LockedExt4Inode::new(
                                    inode_num,
                                    Arc::downgrade(self),
                                    dname.clone(),
                                    parent.clone(),
                                )?
                            });
                            continue;
                        }
                        let inode = candidate.take().expect("candidate checked above");
                        table.insert(
                            inode_num,
                            CanonicalInodeEntry {
                                inode: Arc::downgrade(&inode),
                                lifecycle: inode.lifecycle().clone(),
                            },
                        );
                        drop(admission_guard.take());
                        return Ok(inode);
                    }
                }
            };

            if let Some(lifecycle) = wait_lifecycle {
                match lifecycle.wait_while_freeing() {
                    Ext4InodeLifecycleState::Poisoned(error) => return Err(error),
                    _ => continue,
                }
            }
        }
    }

    pub(super) fn validate_inode(&self, inode: &Arc<LockedExt4Inode>) -> Result<(), SystemError> {
        let inode_num = inode.inner.lock().inner_inode_num;
        let table = self.inode_table.lock();
        let entry = table.get(&inode_num).ok_or(SystemError::ESTALE)?;
        if !Weak::ptr_eq(&entry.inode, &Arc::downgrade(inode))
            || !Arc::ptr_eq(&entry.lifecycle, inode.lifecycle())
        {
            return Err(SystemError::ESTALE);
        }
        inode.lifecycle().begin_operation().map(drop)
    }

    pub(super) fn begin_freeing(
        self: &Arc<Self>,
        inode: &Arc<LockedExt4Inode>,
    ) -> Result<Ext4InodeTombstone, SystemError> {
        let inode_num = inode.inner.lock().inner_inode_num;
        {
            let table = self.inode_table.lock();
            let entry = table.get(&inode_num).ok_or(SystemError::ESTALE)?;
            if !Weak::ptr_eq(&entry.inode, &Arc::downgrade(inode))
                || !Arc::ptr_eq(&entry.lifecycle, inode.lifecycle())
            {
                return Err(SystemError::ESTALE);
            }
            entry.lifecycle.begin_freeing()?;
        }
        inode.lifecycle().wait_for_quiescent();
        Ok(Ext4InodeTombstone {
            fs: Arc::downgrade(self),
            inode_num,
            lifecycle: inode.lifecycle().clone(),
            resolved: false,
        })
    }

    fn finish_tombstone(
        &self,
        tombstone: &mut Ext4InodeTombstone,
        state: Ext4InodeLifecycleState,
        remove: bool,
    ) -> Result<(), SystemError> {
        let mut table = self.inode_table.lock();
        let entry = table.get(&tombstone.inode_num).ok_or(SystemError::ESTALE)?;
        if !Arc::ptr_eq(&entry.lifecycle, &tombstone.lifecycle) {
            return Err(SystemError::ESTALE);
        }
        tombstone.lifecycle.set_state(state);
        if remove {
            table.remove(&tombstone.inode_num);
        }
        tombstone.resolved = true;
        Ok(())
    }

    pub(super) fn complete_freeing(
        &self,
        mut tombstone: Ext4InodeTombstone,
    ) -> Result<(), SystemError> {
        self.finish_tombstone(&mut tombstone, Ext4InodeLifecycleState::Retired, true)
    }

    pub(super) fn abort_freeing(
        &self,
        mut tombstone: Ext4InodeTombstone,
    ) -> Result<(), SystemError> {
        self.finish_tombstone(&mut tombstone, Ext4InodeLifecycleState::Live, false)
    }

    pub(super) fn poison_freeing(
        &self,
        mut tombstone: Ext4InodeTombstone,
        _error: SystemError,
    ) -> Result<(), SystemError> {
        let poison = SystemError::EIO;
        // An unresolved canonical inode tombstone makes the filesystem-wide
        // lifecycle unsafe, not merely this one inode. Use the same ordered
        // lower-poison/proof/cleanup publication as delayed mapper failures
        // so queued leases cannot continue retrying on a nominally failed FS.
        self.fail_stop_lifecycle();
        self.finish_tombstone(
            &mut tombstone,
            Ext4InodeLifecycleState::Poisoned(poison),
            false,
        )
    }

    pub(super) fn mark_inode_dirty(
        inode: &Arc<LockedExt4Inode>,
        dirty: InodeDirtyState,
    ) -> Result<(), SystemError> {
        Self::mark_inode_dirty_with(inode, dirty, |_| {})
    }

    pub(super) fn mark_inode_atime_dirty(
        inode: &Arc<LockedExt4Inode>,
        atime: u32,
        relatime: bool,
    ) -> Result<(), SystemError> {
        let now = crate::time::PosixTimeSpec::new(atime.into(), 0);
        Self::mark_inode_dirty_if(
            inode,
            InodeDirtyState::ATIME_DIRTY,
            |guard| {
                let times = guard.cached_times;
                vfs::should_update_atime(
                    crate::time::PosixTimeSpec::new(times.atime.into(), 0),
                    crate::time::PosixTimeSpec::new(times.mtime.into(), 0),
                    crate::time::PosixTimeSpec::new(times.ctime.into(), 0),
                    now,
                    relatime,
                )
            },
            |guard| {
                let atime_version = guard
                    .cached_atime_version
                    .checked_add(1)
                    .ok_or(SystemError::EOVERFLOW)?;
                guard.cached_times.atime = atime;
                guard.cached_atime_version = atime_version;
                Ok(())
            },
        )
    }

    fn mark_inode_dirty_with<F>(
        inode: &Arc<LockedExt4Inode>,
        dirty: InodeDirtyState,
        update_cached_metadata: F,
    ) -> Result<(), SystemError>
    where
        F: FnOnce(&mut super::inode::Ext4Inode),
    {
        Self::mark_inode_dirty_if(
            inode,
            dirty,
            |_| true,
            |guard| {
                update_cached_metadata(guard);
                Ok(())
            },
        )
    }

    fn mark_inode_dirty_if<P, F>(
        inode: &Arc<LockedExt4Inode>,
        dirty: InodeDirtyState,
        should_update: P,
        update_cached_metadata: F,
    ) -> Result<(), SystemError>
    where
        P: FnOnce(&super::inode::Ext4Inode) -> bool,
        F: FnOnce(&mut super::inode::Ext4Inode) -> Result<(), SystemError>,
    {
        let _operation = inode.lifecycle().begin_operation()?;
        // Acquire a prospective queue ownership before publishing dirty state.
        // If the inode is already queued, the existing AsyncWork owner prevents
        // this temporary retain/release pair from creating a false-zero edge.
        inode.retain(InodeRetentionKind::AsyncWork)?;
        let queue_fs = {
            let mut guard = inode.inner.lock();
            if !should_update(&guard) {
                drop(guard);
                inode.release(InodeRetentionKind::AsyncWork);
                return Ok(());
            }
            let should_queue = !guard
                .dirty_state
                .intersects(InodeDirtyState::QUEUED | InodeDirtyState::WRITEBACK);
            let queue_fs = if should_queue {
                let Some(fs) = guard.fs_ptr.upgrade() else {
                    drop(guard);
                    inode.release(InodeRetentionKind::AsyncWork);
                    return Err(SystemError::EIO);
                };
                Some(fs)
            } else {
                None
            };
            if let Err(error) = update_cached_metadata(&mut guard) {
                drop(guard);
                inode.release(InodeRetentionKind::AsyncWork);
                return Err(error);
            }
            guard.dirty_state.insert(dirty);
            if should_queue {
                guard.dirty_state.insert(InodeDirtyState::QUEUED);
            }
            queue_fs
        };

        if let Some(fs) = queue_fs {
            fs.dirty_inodes.lock().push(inode.clone());
        } else {
            inode.release(InodeRetentionKind::AsyncWork);
        }
        Ok(())
    }

    pub(super) fn release_clean_queued_inode(&self, inode: &Arc<LockedExt4Inode>) {
        {
            let mut dirty_inodes = self.dirty_inodes.lock();
            let Some(position) = dirty_inodes
                .iter()
                .position(|queued| Arc::ptr_eq(queued, inode))
            else {
                // flush_dirty_inodes() already owns this queue entry and will
                // release its AsyncWork retention after observing it clean.
                return;
            };

            let mut guard = inode.inner.lock();
            if !guard.dirty_state.contains(InodeDirtyState::QUEUED)
                || guard
                    .dirty_state
                    .intersects(InodeDirtyState::PERSISTENT_DIRTY | InodeDirtyState::WRITEBACK)
            {
                return;
            }

            dirty_inodes.swap_remove(position);
            guard.dirty_state.remove(InodeDirtyState::QUEUED);
        }

        inode.release(InodeRetentionKind::AsyncWork);
    }

    fn publish_requeued_inodes(&self, requeue: Vec<Arc<LockedExt4Inode>>) {
        let mut clean = Vec::new();
        {
            let mut dirty_inodes = self.dirty_inodes.lock();
            for inode in requeue {
                let mut guard = inode.inner.lock();
                if guard
                    .dirty_state
                    .intersects(InodeDirtyState::PERSISTENT_DIRTY)
                {
                    guard.dirty_state.insert(InodeDirtyState::QUEUED);
                    drop(guard);
                    dirty_inodes.push(inode);
                } else {
                    guard.dirty_state.remove(InodeDirtyState::QUEUED);
                    drop(guard);
                    clean.push(inode);
                }
            }
        }

        for inode in clean {
            inode.release(InodeRetentionKind::AsyncWork);
        }
    }

    fn flush_dirty_inodes(&self) -> Result<(), SystemError> {
        let dirty: Vec<Arc<LockedExt4Inode>> = {
            let mut guard = self.dirty_inodes.lock();
            if guard.is_empty() {
                return Ok(());
            }
            core::mem::take(&mut *guard)
        };

        let mut last_err = Ok(());
        let mut requeue: Vec<Arc<LockedExt4Inode>> = Vec::new();
        for inode in dirty {
            let mut should_requeue = false;
            let mut release_async_owner = false;
            let has_dirty_metadata = {
                let mut guard = inode.inner.lock();
                let has_dirty = guard
                    .dirty_state
                    .intersects(InodeDirtyState::PERSISTENT_DIRTY);
                if !has_dirty {
                    guard
                        .dirty_state
                        .remove(InodeDirtyState::QUEUED | InodeDirtyState::WRITEBACK);
                    release_async_owner = true;
                }
                has_dirty
            };

            if !has_dirty_metadata {
                if release_async_owner {
                    inode.release(InodeRetentionKind::AsyncWork);
                }
                continue;
            }

            let operation = match inode.lifecycle().begin_operation() {
                Ok(operation) => operation,
                Err(error @ (SystemError::ESTALE | SystemError::EIO)) => {
                    log::error!(
                        "ext4: rejecting stale metadata writeback before disk access: {:?}",
                        error
                    );
                    let page_cache = {
                        let mut guard = inode.inner.lock();
                        guard.dirty_state.remove(
                            InodeDirtyState::PERSISTENT_DIRTY
                                | InodeDirtyState::QUEUED
                                | InodeDirtyState::WRITEBACK,
                        );
                        guard.page_cache.clone()
                    };
                    if let Some(page_cache) = page_cache {
                        page_cache.record_writeback_error_with_superblock(error.clone());
                    }
                    inode.release(InodeRetentionKind::AsyncWork);
                    last_err = Err(error);
                    continue;
                }
                Err(error) => {
                    requeue.push(inode);
                    last_err = Err(error);
                    continue;
                }
            };

            // The metadata worker must obey the same ordered publication gate
            // as fsync/truncate: no cached size/time snapshot may overtake the
            // single delayed append which owns the next durable EOF.
            let _delalloc_admission = match inode.close_production_delalloc_admission() {
                Ok(guard) => guard,
                Err(error) => {
                    requeue.push(inode.clone());
                    last_err = Err(error);
                    continue;
                }
            };
            if let Err(error) = inode.drain_delalloc_before_eager() {
                requeue.push(inode.clone());
                last_err = Err(error);
                continue;
            }

            let result = {
                let _operation = operation;
                let _io_guard = inode.io_lock.lock();
                let _metadata_commit = inode.metadata_commit_lock.lock();
                let (
                    fs,
                    inode_num,
                    snapshot_dirty,
                    cached_size,
                    cached_times,
                    cached_atime_version,
                    cached_mtime_version,
                    cached_ctime_version,
                ) = {
                    let mut guard = inode.inner.lock();
                    guard.dirty_state.remove(InodeDirtyState::QUEUED);
                    let snapshot_dirty = guard
                        .dirty_state
                        .intersection(InodeDirtyState::PERSISTENT_DIRTY);
                    if snapshot_dirty.is_empty() {
                        guard.dirty_state.remove(InodeDirtyState::WRITEBACK);
                        release_async_owner = true;
                    }
                    if !snapshot_dirty.is_empty() {
                        guard.dirty_state.insert(InodeDirtyState::WRITEBACK);
                    }
                    (
                        guard.fs_ptr.upgrade(),
                        guard.inner_inode_num,
                        snapshot_dirty,
                        guard.cached_file_size,
                        guard.cached_times,
                        guard.cached_atime_version,
                        guard.cached_mtime_version,
                        guard.cached_ctime_version,
                    )
                };

                if snapshot_dirty.is_empty() {
                    Ok(())
                } else {
                    let result = if let Some(fs) = fs {
                        let size = if snapshot_dirty.contains(InodeDirtyState::SIZE_DIRTY) {
                            match cached_size {
                                Some(size) => Ok(Some(size)),
                                None => fs
                                    .fs
                                    .getattr(inode_num)
                                    .map(|attr| Some(attr.size))
                                    .map_err(SystemError::from),
                            }
                        } else {
                            Ok(None)
                        };
                        size.and_then(|size| {
                            let mtime = if snapshot_dirty.contains(InodeDirtyState::MTIME_DIRTY) {
                                Some(cached_times.mtime)
                            } else {
                                None
                            };
                            let atime = if snapshot_dirty.contains(InodeDirtyState::ATIME_DIRTY) {
                                Some(cached_times.atime)
                            } else {
                                None
                            };
                            let ctime = if snapshot_dirty.contains(InodeDirtyState::CTIME_DIRTY) {
                                Some(cached_times.ctime)
                            } else {
                                None
                            };
                            fs.retry_metadata_contention(|| {
                                fs.fs
                                    .commit_inode_metadata(inode_num, size, atime, mtime, ctime)
                            })
                        })
                    } else {
                        Err(SystemError::EIO)
                    };

                    let mut guard = inode.inner.lock();
                    if result.is_ok() {
                        if snapshot_dirty.contains(InodeDirtyState::MTIME_DIRTY) {
                            guard.durable_mtime_version =
                                guard.durable_mtime_version.max(cached_mtime_version);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::ATIME_DIRTY) {
                            guard.durable_atime_version =
                                guard.durable_atime_version.max(cached_atime_version);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::CTIME_DIRTY) {
                            guard.durable_ctime_version =
                                guard.durable_ctime_version.max(cached_ctime_version);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::SIZE_DIRTY)
                            && guard.cached_file_size == cached_size
                        {
                            guard.dirty_state.remove(InodeDirtyState::SIZE_DIRTY);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::MTIME_DIRTY)
                            && guard.cached_mtime_version == cached_mtime_version
                        {
                            guard.dirty_state.remove(InodeDirtyState::MTIME_DIRTY);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::ATIME_DIRTY)
                            && guard.cached_atime_version == cached_atime_version
                        {
                            guard.dirty_state.remove(InodeDirtyState::ATIME_DIRTY);
                        }
                        if snapshot_dirty.contains(InodeDirtyState::CTIME_DIRTY)
                            && guard.cached_ctime_version == cached_ctime_version
                        {
                            guard.dirty_state.remove(InodeDirtyState::CTIME_DIRTY);
                        }
                    }
                    guard.dirty_state.remove(InodeDirtyState::WRITEBACK);
                    if guard
                        .dirty_state
                        .intersects(InodeDirtyState::PERSISTENT_DIRTY)
                    {
                        guard.dirty_state.insert(InodeDirtyState::QUEUED);
                        should_requeue = true;
                    } else {
                        guard.dirty_state.remove(InodeDirtyState::QUEUED);
                        release_async_owner = true;
                    }
                    result
                }
            };

            if let Err(e) = result {
                log::warn!("flush_dirty_inodes: 元数据刷盘失败: {:?}", e);
                last_err = Err(e);
            }
            if should_requeue {
                requeue.push(inode.clone());
            } else if release_async_owner {
                inode.release(InodeRetentionKind::AsyncWork);
            }
        }
        if !requeue.is_empty() {
            self.publish_requeued_inodes(requeue);
        }
        last_err
    }

    fn read_statfs_from_superblock(&self) -> Result<vfs::SuperBlock, SystemError> {
        let ext4_sb = self.fs.super_block()?;
        let block_size = ext4_sb.block_size();
        let blocks = ext4_sb.block_count();
        let overhead_blocks = ext4_sb.clusters_to_blocks(ext4_sb.overhead_clusters() as u64);
        let bfree = ext4_sb.free_blocks_count();
        let reserved = ext4_sb.reserved_blocks_count();

        let mut sb = vfs::SuperBlock::new(Magic::EXT4_MAGIC, block_size, 255);
        // Linux ext4 语义：f_blocks 不包含元数据开销。
        sb.blocks = blocks.saturating_sub(overhead_blocks);
        sb.bfree = bfree;
        sb.bavail = bfree.saturating_sub(reserved);
        sb.files = ext4_sb.inode_count() as u64;
        sb.ffree = ext4_sb.free_inodes_count() as u64;
        sb.frsize = block_size;
        Ok(sb)
    }

    /// 探测 gendisk 是否包含 ext4 文件系统
    pub fn probe(gendisk: &Arc<GenDisk>) -> Result<bool, SystemError> {
        Ok(another_ext4::Ext4::load(gendisk.clone())
            .map(|_| true)
            .unwrap_or(false))
    }

    pub fn from_gendisk_with_options(
        mount_data: Arc<GenDisk>,
        mount_options: Ext4MountOptions,
    ) -> Result<Arc<dyn FileSystem>, SystemError> {
        mount_options.validate_error_behavior()?;
        let device_mount_holder = mount_data.acquire_mount_holder()?;

        // Worker creation may sleep and must never happen from final-release
        // publication while inode/eviction locks are held.
        lazy_static::initialize(&EXT4_EVICTION_WQ);
        let raw_dev = mount_data.device_num();
        let mount_domain = ext4_mount_domain(raw_dev.data() as usize);
        let mut mounted_instances = mount_domain.instances.lock();
        let existing_writable: Vec<_> = {
            mounted_instances.retain(|entry| entry.strong_count() != 0);
            mounted_instances
                .iter()
                .filter_map(Weak::upgrade)
                .filter(|fs| !fs._mount_options.read_only)
                .collect()
        };
        let mut admission_rollback = DelallocAdmissionRollback::default();
        if !mount_options.read_only {
            for existing in &existing_writable {
                let was_open = existing.delalloc_admission_open();
                if was_open {
                    // Record before close/drain so every failure exit restores
                    // this instance, including an error from the drain itself.
                    admission_rollback.record(existing.clone());
                }
                existing.close_delalloc_admission();
                existing.drain_registered_delalloc()?;
            }
        }
        let delalloc_unique_writable = !mount_options.read_only && existing_writable.is_empty();
        // Writable mounts recover the journal and the validated legacy orphan
        // chain before this filesystem is published to the VFS.
        let prospective = (|| {
            let fs = if mount_options.read_only {
                another_ext4::Ext4::load_read_only_checked(mount_data.clone())?
            } else {
                another_ext4::Ext4::load_writable_with_options(
                    mount_data.clone(),
                    mount_options.write_barrier,
                )?
            };
            let root_attr = fs.getattr(another_ext4::EXT4_ROOT_INO)?;
            Ok::<_, SystemError>((fs, root_attr))
        })();
        let (fs, root_attr) = prospective?;
        let metadata_mutation_wait = Arc::new(Ext4MetadataMutationWait::new());
        fs.install_metadata_mutation_waker(metadata_mutation_wait.clone())?;
        let delalloc_mapper_authority = if !delalloc_unique_writable {
            None
        } else {
            fs.delalloc_append_mapper_authority().ok()
        };
        let root_inode: Arc<LockedExt4Inode> =
            Arc::new_cyclic(|self_ref: &Weak<LockedExt4Inode>| LockedExt4Inode {
                inner: Mutex::new(Ext4Inode::new_mount_root(
                    self_ref.clone(),
                    Ext4InodeTimes::from(&root_attr),
                )),
                io_lock: Mutex::new(()),
                metadata_commit_lock: Mutex::new(()),
                size_lock: RwSem::new(()),
                namespace_lock: Mutex::new(()),
                link_mutation_coordinator: vfs::LinkMutationCoordinator::new(),
                lifecycle: Ext4InodeLifecycle::new(),
                retention: vfs::InodeRetentionState::new(),
                pending_reclaim: SpinLock::new(None),
                eviction_scheduled: SpinLock::new(false),
                retention_callback_self: self_ref.clone(),
                eviction_filesystem: SpinLock::new(Weak::new()),
                delalloc_progress: super::inode::Ext4DelallocProgress::new(),
                delalloc_pool: Mutex::new(None),
            });

        let fs = Arc::new(Ext4FileSystem {
            writeback_domain: PageCacheWritebackDomain::new(),
            fs,
            raw_dev,
            _device_mount_holder: device_mount_holder,
            root_inode,
            dirty_inodes: Mutex::new(Vec::new()),
            inode_table: Mutex::new(BTreeMap::new()),
            _mount_domain: mount_domain.clone(),
            delalloc_mapper_authority,
            delalloc_inodes: Mutex::new(BTreeMap::new()),
            delalloc_wait: WaitQueue::default(),
            delalloc_admission_open: AtomicBool::new(true),
            delalloc_submit_lock: Mutex::new(()),
            metadata_mutation_wait,
            inode_reuse_barrier: RwSem::new(()),
            lifecycle_error: Mutex::new(None),
            quarantined_reclaims: SpinLock::new(Vec::new()),
            eviction_queue: SpinLock::new(Ext4EvictionQueueState::default()),
            eviction_wait: WaitQueue::default(),
            _mount_options: mount_options,
        });
        mounted_instances.push(Arc::downgrade(&fs));
        admission_rollback.commit();
        drop(mounted_instances);
        let mut stats_registry = EXT4_STATS_REGISTRY.lock();
        stats_registry.retain(|entry| entry.strong_count() != 0);
        stats_registry.push(Arc::downgrade(&fs));
        drop(stats_registry);

        let mut guard = fs.root_inode.inner.lock();
        guard.fs_ptr = Arc::downgrade(&fs);
        guard.cached_file_size = Some(root_attr.size);
        drop(guard);
        *fs.root_inode.eviction_filesystem.lock() = Arc::downgrade(&fs);
        fs.inode_table.lock().insert(
            another_ext4::EXT4_ROOT_INO,
            CanonicalInodeEntry {
                inode: Arc::downgrade(&fs.root_inode),
                lifecycle: fs.root_inode.lifecycle().clone(),
            },
        );

        Ok(fs)
    }
}

impl Drop for Ext4InodeTombstone {
    fn drop(&mut self) {
        if self.resolved {
            return;
        }
        if let Some(fs) = self.fs.upgrade() {
            let error = SystemError::EIO;
            fs.fail_stop_lifecycle();
            if fs
                .finish_tombstone(
                    self,
                    Ext4InodeLifecycleState::Poisoned(error.clone()),
                    false,
                )
                .is_err()
            {
                // The canonical table may itself be inconsistent. Always wake waiters on
                // the tombstone's original lifecycle before fail-stopping the filesystem.
                self.lifecycle
                    .set_state(Ext4InodeLifecycleState::Poisoned(error));
            }
            log::error!(
                "ext4: unresolved inode tombstone for inode {}, filesystem fail-stopped",
                self.inode_num
            );
        } else {
            self.lifecycle
                .set_state(Ext4InodeLifecycleState::Poisoned(SystemError::EIO));
        }
    }
}

impl Drop for Ext4FileSystem {
    fn drop(&mut self) {
        if self.delalloc_inodes.lock().is_empty() {
            return;
        }
        // The final Arc owner proves that no claimed submission remains:
        // every submission holds its own strong filesystem reference.  Idle
        // Prepared/Ready owners must nevertheless be detached explicitly
        // before their linear reservation fields are destroyed.
        self.fs.fail_stop_mutations();
        self.delalloc_admission_open.store(false, Ordering::Release);
        self.terminalize_idle_delalloc_after_fail_stop();
        debug_assert!(
            self.delalloc_inodes.lock().is_empty(),
            "last ext4 owner cannot retain a claimed delayed submission"
        );
    }
}

impl MountableFileSystem for Ext4FileSystem {
    fn make_fs_with_flags(
        data: Option<&dyn FileSystemMakerData>,
        mount_flags: MountFlags,
    ) -> Result<Arc<dyn FileSystem + 'static>, SystemError> {
        let mount_data = data
            .and_then(|d| d.as_any().downcast_ref::<Ext4MountData>())
            .ok_or(SystemError::EINVAL)?;
        let mut options = mount_data.options;
        options.read_only = mount_flags.contains(MountFlags::RDONLY);
        Self::from_gendisk_with_options(mount_data.gendisk.clone(), options)
    }

    fn make_fs(
        data: Option<&dyn FileSystemMakerData>,
    ) -> Result<Arc<dyn FileSystem + 'static>, SystemError> {
        let mount_data = data
            .and_then(|d| d.as_any().downcast_ref::<Ext4MountData>())
            .ok_or(SystemError::EINVAL)?;

        Self::from_gendisk_with_options(mount_data.gendisk.clone(), mount_data.options)
    }
    fn make_mount_data(
        raw_data: Option<&str>,
        source: &str,
    ) -> Result<Option<Arc<dyn FileSystemMakerData + 'static>>, SystemError> {
        let options = Ext4MountOptions::parse(raw_data)?;
        let mount_data = Ext4MountData::from_source(source, options).map_err(|e| {
            log::error!(
                "Failed to create Ext4 mount data from source '{}': {:?}",
                source,
                e
            );
            e
        })?;
        Ok(Some(Arc::new(mount_data)))
    }
}

register_mountable_fs!(Ext4FileSystem, EXT4FSMAKER, "ext4");

pub struct Ext4MountData {
    gendisk: Arc<GenDisk>,
    options: Ext4MountOptions,
}

impl FileSystemMakerData for Ext4MountData {
    fn as_any(&self) -> &dyn core::any::Any {
        self
    }
}

impl Ext4MountData {
    fn from_source(path: &str, options: Ext4MountOptions) -> Result<Self, SystemError> {
        let pcb = ProcessManager::current_pcb();
        let (current_node, rest_path) = user_path_at(&pcb, AtFlags::AT_FDCWD.bits(), path)?;
        let inode = current_node.lookup_follow_symlink(&rest_path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
        if !inode.metadata()?.file_type.eq(&vfs::FileType::BlockDevice) {
            return Err(SystemError::ENOTBLK);
        }

        let disk = inode.dname()?;

        if let Some(gendisk) = try_find_gendisk(disk.0.as_str()) {
            return Ok(Self { gendisk, options });
        }
        Err(SystemError::ENOENT)
    }
}

impl core::fmt::Debug for Ext4FileSystem {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "ext4")
    }
}

#[cfg(test)]
mod mount_options_tests {
    use super::{Ext4ErrorsBehavior, Ext4MountOptions};
    use system_error::SystemError;

    #[test]
    fn barrier_defaults_to_enabled() {
        assert!(Ext4MountOptions::parse(None).unwrap().write_barrier);
        assert!(Ext4MountOptions::parse(Some("")).unwrap().write_barrier);
    }

    #[test]
    fn barrier_aliases_and_last_value_win() {
        assert!(
            Ext4MountOptions::parse(Some("barrier=0,barrier"))
                .unwrap()
                .write_barrier
        );
        assert!(
            !Ext4MountOptions::parse(Some("barrier=1,nobarrier"))
                .unwrap()
                .write_barrier
        );
        assert!(
            !Ext4MountOptions::parse(Some("barrier, barrier=0,"))
                .unwrap()
                .write_barrier
        );
    }

    #[test]
    fn unknown_and_invalid_options_are_rejected() {
        assert!(Ext4MountOptions::parse(Some("discard")).is_err());
        assert!(Ext4MountOptions::parse(Some("barrier=2")).is_err());
        assert!(Ext4MountOptions::parse(Some("barrier=")).is_err());
        assert!(Ext4MountOptions::parse(Some("dax")).is_err());
        assert!(Ext4MountOptions::parse(Some("dax=invalid")).is_err());
        assert!(Ext4MountOptions::parse(Some("errors=invalid")).is_err());
    }

    #[test]
    fn existing_read_only_rootflags_are_supported() {
        let options = Ext4MountOptions::parse(Some("errors=remount-ro")).unwrap();
        assert_eq!(options.errors, Ext4ErrorsBehavior::RemountRo);
        assert!(options.write_barrier);

        let mut read_only = options;
        read_only.read_only = true;
        assert!(read_only.validate_error_behavior().is_ok());
        assert_eq!(
            options.validate_error_behavior(),
            Err(SystemError::EOPNOTSUPP_OR_ENOTSUP)
        );
    }

    #[test]
    fn error_modes_are_last_value_wins() {
        let options = Ext4MountOptions::parse(Some("errors=panic,errors=continue")).unwrap();
        assert_eq!(options.errors, Ext4ErrorsBehavior::Continue);
        assert!(options.validate_error_behavior().is_ok());
    }

    #[test]
    fn unsupported_panic_behavior_is_rejected() {
        let mut options = Ext4MountOptions::parse(Some("errors=panic")).unwrap();
        assert_eq!(
            options.validate_error_behavior(),
            Err(SystemError::EOPNOTSUPP_OR_ENOTSUP)
        );
        options.read_only = true;
        assert_eq!(
            options.validate_error_behavior(),
            Err(SystemError::EOPNOTSUPP_OR_ENOTSUP)
        );
    }
}
