Files
bun.sh/test/js/node/fs/fs-birthtime-linux.test.ts
robobun 46e7a3b3c5 Implement birthtime support on Linux using statx syscall (#23209)
## 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>
2025-10-04 04:57:29 -07:00

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);
});
});