Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion kernel/src/driver/base/block/gendisk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
driver::{base::device::device_number::DeviceNumber, block::loop_device::LoopDevice},
filesystem::{
devfs::{DevFS, DeviceINode, LockedDevFSInode},
vfs::{utils::DName, IndexNode, InodeMode, Metadata},
vfs::{utils::DName, FilePrivateData, FileType, IndexNode, InodeMode, Metadata},
},
libs::{mutex::MutexGuard, rwlock::RwLock},
};
Expand Down Expand Up @@ -219,6 +219,18 @@ impl IndexNode for GenDisk {
self
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::BlockDevice
}

fn sync_file(
&self,
_datasync: bool,
_data: MutexGuard<FilePrivateData>,
) -> Result<(), SystemError> {
self.sync()
}

fn read_at(
&self,
offset: usize,
Expand Down
14 changes: 14 additions & 0 deletions kernel/src/driver/block/loop_device/loop_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,18 @@ impl IndexNode for LoopDevice {
self
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::BlockDevice
}

fn sync_file(
&self,
_datasync: bool,
_data: MutexGuard<FilePrivateData>,
) -> Result<(), SystemError> {
<Self as BlockDevice>::sync(self)
Comment thread
donjuanplatinum marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Retain and fsync the backing File, not only its inode

LOOP_SET_FD currently stores only file.inode(), so after the setup fd is
closed the loop device has lost the backing open-file description and its
FilePrivateData. This is not sufficient for FUSE (file handle and open state),
OverlayFS (the active per-open backing file), credentials, or the file-level
writeback error cursor.

The new sync_file() activates BlockDevice::sync(), which calls inode.sync()
followed by inode.fs().sync_fs(true). That can either miss the actual backing
open file or expand one loop flush into a whole-filesystem sync with unrelated
writeback and duplicate device flushes. Linux loop retains lo_backing_file and
lo_req_flush() calls vfs_fsync() on that File.

Please retain an Arc for the backing object, use its file-level sync
path, and release or replace it only after in-flight I/O is drained. The new
IoGuard is useful for that transition and should remain, but it cannot recover
the open-file context once only an inode was stored. The read/write paths
should use the same retained FilePrivateData as well.

}

fn read_at(
&self,
offset: usize,
Expand Down Expand Up @@ -1330,6 +1342,8 @@ impl BlockDevice for LoopDevice {
}

fn sync(&self) -> Result<(), SystemError> {
let _io_guard = IoGuard::new(self)?;

let inode = self.inner().file_inode.clone().ok_or(SystemError::ENODEV)?;
inode.sync()?;
inode.fs().sync_fs(true)
Expand Down
17 changes: 16 additions & 1 deletion kernel/src/driver/block/pmem/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ use crate::{
filesystem::{
devfs::{DevFS, DeviceINode, LockedDevFSInode},
kernfs::KernFSInode,
vfs::{utils::DName, FilePrivateData, IndexNode, InodeFlags, InodeId, InodeMode, Metadata},
vfs::{
utils::DName, FilePrivateData, FileType, IndexNode, InodeFlags, InodeId, InodeMode,
Metadata,
},
},
libs::{
align::{page_align_down, page_align_up},
Expand Down Expand Up @@ -222,6 +225,18 @@ impl IndexNode for PmemBlockDevice {
self
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::BlockDevice
}

fn sync_file(
&self,
_datasync: bool,
_data: MutexGuard<FilePrivateData>,
) -> Result<(), SystemError> {
<Self as BlockDevice>::sync(self)
}

fn read_at(
&self,
_offset: usize,
Expand Down
15 changes: 14 additions & 1 deletion kernel/src/driver/block/virtio_blk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use crate::{
devfs::{DevFS, DeviceINode, LockedDevFSInode},
kernfs::KernFSInode,
mbr::MbrDiskPartionTable,
vfs::{utils::DName, IndexNode, InodeMode, Metadata},
vfs::{utils::DName, FilePrivateData, FileType, IndexNode, InodeMode, Metadata},
},
init::initcall::INITCALL_POSTCORE,
libs::{
Expand Down Expand Up @@ -849,6 +849,19 @@ impl IndexNode for VirtIOBlkDevice {
fn as_any_ref(&self) -> &dyn core::any::Any {
self
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::BlockDevice
}

fn sync_file(
&self,
_datasync: bool,
_data: MutexGuard<FilePrivateData>,
) -> Result<(), SystemError> {
<Self as BlockDevice>::sync(self)
}

fn read_at(
&self,
_offset: usize,
Expand Down
4 changes: 4 additions & 0 deletions kernel/src/filesystem/ext4/inode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ impl IndexNode for LockedExt4Inode {
Some(self.fs())
}

fn supports_post_write_sync(&self, file_type: vfs::FileType) -> bool {
file_type == vfs::FileType::File
}

fn retention_state(&self) -> Option<&InodeRetentionState> {
Some(&self.retention)
}
Expand Down
4 changes: 4 additions & 0 deletions kernel/src/filesystem/fuse/inode/vfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ impl IndexNode for FuseNode {
self.try_fs()
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::File
}

fn as_any_ref(&self) -> &dyn core::any::Any {
self
}
Expand Down
4 changes: 4 additions & 0 deletions kernel/src/filesystem/ramfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ impl IndexNode for LockedRamFSInode {
Some(self.fs())
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
file_type == FileType::File
}

fn mmap(&self, _start: usize, _len: usize, _offset: usize) -> Result<(), SystemError> {
Ok(())
}
Expand Down
79 changes: 48 additions & 31 deletions kernel/src/filesystem/vfs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use system_error::SystemError;
use super::{
append_lock::{with_inode_append_lock, AppendLockKey},
inode_lifecycle::{InodeRetentionGuard, InodeRetentionKind},
mount::{MountExternalGuard, MountFSInode},
mount::{MountExternalGuard, MountFSInode, MountFlags},
utils::should_remove_sgid,
DirectoryEntry, FileSystem, FileType, IndexNode, InodeId, Metadata, SetMetadataMask,
SpecialNodeData,
Expand Down Expand Up @@ -205,10 +205,6 @@ struct WriteConfig {
update_offset: bool,
/// 偏移量更新方式
offset_update: OffsetUpdate,
/// 文件标志
flags: FileFlags,
/// inode 标志
inode_flags: InodeFlags,
}
/// Namespace fd backing data, typically created from /proc/thread-self/ns/* files.
#[derive(Clone)]
Expand Down Expand Up @@ -826,29 +822,49 @@ impl File {
Ok(len.min(limit.saturating_sub(offset)))
}

fn combined_mount_flags(&self) -> MountFlags {
match self.inode.clone().downcast_arc::<MountFSInode>() {
Some(mnt_inode) => mnt_inode.mount_fs().combined_flags(),
None => self.inode.mount_flags(),
}
}

fn maybe_sync_after_write(
&self,
file_type: FileType,
start: usize,
written_len: usize,
flags: FileFlags,
inode_flags: InodeFlags,
) -> Result<(), SystemError> {
if written_len == 0 {
return Ok(());
}

// O_SYNC 包含 O_DSYNC 位,所以只需检查 O_DSYNC 即可判断是否需要数据同步
let need_data_sync = flags.contains(FileFlags::O_DSYNC);
// 检查是否需要元数据同步(O_SYNC = __O_SYNC | O_DSYNC)
let need_metadata_sync = flags.contains(FileFlags::__O_SYNC);

// inode 级别的 S_SYNC 标志
let inode_sync = inode_flags.contains(InodeFlags::S_SYNC);
// Linux IS_SYNC(inode) 同时包含 inode S_SYNC 与 superblock/mount sync 语义。
let mount_sync = self
.combined_mount_flags()
.contains(MountFlags::SYNCHRONOUS);

if need_data_sync || inode_sync {
if need_metadata_sync || inode_sync {
// O_SYNC 或 S_SYNC: 完整同步(数据 + 元数据)
self.inode.sync_file(false, self.private_data.lock())?;
} else {
// O_DSYNC: 仅数据同步
self.inode.sync_file(true, self.private_data.lock())?;
}
if !(need_data_sync || inode_sync || mount_sync) {
return Ok(());
}
Ok(())

if !self.inode.supports_post_write_sync(file_type) {
return Ok(());
}

let end = start.saturating_add(written_len).saturating_sub(1);
// Linux generic_write_sync() 只有 IOCB_SYNC 才请求完整 metadata sync;
// O_DSYNC、S_SYNC 与 SB_SYNCHRONOUS 都按 datasync 处理。
self.sync_range_and_check_wb_error(start, end, !need_metadata_sync)
}

#[inline(never)]
Expand Down Expand Up @@ -919,7 +935,6 @@ impl File {
}
}

self.maybe_sync_after_write(config.flags, config.inode_flags)?;
Ok(written_len)
}
/// @brief 创建一个新的文件对象
Expand Down Expand Up @@ -1484,31 +1499,32 @@ impl File {
WriteConfig {
update_offset,
offset_update: OffsetUpdate::StoreEnd,
flags,
inode_flags,
},
)
.map(|written_len| (actual_offset, written_len))
};
return match self.append_lock_domain.as_ref() {
let (actual_offset, written_len) = match self.append_lock_domain.as_ref() {
Some(domain) => with_inode_append_lock(domain.key(&md), append_write),
None => append_write(),
};
}?;
self.maybe_sync_after_write(file_type, actual_offset, written_len, flags, inode_flags)?;
return Ok(written_len);
}

let actual_offset = offset;
let actual_len = self.limit_write_len_by_fsize(file_type, actual_offset, len)?;

self.write_at_and_finalize(
let written_len = self.write_at_and_finalize(
actual_offset,
actual_len,
buf,
WriteConfig {
update_offset,
offset_update: OffsetUpdate::Add,
flags,
inode_flags,
},
)
)?;
self.maybe_sync_after_write(file_type, actual_offset, written_len, flags, inode_flags)?;
Ok(written_len)
}

pub fn do_write_user(
Expand Down Expand Up @@ -1553,31 +1569,32 @@ impl File {
WriteConfig {
update_offset,
offset_update: OffsetUpdate::StoreEnd,
flags,
inode_flags,
},
)
.map(|written_len| (actual_offset, written_len))
};
return match self.append_lock_domain.as_ref() {
let (actual_offset, written_len) = match self.append_lock_domain.as_ref() {
Some(domain) => with_inode_append_lock(domain.key(&md), append_write),
None => append_write(),
};
}?;
self.maybe_sync_after_write(file_type, actual_offset, written_len, flags, inode_flags)?;
return Ok(written_len);
}

let actual_offset = offset;
let actual_len = self.limit_write_len_by_fsize(file_type, actual_offset, len)?;

self.write_user_at_and_finalize(
let written_len = self.write_user_at_and_finalize(
actual_offset,
actual_len,
reader,
WriteConfig {
update_offset,
offset_update: OffsetUpdate::Add,
flags,
inode_flags,
},
)
)?;
self.maybe_sync_after_write(file_type, actual_offset, written_len, flags, inode_flags)?;
Ok(written_len)
}

/// Write to the file from a userspace buffer.
Expand Down
9 changes: 9 additions & 0 deletions kernel/src/filesystem/vfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,15 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync {
/// protocol flags.
fn adjust_file_mode_after_open(&self, _data: &FilePrivateData, _mode: &mut FileMode) {}

/// Whether the VFS write path should apply Linux `generic_write_sync()`
/// semantics after a successful positive-length write.
///
/// This is intentionally opt-in: many pseudo files are reported as regular
/// files but their Linux write paths do not perform generic write syncing.
fn supports_post_write_sync(&self, _file_type: FileType) -> bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Model this as a write-operation policy, not a default-false inode capability

Linux does not decide whether to run generic_write_sync() from an inode
property. The concrete file_operations.write_iter either invokes the helper,
forwards IOCB_SYNC/IOCB_DSYNC to another File, or implements unrelated write
semantics.

Making this a default-false IndexNode method creates a silent durability
failure mode: every new disk filesystem and every transparent wrapper must
remember to opt in or forward this method, otherwise O_SYNC/O_DSYNC writes
still return success without syncing. The OverlayFS and tmpfs overrides in
this patch already show that the boolean is also too weak to distinguish
"generic sync", "handled by the delegated write", and "no generic sync".

Please make the selected open-file/write operation own this decision. A
bounded intermediate design would cache an explicit per-open policy such as
Generic / HandledByWrite / None in File. That avoids a full VFS rewrite while
making the responsibility explicit and removing the per-write wrapper
dispatch.

false
}

/// @brief 关闭文件
///
/// @return 成功:Ok()
Expand Down
4 changes: 4 additions & 0 deletions kernel/src/filesystem/vfs/mount/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3611,6 +3611,10 @@ impl IndexNode for MountFSInode {
self.dentry.inode.adjust_file_mode_after_open(data, mode)
}

fn supports_post_write_sync(&self, file_type: FileType) -> bool {
self.dentry.inode.supports_post_write_sync(file_type)
}

fn mmap(&self, start: usize, len: usize, offset: usize) -> Result<(), SystemError> {
return self.dentry.inode.mmap(start, len, offset);
}
Expand Down
Loading