mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
## Summary - Adds birthtime (file creation time) support on Linux using the `statx` syscall - Stores birthtime in architecture-specific unused fields of the kernel Stat struct (x86_64 and aarch64) - Falls back to traditional `stat` on kernels < 4.11 that don't support `statx` - Includes comprehensive tests validating birthtime behavior Fixes #6585 ## Implementation Details **src/sys.zig:** - Added `StatxField` enum for field selection - Implemented `statxImpl()`, `fstatx()`, `statx()`, and `lstatx()` functions - Stores birthtime in unused padding fields (architecture-specific for x86_64 and aarch64) - Graceful fallback to traditional stat if statx is not supported **src/bun.js/node/node_fs.zig:** - Updated `stat()`, `fstat()`, and `lstat()` to use statx functions on Linux **src/bun.js/node/Stat.zig:** - Added `getBirthtime()` helper to extract birthtime from architecture-specific storage **test/js/node/fs/fs-birthtime-linux.test.ts:** - Tests non-zero birthtime values - Verifies birthtime immutability across file modifications - Validates consistency across stat/lstat/fstat - Tests BigInt stats with nanosecond precision - Verifies birthtime ordering relative to other timestamps ## Test Plan - [x] Run `bun bd test test/js/node/fs/fs-birthtime-linux.test.ts` - all 5 tests pass - [x] Compare behavior with Node.js - identical behavior - [x] Compare with system Bun - system Bun returns epoch, new implementation returns real birthtime 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
109 lines
3.8 KiB
TypeScript
109 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { isLinux, tempDirWithFiles } from "harness";
|
|
import { chmodSync, closeSync, fstatSync, lstatSync, openSync, statSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
describe.skipIf(!isLinux)("birthtime", () => {
|
|
it("should return non-zero birthtime on Linux", () => {
|
|
const dir = tempDirWithFiles("birthtime-test", {
|
|
"test.txt": "initial content",
|
|
});
|
|
|
|
const filepath = join(dir, "test.txt");
|
|
const stats = statSync(filepath);
|
|
|
|
// On Linux with statx support, birthtime should be > 0
|
|
expect(stats.birthtimeMs).toBeGreaterThan(0);
|
|
expect(stats.birthtime.getTime()).toBeGreaterThan(0);
|
|
expect(stats.birthtime.getFullYear()).toBeGreaterThanOrEqual(2025);
|
|
});
|
|
|
|
it("birthtime should remain constant while other timestamps change", () => {
|
|
const dir = tempDirWithFiles("birthtime-immutable", {});
|
|
const filepath = join(dir, "immutable-test.txt");
|
|
|
|
// Create file and capture birthtime
|
|
writeFileSync(filepath, "original");
|
|
const initialStats = statSync(filepath);
|
|
const birthtime = initialStats.birthtimeMs;
|
|
|
|
expect(birthtime).toBeGreaterThan(0);
|
|
|
|
// Wait a bit to ensure timestamps would differ
|
|
Bun.sleepSync(10);
|
|
|
|
// Modify content (updates mtime and ctime)
|
|
writeFileSync(filepath, "modified");
|
|
const afterModify = statSync(filepath);
|
|
|
|
expect(afterModify.birthtimeMs).toBe(birthtime);
|
|
expect(afterModify.mtimeMs).toBeGreaterThan(initialStats.mtimeMs);
|
|
|
|
// Wait again
|
|
Bun.sleepSync(10);
|
|
|
|
// Change permissions (updates ctime)
|
|
chmodSync(filepath, 0o755);
|
|
const afterChmod = statSync(filepath);
|
|
|
|
expect(afterChmod.birthtimeMs).toBe(birthtime);
|
|
expect(afterChmod.ctimeMs).toBeGreaterThan(afterModify.ctimeMs);
|
|
});
|
|
|
|
it("birthtime should work with lstat and fstat", () => {
|
|
const dir = tempDirWithFiles("birthtime-variants", {
|
|
"test.txt": "content",
|
|
});
|
|
|
|
const filepath = join(dir, "test.txt");
|
|
|
|
const statResult = statSync(filepath);
|
|
const lstatResult = lstatSync(filepath);
|
|
const fd = openSync(filepath, "r");
|
|
const fstatResult = fstatSync(fd);
|
|
closeSync(fd);
|
|
|
|
// All three should return the same birthtime
|
|
expect(statResult.birthtimeMs).toBeGreaterThan(0);
|
|
expect(lstatResult.birthtimeMs).toBe(statResult.birthtimeMs);
|
|
expect(fstatResult.birthtimeMs).toBe(statResult.birthtimeMs);
|
|
|
|
expect(statResult.birthtime.getTime()).toBe(lstatResult.birthtime.getTime());
|
|
expect(statResult.birthtime.getTime()).toBe(fstatResult.birthtime.getTime());
|
|
});
|
|
|
|
it("birthtime should work with BigInt stats", () => {
|
|
const dir = tempDirWithFiles("birthtime-bigint", {
|
|
"test.txt": "content",
|
|
});
|
|
|
|
const filepath = join(dir, "test.txt");
|
|
|
|
const regularStats = statSync(filepath);
|
|
const bigintStats = statSync(filepath, { bigint: true });
|
|
|
|
expect(bigintStats.birthtimeMs).toBeGreaterThan(0n);
|
|
expect(bigintStats.birthtimeNs).toBeGreaterThan(0n);
|
|
|
|
// birthtimeMs should be close (within rounding)
|
|
const regularMs = BigInt(Math.floor(regularStats.birthtimeMs));
|
|
expect(bigintStats.birthtimeMs).toBe(regularMs);
|
|
|
|
// birthtimeNs should have nanosecond precision
|
|
expect(bigintStats.birthtimeNs).toBeGreaterThanOrEqual(bigintStats.birthtimeMs * 1000000n);
|
|
});
|
|
|
|
it("birthtime should be less than or equal to all other timestamps on creation", () => {
|
|
const dir = tempDirWithFiles("birthtime-ordering", {});
|
|
const filepath = join(dir, "new-file.txt");
|
|
|
|
writeFileSync(filepath, "new content");
|
|
const stats = statSync(filepath);
|
|
|
|
// birthtime should be <= all other times since it's when file was created
|
|
expect(stats.birthtimeMs).toBeLessThanOrEqual(stats.mtimeMs);
|
|
expect(stats.birthtimeMs).toBeLessThanOrEqual(stats.atimeMs);
|
|
expect(stats.birthtimeMs).toBeLessThanOrEqual(stats.ctimeMs);
|
|
});
|
|
});
|