Conversation
Stat::mtime is documented and stamped as nanoseconds since boot, DATA stores that number across reboots, the FAT adapter answers local Unix seconds instead, and std and libc read both as time since the epoch. A build tool comparing mtimes is misled by every reboot. Filed so the fix that follows closes it by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner approved this ABI change for the self-hosting track: ninja and make decide rebuilds by mtime, and an mtime since boot made every reboot reorder the tree. Stat::mtime is now nanoseconds since the Unix epoch in UTC, off the wall clock at the write, and never a time since boot. The layout is unchanged (a u64); the meaning is the kernel's to keep: - kernel/src/clock.rs: utc_nanos is the RTC's whole-second anchor carried on by the counter, and utc_secs (SYS_CLOCK_EPOCH) is now its whole seconds, so a stamp taken after the epoch syscall answered s is at least s seconds - the two can never disagree about a second. mtime_now is what a write stamps: utc_nanos, or on a machine whose RTC never answered, a clock that starts at the epoch at boot, so one boot's stamps still order. local_of_utc and utc_of_local convert for a format that stores local time. - object/ops.rs, vfs.rs and the two self-tests stamp with mtime_now where they stamped nanos_since_boot. - fat32_adapter.rs stamps the mtime the VFS hands it (the write's instant, not the flush's) as local seconds, and answers file_mtime as UTC nanoseconds; it used to answer local seconds, a different unit from every other mount. FAT keeps two seconds of local time, which the ABI doc now says, and toyos-fat32 gains a host test that a write time drops the odd second. - bcachefs and tmpfs store the u64 as given, so DATA keeps nanoseconds across a reboot; ROOT's image stamps 0, as src/image.rs always did. - libc's struct stat gains POSIX's st_atim/st_mtim/st_ctim, with st_mtime as the macro POSIX defines; fstat put nanoseconds into st_mtime, which is seconds. - std needs no change: the fork's Metadata::modified already reads the word as nanoseconds since UNIX_EPOCH. sshd's SFTP attributes, which read modified(), now report real times. The contract is the one PR #536's fsd already meets: it reports nanoseconds since the epoch, off SYS_CLOCK_EPOCH's whole seconds. Tests: file_mtime judges /tmp's stamp between two SYS_CLOCK_EPOCH readings and requires a second write's stamp to be later (whole seconds cannot say that), on the shared boot; its write/read modes are driven by file_mtime_survives_a_reboot, which stages the RTC with -rtc base=, checks DATA's stamp against that instant, reads the same stamp off the image with the host's bcachefs reader, and boots again with the RTC a day on to read it back unchanged. Closes issues/filesystem/a-files-mtime-is-nanoseconds-since-boot.md. Files what the audit found and this does not fix: userlands-wall-clock-has-whole-seconds-and-an-mtime-has-nanoseconds, the-last-handle-to-close-stamps-the-file-with-its-own-mtime, a-fat-files-mtime-reads-finer-through-its-writer-than-after-a-reopen, std-answers-an-mtime-of-1970-for-what-it-did-not-stat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review, round 1, at 32948daReady for review. CI BLOCKER
NOTE
REMOVE
SEND BACK |
…every stamp site observed - clock: `mtime_now` is `utc_nanos().unwrap_or(0)`. A machine whose RTC never answered has no date, so a file written there is undated (0), never 1970 plus uptime. `NANOS_PER_SEC` is private and used for every second in the module; FAT's two conversions take and give an `mtime`. - toyos-abi: `Stat::mtime` states each mount's resolution and that 0 is undated; the path citations and "never a time since boot" are gone. - std (fork ac63a08077f, `wt-toyos-mtime`): `Metadata::modified` answers `Unsupported` for 0, the kind `SYS_CLOCK_EPOCH` refuses with there. - libc: `st_nlink` is `u64`, as `nlink_t` is. The Rust `Stat` was 112 bytes against C's 120, so C read `st_blksize` as `st_size` and a stamp's nanoseconds as `st_mtime`. `202_stat_mtime` asserts the size, the length, the seconds and the nanoseconds through `fstat` and `stat`. - `file_mtime` judges a create with truncation, a create of a missing file and a truncation against the wall clock, and its `undated` mode, run by the new `file_mtime_undated` on the `rtc-dead` machine, finds a written file undated without ever asking the time. - Issues: the last-handle defect now reaches FAT; the std issue is renamed to what stays true; filed: an undated FAT file reads back as 1980, and the shared-object cache's identity is blind on an undated machine. The so-cache issue's `nanos_since_boot` claim is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Round 2, head 57cc41e: NOT READY FOR REVIEW |
Recount tests/testcases/LICENSE's tinycc totals from the tree as merged (318 files, 62 not upstream, 61 written here) instead of either side's stale count. main's wall-clock boot-drift refactor (MAX_BOOT_DRIFT_SECS replaced by after_the_base, measured against the boot's own elapsed time) merged without a textual conflict but left file_mtime_survives_a_reboot referencing the deleted constant; it now uses after_the_base like main's other callers, with mtime_boot returning the elapsed Duration alongside the printed mtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner approved this ABI change for the self-hosting track (ToyOS builds LLVM on itself, and ninja and make decide rebuilds by mtime): a file's modification time is wall-clock time, in nanoseconds, and a reboot does not change it.
The contract
toyos_abi::syscall::Stat::mtimeis nanoseconds since the Unix epoch, UTC, taken off the wall clock at the write, to the resolution its mount keeps:/tmp(tmpfs)/boot,/log)src/image.rsstamps 0, for a reproducible image0 is undated. A machine whose RTC never answered has no date, so a file written there is stamped 0 and never 1970 plus uptime. std's
Metadata::modifiedanswers 0 withErrorKind::Unsupported, the kindSYS_CLOCK_EPOCH's refusal maps to on that machine, whereSystemTime::nowpanics. libc reports 0 as 0.Accuracy is the RTC's second and resolution the counter's: the anchor truncates the RTC's second, as
SYS_CLOCK_EPOCHalready did.The layout of
Statis unchanged (oneu64), so no syscall changes. The sysroot key moves, because libc'sstruct statand the std fork change.What changed, per decision
kernel/src/clock.rsutc_nanosis the RTC's whole-second anchor carried on by the counter.utc_secs(SYS_CLOCK_EPOCH) isutc_nanos / 10⁹, so the epoch syscall and a stamp cannot disagree about a second. The arithmetic is unchanged:(boot + offset)·10⁹ + nsbover 10⁹ isboot + offset + ⌊nsb/10⁹⌋.mtime_nowis what a write stamps:utc_nanos, or 0 with no RTC.local_secs_ofandmtime_of_localconvert anmtimefor FAT, which stores local seconds.NANOS_PER_SECis private to the module.object/ops.rs,vfs.rs,revoke_selftest.rs,leak_selftest.rsstamp withmtime_nowwherever they stampednanos_since_boot: create with truncation, create of a missing file, write,ftruncate, and the flushopen_backing_identifiedmakes.fat32_adapter.rscreateandupdate_metadatastamp themtimethe VFS hands them. Before, they stamped the flush's instant and ignored the argument.file_mtimeanswers UTC nanoseconds. Before, it answered local seconds, a different unit from every other mount.now()stays for the stamps the VFS gives no time for: reconcile and mkdir.ac63a08077fonToyOSOrg/rustbranchwt-toyos-mtime, pinned here):Metadata::modifiedanswersUnsupportedfor 0. Otherwise it already read the word as nanoseconds sinceUNIX_EPOCH, so it is correct now and was wrong before.struct statgets POSIX'sst_atim/st_mtim/st_ctim(struct timespec), andst_mtimebecomes the macro POSIX defines.fstatused to put nanoseconds intost_mtime, which is seconds.st_nlinkisu64, asnlink_t(unsigned long) is. The RustStatwas 112 bytes against C's 120, so a C caller readst_blksize(0) asst_sizeand the stamp'stv_nsecasst_mtime, andfstatleft C's last 8 bytes unwritten. That mismatch predates this branch; nothing in the tree calledstatfrom C.Tests
202_stat_mtime(C, Fast): writes 17 bytes to/tmp, thenfstats andstats the file. It printssizeof(struct stat)(120),st_size(17), whetherst_mtimelies between twotime(NULL)readings around the write, and whetherst_mtim.tv_nsecis below a second.file_mtime(Rust, Fast, shared boot) judges/tmp. Each stamp must lie between twoSYS_CLOCK_EPOCHreadings taken around what made it:before·10⁹ ≤ mtime < (after+1)·10⁹. It covers four stamp sites:File::createwith no write (create with truncation);OpenOptions::new().write(true).create(true)on a path shown missing first (create of a missing file);set_len(1)andsync_all(ftruncate), strictly later than its create.file_mtime_undated(Nightly): boots withrtc-deadand checks that the kernel refused the clock by name. It then runsfile_mtime undated, which writes/tmp/file-mtime-undatedwithout asking the time and requiresmodified()to fail withUnsupported.file_mtime_survives_a_reboot(Nightly, two boots of one DATA image):2033-03-07T09:14:25with-rtc base=and writes/home/file-mtime.bin. The printed stamp must lie within 0..300 s of that instant.toyos-fat32a_write_time_keeps_the_even_second(host) is the pure conversion behind the FAT row: a write time drops the odd second thatFatTimekeeps only for creation times.Gates
Run on the committed tree, host only; no QEMU was run.
cargo test -p toyos-build --libcargo test --workspace --exclude toyos-buildcargo test --test toyos-build -- --listFast 202_stat_mtime,Fast file_mtime,Nightly file_mtime_survives_a_reboot,Nightly file_mtime_undated)cargo run -- --clippycargo run -- --build-only202_stat_mtime.ccompiled and linked by the toolchain's clang against this key's C sysroot;_Static_asserts onsizeof(struct stat) == 120,offsetof(st_size) == 48,offsetof(st_mtim) == 88High-risk: ABI and filesystems
mtime-r2/). Each was applied, built withcargo run -- --build-only(EXIT=0 for all five), and reverted in the same script, leaving the tree clean. Each must turn its test red:nlink-u32.patch(libc back to 32948da's layout) →202_stat_mtime:st_sizereads 0 andst_mtimereads a nanosecond count.site-114.patch,site-132.patch,site-821.patch(each stamp site back tonanos_since_boot()alone) →file_mtime, on its create-with-truncation, create-of-a-missing-file and truncation cases respectively.undated.patch(unwrap_or(0)→unwrap_or_else(nanos_since_boot)) →file_mtime_undated:modified()answers 1970 plus uptime.negative-control.patchisgit diff HEAD origin/main -- kernel toyos-abi userland/libc: the whole implementation reverted ontoe3a1cdc8, except the std pin, which only changes what 0 means. It was applied, built (EXIT=0) and reverted in the same script, leaving the tree clean.file_mtimegoes red on its first assertion, since a stamp since boot is about 10⁹·uptime, far belowbefore·10⁹.file_mtime_undatedgoes red becausemodified()answers 1970 plus uptime.file_mtime_survives_a_rebootgoes red on the oracle: a drift of about −1.99·10⁹ s.202_stat_mtimegoes red by not compiling, because main's header has nost_mtim.-rtc base=, the same oraclewall_clock_*uses. For the C layout, clang's ownsizeof/offsetofover the header.What I am unsure of
BackingId(size plus mtime) no longer sees a same-size rewrite on any mount there. Before,nanos_since_bootkept two writes apart. Filed:issues/isolation/the-so-caches-identity-is-blind-on-an-undated-machine.md.FatTime::from_unix_secsclamps 0 to 1980-01-01, which reads back as a date. Filed:issues/filesystem/an-undated-file-on-fat-reads-back-as-1980.md.SystemTime::nowand libc'sclock_gettimedo, so a just-written file can read as up to a second in the future against the program's own "now". Filed:issues/filesystem/userlands-wall-clock-has-whole-seconds-and-an-mtime-has-nanoseconds.md.When merging
#583 and #536 land first.
clock.rs:utc_nanos=BOOT_SECS·10⁹ + nanos_since_boot(), andutc_secsstaysutc_nanos / 10⁹. Deletelocal_secs_ofandmtime_of_local, andstamp's andfile_mtime's calls to them iffat32_adapter.rsstill exists.tests/toyos.rs: take Loader slimming stage 1: the RTC is UTC, KernelArgs refuses another layout by name, IA32_TSC_ADJUST moves to the kernel #583's side of thewall_clock_zonerows.fat32_adapter.rs: take the delete and drop the whole hunk.vfs.rs: theopen_backing_identifiedflush hunk goes with Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536.ops.rs: Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536's fournanos_since_boot()stamps (its:115,:133,:536beforetouch,:779beforetouch) becomemtime_now(), and so doesrevoke_selftest.rs:21. Take Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536'sleak_selftest.rs.Stat::mtime's doc: DATA's resolution becomes the second, because fsd stamps offSYS_CLOCK_EPOCH's whole seconds. Say so there, citingissues/filesystem/userlands-wall-clock-has-whole-seconds-and-an-mtime-has-nanoseconds.md.the-last-handle-to-close-stamps-the-file-with-its-own-mtime.md: judge it again againstfile_cache::touch, and delete it if that closes it.a-fat-files-mtime-reads-finer-through-its-writer-than-after-a-reopen.mdandan-undated-file-on-fat-reads-back-as-1980.md: the mechanism becomes fsd's.Closes
issues/filesystem/a-files-mtime-is-nanoseconds-since-boot.md. Also filed, found by the audit and not fixed here:issues/filesystem/the-last-handle-to-close-stamps-the-file-with-its-own-mtime.md, which reaches FAT as well since this change.issues/filesystem/a-fat-files-mtime-reads-finer-through-its-writer-than-after-a-reopen.mdissues/filesystem/std-calls-undated-what-it-did-not-stat.md:DirEntry::metadataanswers 0 for a file the kernel has a stamp for, andset_timesreturnsOkhaving set nothing.🤖 Generated with Claude Code