Skip to content

Commit f5f3066

Browse files
Copilotstreamich
andcommitted
fix: prevent readFile from updating ctime when only accessing files
Co-authored-by: streamich <[email protected]>
1 parent 89f28a1 commit f5f3066

File tree

3 files changed

+96
-9
lines changed

3 files changed

+96
-9
lines changed

src/__tests__/node.test.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ describe('node.ts', () => {
6060
node.read(buf, 0, 1, 1);
6161
expect(buf.equals(Buffer.from([2]))).toBe(true);
6262
});
63-
it('updates the atime and ctime', () => {
63+
it('updates the atime but not ctime', () => {
6464
const node = new Node(1);
6565
const oldAtime = node.atime;
6666
const oldCtime = node.ctime;
6767
node.read(Buffer.alloc(0));
6868
const newAtime = node.atime;
6969
const newCtime = node.ctime;
7070
expect(newAtime).not.toBe(oldAtime);
71-
expect(newCtime).not.toBe(oldCtime);
71+
expect(newCtime).toBe(oldCtime);
7272
});
7373
});
7474
describe('.chmod(perm)', () => {
@@ -79,7 +79,7 @@ describe('node.ts', () => {
7979
expect(node.perm).toBe(0o600);
8080
expect(node.isFile()).toBe(true);
8181
});
82-
describe.each(['uid', 'gid', 'atime', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
82+
describe.each(['uid', 'gid', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
8383
it('updates the property and the ctime', () => {
8484
const node = new Node(1);
8585
const oldCtime = node.ctime;
@@ -89,28 +89,38 @@ describe('node.ts', () => {
8989
expect(node[field]).toBe(1);
9090
});
9191
});
92+
describe('when atime changes', () => {
93+
it('updates the property but NOT the ctime', () => {
94+
const node = new Node(1);
95+
const oldCtime = node.ctime;
96+
node.atime = new Date(1);
97+
const newCtime = node.ctime;
98+
expect(newCtime).toBe(oldCtime);
99+
expect(node.atime).toEqual(new Date(1));
100+
});
101+
});
92102
describe('.getString(encoding?)', () => {
93-
it('updates the atime and ctime', () => {
103+
it('updates the atime but not ctime', () => {
94104
const node = new Node(1);
95105
const oldAtime = node.atime;
96106
const oldCtime = node.ctime;
97107
node.getString();
98108
const newAtime = node.atime;
99109
const newCtime = node.ctime;
100110
expect(newAtime).not.toBe(oldAtime);
101-
expect(newCtime).not.toBe(oldCtime);
111+
expect(newCtime).toBe(oldCtime);
102112
});
103113
});
104114
describe('.getBuffer()', () => {
105-
it('updates the atime and ctime', () => {
115+
it('updates the atime but not ctime', () => {
106116
const node = new Node(1);
107117
const oldAtime = node.atime;
108118
const oldCtime = node.ctime;
109119
node.getBuffer();
110120
const newAtime = node.atime;
111121
const newCtime = node.ctime;
112122
expect(newAtime).not.toBe(oldAtime);
113-
expect(newCtime).not.toBe(oldCtime);
123+
expect(newCtime).toBe(oldCtime);
114124
});
115125
});
116126
});

src/core/Node.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export class Node extends EventEmitter {
6868

6969
public set atime(atime: Date) {
7070
this._atime = atime;
71-
this.ctime = new Date();
7271
}
7372

7473
public get atime(): Date {
@@ -115,7 +114,7 @@ export class Node extends EventEmitter {
115114

116115
getBuffer(): Buffer {
117116
this.atime = new Date();
118-
if (!this.buf) this.setBuffer(bufferAllocUnsafe(0));
117+
if (!this.buf) this.buf = bufferAllocUnsafe(0);
119118
return bufferFrom(this.buf); // Return a copy.
120119
}
121120

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { memfs } from '../../..';
2+
3+
describe('readFile ctime issue', () => {
4+
it('should NOT change ctime when readFile is called', async () => {
5+
const { fs } = memfs();
6+
7+
const file = '/test-readfile-ctime.txt';
8+
9+
// Create the file
10+
await fs.promises.writeFile(file, 'test content');
11+
12+
// Get initial ctime
13+
const initialStat = await fs.promises.stat(file);
14+
const initialCtime = initialStat.ctimeMs;
15+
16+
// Wait a bit to ensure any ctime change would be detectable
17+
await new Promise(resolve => setTimeout(resolve, 10));
18+
19+
// Read the file (this should only update atime, not ctime)
20+
await fs.promises.readFile(file);
21+
22+
// Check ctime again
23+
const afterReadStat = await fs.promises.stat(file);
24+
const afterReadCtime = afterReadStat.ctimeMs;
25+
26+
// ctime should be unchanged after read
27+
expect(afterReadCtime).toBe(initialCtime);
28+
});
29+
30+
it('should change atime when readFile is called', async () => {
31+
const { fs } = memfs();
32+
33+
const file = '/test-readfile-atime.txt';
34+
35+
// Create the file
36+
await fs.promises.writeFile(file, 'test content');
37+
38+
// Get initial atime
39+
const initialStat = await fs.promises.stat(file);
40+
const initialAtime = initialStat.atimeMs;
41+
42+
// Wait a bit to ensure any atime change would be detectable
43+
await new Promise(resolve => setTimeout(resolve, 10));
44+
45+
// Read the file (this should update atime)
46+
await fs.promises.readFile(file);
47+
48+
// Check atime again
49+
const afterReadStat = await fs.promises.stat(file);
50+
const afterReadAtime = afterReadStat.atimeMs;
51+
52+
// atime should be updated after read
53+
expect(afterReadAtime).toBeGreaterThan(initialAtime);
54+
});
55+
56+
it('should NOT change ctime when readFileSync is called', () => {
57+
const { fs } = memfs();
58+
59+
const file = '/test-readfilesync-ctime.txt';
60+
61+
// Create the file
62+
fs.writeFileSync(file, 'test content');
63+
64+
// Get initial ctime
65+
const initialStat = fs.statSync(file);
66+
const initialCtime = initialStat.ctimeMs;
67+
68+
// Read the file (this should only update atime, not ctime)
69+
fs.readFileSync(file);
70+
71+
// Check ctime again
72+
const afterReadStat = fs.statSync(file);
73+
const afterReadCtime = afterReadStat.ctimeMs;
74+
75+
// ctime should be unchanged after read
76+
expect(afterReadCtime).toBe(initialCtime);
77+
});
78+
});

0 commit comments

Comments
 (0)