Skip to content

Commit e683b20

Browse files
authored
fix(namespace): pass mount and pivot_root conformance (#2137)
* fix(namespace): pass mount and pivot_root conformance Align mount namespaces, propagation, pivot_root, proc mount topology, VFS permissions, and tmpfs accounting with Linux 6.6 semantics. Make current-task publication and x86 fast CPU identification safe across context switches and AP startup, and harden page-cache allocation and rollback paths. Add focused dunit and gVisor coverage for mount, pivot_root, dangling symlinks, tmpfs quota, and propagation. Make both runners fail closed on incomplete XML, unexpected skips, timeouts, and stale results while always publishing CI artifacts. Validated with kernel builds, runner unit tests and linting, slab allocator tests, QEMU boot, the full dunit suite, focused namespace tests, and gVisor mount/pivot_root tests. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): address review and CI regressions Make atime updates field-specific and synchronized in mutable filesystems, serialize FUSE and ext4 metadata updates, enforce O_NOATIME ownership, and cover directory access semantics. Compute tmpfs statfs data from atomic page accounting without taking a superblock write lock on every page operation. Preserve slab allocator soundness without clearing an entire object page. Restore the origin/master current-task implementation that predates the wait/epoll CI regressions, reject invalid open flag combinations, read complete symlink targets, and decouple existing mount topology traversal from mount-max admission limits. Add dunit coverage for metadata races, atime flags, tmpfs quota reporting, long symlinks, open errors, and lowered mount limits. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): skip atime updates for anonymous inodes Use the optional filesystem lookup before checking mount state during access-time updates. Anonymous kernel objects such as sockets now report that they are not backed by a filesystem, matching Linux socket read semantics and avoiding a panic in readv and splice paths.\n\nDocument the expanded try_fs contract so callers can distinguish filesystem-backed inodes from anonymous objects without relying on a panicking fs implementation.\n\nThis fixes the gVisor TCP splice timeout and UDP writev/readv failure observed in the integration workflow. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): address follow-up review findings Preserve Linux open error precedence by applying O_NOATIME ownership checks after path, type, and DAC validation while keeping them ahead of direct-I/O and socket-open failures. Add regression coverage for final symlinks and directory O_DIRECT combinations.\n\nReuse one fallibly allocated PATH_MAX buffer across a symlink walk to avoid per-hop allocation and clearing.\n\nEnsure the gVisor runner terminates the full test process group and reaps its leader when try_wait fails, sharing the same cleanup path used for timeouts. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): address latest review and integration failures Initialize new inode ownership before publication across tmpfs, ramfs, and ext4, including mknod and rename whiteouts. Preserve setgid directory inheritance while stripping unauthorized executable setgid modes. Harden transactional page-cache rollback against concurrent dirty publication, update regular-file atime on EOF reads, and avoid filesystem lookups for anonymous socket permission checks. Disable required-test manifest enforcement completely when requested and add regression coverage for ownership, SGID, EOF atime, and runner behavior. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): preserve bootstrap inode creation Use the initial root credential for kernel-owned filesystem objects created before ProcessManager installs a current task. Keep setgid directory inheritance intact while avoiding an early-boot current_pcb spin during procfs initialization. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): preserve fallocate metadata and defer atime writes Apply Linux-compatible write-side metadata effects after successful tmpfs fallocate allocation, including timestamp updates and setid removal, while preserving rollback atomicity. Cache ext4 atime updates in memory, track them through the existing dirty inode queue, and commit them with size and mtime during periodic or explicit metadata writeback instead of synchronously writing from the read path. Add dunitest coverage for fallocate metadata effects, failure stability, setid clearing, and ext4 atime visibility and persistence. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): address symlink and ext4 timestamp review Enforce Linux symlink creation semantics in the common syscall path, including empty targets, trailing slashes, parent search/write permission ordering, immutable directories, and an atomic tmpfs permission recheck under the parent inode lock. Keep ext4 inode timestamps authoritative in memory and evaluate relatime without io_lock or disk getattr. Add masked setattr support and generation-based atime/mtime writeback commits so concurrent reads, writes, mmap faults, setters, resize, and writeback cannot lose dirty timestamp updates. Add dunitest coverage for symlink error and permission semantics plus ext4 relatime behavior. Validated with make kernel and the full DragonOS dunitest suite (797 tests, 0 failures). Signed-off-by: longjin <longjin@dragonos.org> * ci(test): align job and guest timeout budgets Raise the syscall workflow timeout to cover its 50-minute guest monitor after the build and disk preparation phases, while preserving time for cleanup and diagnostic uploads. Apply the same outer-timeout invariant to dunitest so its 30-minute monitor can terminate QEMU and publish the serial artifact before the Actions job deadline. Signed-off-by: longjin <longjin@dragonos.org> * fix(ext4): publish created inode without reread Return authoritative attributes from the in-memory inode used by create, mkdir, and mknod transactions, while preserving the existing inode-number APIs. Initialize canonical VFS inodes from those attributes for regular creation, special nodes, and rename whiteouts. This removes the post-link getattr failure window that could report EIO after the directory entry was already committed. Keep lookup-time getattr behavior unchanged and add coverage for complete in-memory FileAttr conversion. Validated with make kernel, all 130 another_ext4 tests, and the full DragonOS dunitest suite (797 tests, 0 failures). Signed-off-by: longjin <longjin@dragonos.org> * fix(namespace): stabilize propagation conformance checks Replace the scheduler-dependent recursive propagation observer with explicit shared/private phase acknowledgements and a midpoint stress snapshot handshake. Preserve the two unsynchronized 512-transition halves, namespace copying, and concurrent topology mutation while reporting the exact failing child status. Allocate a fresh conceptual hidden-parent mount ID when copying an attached namespace so mountinfo never exposes the source namespace's parent identity. Keep ordinary overlay traversal single-pass for the common shallow path and defer cycle detection to pathological depths without imposing a semantic limit. Require the recursive-bind topology and mount-limit rollback suites to remain skip-free alongside the existing namespace conformance binaries. Validated with make kernel, 12 dunit runner tests, the full DragonOS dunit suite, and 100-run focused stress loops on Linux and in the DragonOS guest. Signed-off-by: longjin <longjin@dragonos.org> * fix(namespace): address propagation review findings Signed-off-by: longjin <longjin@dragonos.org> * fix(mm): preserve zero-length mmap error priority Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent 2b5fdee commit e683b20

67 files changed

Lines changed: 4364 additions & 795 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dunitest.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ jobs:
1717
dunitest:
1818
name: Dunitest
1919
runs-on: ubuntu-latest
20-
timeout-minutes: 30
20+
# Allow the 30-minute guest test budget plus build, cleanup, and diagnostics.
21+
timeout-minutes: 50
2122
container:
2223
image: dragonos/dragonos-dev:v1.23
2324
options: --privileged -v /dev:/dev
@@ -41,3 +42,11 @@ jobs:
4142
DISK_SAVE_MODE: "1"
4243
run: |
4344
make test-dunit
45+
46+
- name: Preserve dunitest serial log
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: dunitest-serial-x86_64-${{ github.run_id }}-${{ github.run_attempt }}
51+
path: serial_opt.txt
52+
if-no-files-found: error

.github/workflows/test-x86.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ jobs:
1818
integration-test:
1919
name: Integration Test
2020
runs-on: ubuntu-latest
21-
timeout-minutes: 60
21+
# Allow the 50-minute guest test budget plus build, cleanup, and diagnostics.
22+
timeout-minutes: 75
2223
container:
2324
image: dragonos/dragonos-dev:v1.23
2425
options: --privileged -v /dev:/dev
@@ -42,6 +43,14 @@ jobs:
4243
run: |
4344
make test-syscall
4445
46+
- name: Preserve syscall serial log
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: gvisor-serial-x86_64-${{ github.run_id }}-${{ github.run_attempt }}
51+
path: serial_opt.txt
52+
if-no-files-found: error
53+
4554
- name: Upload test results
4655
if: always() && github.repository == 'DragonOS-Community/DragonOS'
4756
shell: bash -ileo pipefail {0}

kernel/crates/another_ext4/src/ext4/alloc.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,14 @@ impl Ext4 {
400400
self.transaction_stage_super_block(transaction, &sb)
401401
}
402402

403-
/// Create a new inode, returning the inode and its number
403+
/// Create a new inode with its final owner, returning the inode and its number.
404404
#[inline(never)]
405-
pub(super) fn create_inode(&self, mode: InodeMode) -> Result<InodeRef> {
405+
pub(super) fn create_inode_with_owner(
406+
&self,
407+
mode: InodeMode,
408+
uid: u32,
409+
gid: u32,
410+
) -> Result<InodeRef> {
406411
self.ensure_mutable()?;
407412
// Allocate an inode
408413
let is_dir = mode.file_type() == FileType::Directory;
@@ -413,6 +418,8 @@ impl Ext4 {
413418
let mut inode = Box::new(Inode::default());
414419
inode.set_generation(generation);
415420
inode.set_mode(mode);
421+
inode.set_uid(uid);
422+
inode.set_gid(gid);
416423
inode.extent_init();
417424
let mut inode_ref = InodeRef::new(id, inode);
418425
self.write_inode_with_csum(&mut inode_ref)?;
@@ -443,6 +450,8 @@ impl Ext4 {
443450
mode: InodeMode,
444451
major: u32,
445452
minor: u32,
453+
uid: u32,
454+
gid: u32,
446455
) -> Result<InodeRef> {
447456
self.ensure_mutable()?;
448457
// Device nodes are never directories
@@ -453,6 +462,8 @@ impl Ext4 {
453462
let mut inode = Box::new(Inode::default());
454463
inode.set_generation(generation);
455464
inode.set_mode(mode);
465+
inode.set_uid(uid);
466+
inode.set_gid(gid);
456467
inode.set_device(major, minor);
457468
let mut inode_ref = InodeRef::new(id, inode);
458469
self.write_inode_with_csum(&mut inode_ref)?;

kernel/crates/another_ext4/src/ext4/low_level.rs

Lines changed: 140 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ pub struct SetAttr {
4848
pub crtime: Option<u32>,
4949
}
5050

51+
#[derive(Clone, Copy)]
52+
pub struct InodeOwner {
53+
pub uid: u32,
54+
pub gid: u32,
55+
}
56+
5157
impl SetAttr {
5258
/// Create a new SetAttr struct with all fields set to None.
5359
pub fn new() -> Self {
@@ -270,15 +276,19 @@ impl Ext4 {
270276
return_error!(ErrCode::EINVAL, "Invalid inode {}", id);
271277
}
272278

279+
Ok(Self::file_attr(&inode))
280+
}
281+
282+
fn file_attr(inode: &InodeRef) -> FileAttr {
273283
// Get device number for device nodes
274284
let rdev = if inode.inode.is_device() {
275285
inode.inode.device()
276286
} else {
277287
(0, 0)
278288
};
279289

280-
Ok(FileAttr {
281-
ino: id,
290+
FileAttr {
291+
ino: inode.id,
282292
size: inode.inode.size(),
283293
blocks: inode.inode.block_count(),
284294
atime: inode.inode.atime(),
@@ -291,7 +301,7 @@ impl Ext4 {
291301
uid: inode.inode.uid(),
292302
gid: inode.inode.gid(),
293303
rdev,
294-
})
304+
}
295305
}
296306

297307
/// Set file attributes.
@@ -486,6 +496,7 @@ impl Ext4 {
486496
&self,
487497
id: InodeId,
488498
size: Option<u64>,
499+
atime: Option<u32>,
489500
mtime: Option<u32>,
490501
) -> Result<()> {
491502
self.ensure_mutable()?;
@@ -498,6 +509,9 @@ impl Ext4 {
498509
if let Some(size) = size {
499510
inode.inode.set_size(size);
500511
}
512+
if let Some(atime) = atime {
513+
inode.inode.set_atime(atime);
514+
}
501515
if let Some(mtime) = mtime {
502516
inode.inode.set_mtime(mtime);
503517
}
@@ -510,7 +524,7 @@ impl Ext4 {
510524
///
511525
/// Call this after successful page-cache write to finalise the new file size.
512526
pub fn commit_inode_size(&self, id: InodeId, size: u64, mtime: Option<u32>) -> Result<()> {
513-
self.commit_inode_metadata(id, Some(size), mtime)
527+
self.commit_inode_metadata(id, Some(size), None, mtime)
514528
}
515529

516530
/// Link a newly created inode into `parent`.
@@ -557,6 +571,30 @@ impl Ext4 {
557571
/// * `ENOTDIR` - `parent` is not a directory
558572
/// * `ENOSPC` - No space left on device
559573
pub fn create(&self, parent: InodeId, name: &str, mode: InodeMode) -> Result<InodeId> {
574+
self.create_with_owner(parent, name, mode, InodeOwner { uid: 0, gid: 0 })
575+
}
576+
577+
pub fn create_with_owner(
578+
&self,
579+
parent: InodeId,
580+
name: &str,
581+
mode: InodeMode,
582+
owner: InodeOwner,
583+
) -> Result<InodeId> {
584+
Ok(self
585+
.create_with_owner_and_attr(parent, name, mode, owner)?
586+
.ino)
587+
}
588+
589+
/// Create and link a file, returning the attributes from the authoritative
590+
/// in-memory inode used by the namespace transaction.
591+
pub fn create_with_owner_and_attr(
592+
&self,
593+
parent: InodeId,
594+
name: &str,
595+
mode: InodeMode,
596+
owner: InodeOwner,
597+
) -> Result<FileAttr> {
560598
self.ensure_mutable()?;
561599
let _metadata_guard = self.lock_direct_metadata_mutation()?;
562600
let _namespace_guard = self.namespace_lock.lock();
@@ -567,10 +605,9 @@ impl Ext4 {
567605
return_error!(ErrCode::ENOTDIR, "Inode {} is not a directory", parent.id);
568606
}
569607
// Create child inode and link it to parent directory
570-
let mut child = self.create_inode(mode)?;
608+
let mut child = self.create_inode_with_owner(mode, owner.uid, owner.gid)?;
571609
self.link_new_inode_or_free(&mut parent, &mut child, name)?;
572-
// Create file handler
573-
Ok(child.id)
610+
Ok(Self::file_attr(&child))
574611
}
575612

576613
/// Create a device node (character or block device).
@@ -603,6 +640,41 @@ impl Ext4 {
603640
major: u32,
604641
minor: u32,
605642
) -> Result<InodeId> {
643+
self.mknod_with_owner(
644+
parent,
645+
name,
646+
mode,
647+
major,
648+
minor,
649+
InodeOwner { uid: 0, gid: 0 },
650+
)
651+
}
652+
653+
pub fn mknod_with_owner(
654+
&self,
655+
parent: InodeId,
656+
name: &str,
657+
mode: InodeMode,
658+
major: u32,
659+
minor: u32,
660+
owner: InodeOwner,
661+
) -> Result<InodeId> {
662+
Ok(self
663+
.mknod_with_owner_and_attr(parent, name, mode, major, minor, owner)?
664+
.ino)
665+
}
666+
667+
/// Create and link a device node, returning the attributes from the
668+
/// authoritative in-memory inode used by the namespace transaction.
669+
pub fn mknod_with_owner_and_attr(
670+
&self,
671+
parent: InodeId,
672+
name: &str,
673+
mode: InodeMode,
674+
major: u32,
675+
minor: u32,
676+
owner: InodeOwner,
677+
) -> Result<FileAttr> {
606678
self.ensure_mutable()?;
607679
let _metadata_guard = self.lock_direct_metadata_mutation()?;
608680
let _namespace_guard = self.namespace_lock.lock();
@@ -619,13 +691,13 @@ impl Ext4 {
619691
}
620692

621693
// Create device inode (uses create_device_inode which sets device number)
622-
let mut child = self.create_device_inode(mode, major, minor)?;
694+
let mut child = self.create_device_inode(mode, major, minor, owner.uid, owner.gid)?;
623695

624696
// Link to parent directory
625697
self.link_new_inode_or_free(&mut parent_ref, &mut child, name)?;
626698

627699
trace!("mknod {} ({}:{}) -> inode {}", name, major, minor, child.id);
628-
Ok(child.id)
700+
Ok(Self::file_attr(&child))
629701
}
630702

631703
/// Read data from a file. This function will read exactly `buf.len()`
@@ -1404,6 +1476,30 @@ impl Ext4 {
14041476
/// * `ENOTDIR` - `parent` is not a directory
14051477
/// * `ENOSPC` - no space left on device
14061478
pub fn mkdir(&self, parent: InodeId, name: &str, mode: InodeMode) -> Result<InodeId> {
1479+
self.mkdir_with_owner(parent, name, mode, InodeOwner { uid: 0, gid: 0 })
1480+
}
1481+
1482+
pub fn mkdir_with_owner(
1483+
&self,
1484+
parent: InodeId,
1485+
name: &str,
1486+
mode: InodeMode,
1487+
owner: InodeOwner,
1488+
) -> Result<InodeId> {
1489+
Ok(self
1490+
.mkdir_with_owner_and_attr(parent, name, mode, owner)?
1491+
.ino)
1492+
}
1493+
1494+
/// Create and link a directory, returning the attributes from the
1495+
/// authoritative in-memory inode used by the namespace transaction.
1496+
pub fn mkdir_with_owner_and_attr(
1497+
&self,
1498+
parent: InodeId,
1499+
name: &str,
1500+
mode: InodeMode,
1501+
owner: InodeOwner,
1502+
) -> Result<FileAttr> {
14071503
self.ensure_mutable()?;
14081504
let _metadata_guard = self.lock_direct_metadata_mutation()?;
14091505
let _namespace_guard = self.namespace_lock.lock();
@@ -1415,7 +1511,7 @@ impl Ext4 {
14151511
}
14161512
// Create file/directory
14171513
let mode = mode & InodeMode::PERM_MASK | InodeMode::DIRECTORY;
1418-
let mut child = self.create_inode(mode)?;
1514+
let mut child = self.create_inode_with_owner(mode, owner.uid, owner.gid)?;
14191515
// Add "." entry
14201516
let child_self = child.clone();
14211517
if let Err(error) = self.dir_add_entry(&mut child, &child_self, ".") {
@@ -1427,7 +1523,7 @@ impl Ext4 {
14271523
child.inode.set_link_count(1);
14281524
// Link the new inode
14291525
self.link_new_inode_or_free(&mut parent, &mut child, name)?;
1430-
Ok(child.id)
1526+
Ok(Self::file_attr(&child))
14311527
}
14321528

14331529
/// Look up a directory entry by name.
@@ -1705,6 +1801,39 @@ mod tests {
17051801
use crate::FileType;
17061802
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
17071803

1804+
#[test]
1805+
fn file_attr_is_derived_from_the_authoritative_in_memory_inode() {
1806+
let mut inode = Box::new(Inode::default());
1807+
inode.set_mode(InodeMode::CHARDEV | InodeMode::from_bits_retain(0o640));
1808+
inode.set_uid(0x12345);
1809+
inode.set_gid(0x23456);
1810+
inode.set_size(0x1_0000_0020);
1811+
inode.set_block_count(17);
1812+
inode.set_atime(11);
1813+
inode.set_mtime(12);
1814+
inode.set_ctime(13);
1815+
inode.set_crtime(14);
1816+
inode.set_link_count(2);
1817+
inode.set_device(259, 0x1_0002);
1818+
let inode = InodeRef::new(42, inode);
1819+
1820+
let attr = Ext4::file_attr(&inode);
1821+
1822+
assert_eq!(attr.ino, 42);
1823+
assert_eq!(attr.ftype, FileType::CharacterDev);
1824+
assert_eq!(attr.perm.bits(), 0o640);
1825+
assert_eq!(attr.uid, 0x12345);
1826+
assert_eq!(attr.gid, 0x23456);
1827+
assert_eq!(attr.size, 0x1_0000_0020);
1828+
assert_eq!(attr.blocks, 17);
1829+
assert_eq!(
1830+
(attr.atime, attr.mtime, attr.ctime, attr.crtime),
1831+
(11, 12, 13, 14)
1832+
);
1833+
assert_eq!(attr.links, 2);
1834+
assert_eq!(attr.rdev, (259, 0x1_0002));
1835+
}
1836+
17081837
struct StubBlockDevice {
17091838
sb_block: Block,
17101839
}

kernel/crates/another_ext4/src/ext4/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod orphan;
1717
mod rw;
1818
mod xattr_reclaim;
1919

20-
pub use low_level::SetAttr;
20+
pub use low_level::{InodeOwner, SetAttr};
2121

2222
/// Simple fixed-size inode cache.
2323
/// When full, the entire cache is cleared (simple but effective for common workloads).

kernel/crates/another_ext4/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod prelude;
1414

1515
pub use constants::{BLOCK_SIZE, EXT4_ROOT_INO, INODE_BLOCK_SIZE};
1616
pub use error::{ErrCode, Ext4Error};
17-
pub use ext4::{Ext4, SetAttr};
17+
pub use ext4::{Ext4, InodeOwner, SetAttr};
1818
pub use ext4_defs::{
1919
Block, BlockDevice, DirEntry, FileAttr, FileType, Inode, InodeMode, InodeReclaimError,
2020
InodeReclaimHandle, InodeRef,

0 commit comments

Comments
 (0)