Skip to content

Commit 312ce88

Browse files
Copilotstreamich
andcommitted
feat: implement statfsSync and statfs functions with StatFs class
Co-authored-by: streamich <[email protected]>
1 parent ae96410 commit 312ce88

File tree

4 files changed

+180
-4
lines changed

4 files changed

+180
-4
lines changed

src/node/StatFs.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Superblock } from '../core';
2+
import type { IStatFs } from './types/misc';
3+
4+
export type TStatNumber = number | bigint;
5+
6+
/**
7+
* Statistics about a file system, like `fs.StatFs`.
8+
*/
9+
export class StatFs<T = TStatNumber> implements IStatFs<T> {
10+
static build(superblock: Superblock, bigint: false): StatFs<number>;
11+
static build(superblock: Superblock, bigint: true): StatFs<bigint>;
12+
static build(superblock: Superblock, bigint?: boolean): StatFs<TStatNumber>;
13+
static build(superblock: Superblock, bigint: boolean = false): StatFs<TStatNumber> {
14+
const statfs = new StatFs<TStatNumber>();
15+
16+
const getStatNumber = !bigint ? number => number : number => BigInt(number);
17+
18+
// For in-memory filesystem, provide mock but reasonable values
19+
// Magic number for in-memory filesystem type (similar to ramfs)
20+
statfs.type = getStatNumber(0x858458f6);
21+
22+
// Optimal transfer block size - commonly 4096 bytes
23+
statfs.bsize = getStatNumber(4096);
24+
25+
// Calculate filesystem stats based on current state
26+
const totalInodes = Object.keys(superblock.inodes).length;
27+
28+
// Mock large filesystem capacity (appears as a large filesystem to applications)
29+
const totalBlocks = 1000000;
30+
const usedBlocks = Math.min(totalInodes * 2, totalBlocks); // Rough estimation
31+
const freeBlocks = totalBlocks - usedBlocks;
32+
33+
statfs.blocks = getStatNumber(totalBlocks); // Total data blocks
34+
statfs.bfree = getStatNumber(freeBlocks); // Free blocks in file system
35+
statfs.bavail = getStatNumber(freeBlocks); // Free blocks available to unprivileged users
36+
37+
// File node statistics
38+
const maxFiles = 1000000; // Mock large number of available inodes
39+
statfs.files = getStatNumber(maxFiles); // Total file nodes in file system
40+
statfs.ffree = getStatNumber(maxFiles - totalInodes); // Free file nodes
41+
42+
return statfs;
43+
}
44+
45+
type: T;
46+
bsize: T;
47+
blocks: T;
48+
bfree: T;
49+
bavail: T;
50+
files: T;
51+
ffree: T;
52+
}
53+
54+
export default StatFs;

src/node/__tests__/volume.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { URL } from 'url';
33
import { Link } from '../../core';
44
import Stats from '../Stats';
55
import Dirent from '../Dirent';
6+
import StatFs from '../StatFs';
67
import { Volume, StatWatcher } from '../volume';
78
import hasBigInt from '../../__tests__/hasBigInt';
89
import { tryGetChild, tryGetChildNode } from '../../__tests__/util';
@@ -1097,6 +1098,92 @@ describe('volume', () => {
10971098
describe('.fstat(fd, callback)', () => {
10981099
xit('...', () => {});
10991100
});
1101+
describe('.statfsSync(path)', () => {
1102+
const vol = new Volume();
1103+
vol.mkdirSync('/test');
1104+
vol.writeFileSync('/test/file.txt', 'hello world');
1105+
1106+
it('Returns StatFs instance', () => {
1107+
const statfs = vol.statfsSync('/test');
1108+
expect(statfs).toBeInstanceOf(StatFs);
1109+
});
1110+
1111+
it('Returns filesystem statistics', () => {
1112+
const statfs = vol.statfsSync('/');
1113+
expect(typeof statfs.type).toBe('number');
1114+
expect(typeof statfs.bsize).toBe('number');
1115+
expect(typeof statfs.blocks).toBe('number');
1116+
expect(typeof statfs.bfree).toBe('number');
1117+
expect(typeof statfs.bavail).toBe('number');
1118+
expect(typeof statfs.files).toBe('number');
1119+
expect(typeof statfs.ffree).toBe('number');
1120+
1121+
expect(statfs.bsize).toBeGreaterThan(0);
1122+
expect(statfs.blocks).toBeGreaterThan(0);
1123+
expect(statfs.files).toBeGreaterThan(0);
1124+
});
1125+
1126+
it('Works with bigint option', () => {
1127+
const statfs = vol.statfsSync('/', { bigint: true });
1128+
expect(typeof statfs.type).toBe('bigint');
1129+
expect(typeof statfs.bsize).toBe('bigint');
1130+
expect(typeof statfs.blocks).toBe('bigint');
1131+
expect(typeof statfs.bfree).toBe('bigint');
1132+
expect(typeof statfs.bavail).toBe('bigint');
1133+
expect(typeof statfs.files).toBe('bigint');
1134+
expect(typeof statfs.ffree).toBe('bigint');
1135+
});
1136+
1137+
it('Throws when path does not exist', () => {
1138+
expect(() => vol.statfsSync('/nonexistent')).toThrow();
1139+
});
1140+
1141+
it('Works with different paths in same filesystem', () => {
1142+
const statfs1 = vol.statfsSync('/');
1143+
const statfs2 = vol.statfsSync('/test');
1144+
1145+
// Should return same filesystem stats for any path
1146+
expect(statfs1.type).toBe(statfs2.type);
1147+
expect(statfs1.bsize).toBe(statfs2.bsize);
1148+
expect(statfs1.blocks).toBe(statfs2.blocks);
1149+
});
1150+
});
1151+
describe('.statfs(path, callback)', () => {
1152+
const vol = new Volume();
1153+
vol.mkdirSync('/test');
1154+
vol.writeFileSync('/test/file.txt', 'hello world');
1155+
1156+
it('Calls callback with StatFs instance', done => {
1157+
vol.statfs('/test', (err, statfs) => {
1158+
expect(err).toBeNull();
1159+
expect(statfs).toBeInstanceOf(StatFs);
1160+
expect(statfs).toBeDefined();
1161+
expect(typeof statfs!.type).toBe('number');
1162+
expect(statfs!.bsize).toBeGreaterThan(0);
1163+
done();
1164+
});
1165+
});
1166+
1167+
it('Works with bigint option', done => {
1168+
vol.statfs('/', { bigint: true }, (err, statfs) => {
1169+
expect(err).toBeNull();
1170+
expect(statfs).toBeDefined();
1171+
expect(typeof statfs!.type).toBe('bigint');
1172+
expect(typeof statfs!.bsize).toBe('bigint');
1173+
done();
1174+
});
1175+
});
1176+
1177+
it('Calls callback with error when path does not exist', done => {
1178+
vol.statfs('/nonexistent', (err, statfs) => {
1179+
expect(err).toBeTruthy();
1180+
expect(err).toBeDefined();
1181+
expect(err!.code).toBe('ENOENT');
1182+
expect(statfs).toBeUndefined();
1183+
done();
1184+
});
1185+
});
1186+
});
11001187
describe('.linkSync(existingPath, newPath)', () => {
11011188
const vol = new Volume();
11021189
it('Create a new link', () => {

src/node/options.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ export const getStatOptsAndCb: (
108108
) => [opts.IStatOptions, misc.TCallback<misc.IStats>] = (options, callback?) =>
109109
typeof options === 'function' ? [getStatOptions(), options] : [getStatOptions(options), validateCallback(callback)];
110110

111+
const statfsDefaults: opts.IStafsOptions = {
112+
bigint: false,
113+
};
114+
export const getStatfsOptions: (options?: any) => opts.IStafsOptions = (options = {}) =>
115+
Object.assign({}, statfsDefaults, options);
116+
export const getStatfsOptsAndCb: (
117+
options: any,
118+
callback?: misc.TCallback<misc.IStatFs>,
119+
) => [opts.IStafsOptions, misc.TCallback<misc.IStatFs>] = (options, callback?) =>
120+
typeof options === 'function'
121+
? [getStatfsOptions(), options]
122+
: [getStatfsOptions(options), validateCallback(callback)];
123+
111124
const realpathDefaults: opts.IReadFileOptions = optsDefaults;
112125
export const getRealpathOptions = optsGenerator<opts.IRealpathOptions>(realpathDefaults);
113126
export const getRealpathOptsAndCb = optsAndCbGenerator<opts.IRealpathOptions, misc.TDataOut>(getRealpathOptions);

src/node/volume.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as pathModule from 'path';
22
import { Link, Superblock } from '../core';
33
import Stats from './Stats';
44
import Dirent from './Dirent';
5+
import StatFs from './StatFs';
56
import { Buffer, bufferAllocUnsafe, bufferFrom } from '../internal/buffer';
67
import queueMicrotask from '../queueMicrotask';
78
import setTimeoutUnref, { TSetTimeout } from '../setTimeoutUnref';
@@ -32,6 +33,8 @@ import {
3233
getAppendFileOpts,
3334
getStatOptsAndCb,
3435
getStatOptions,
36+
getStatfsOptsAndCb,
37+
getStatfsOptions,
3538
getRealpathOptsAndCb,
3639
getRealpathOptions,
3740
getWriteFileOptions,
@@ -1442,10 +1445,29 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
14421445
this.wrapAsync(this._cp, [srcFilename, destFilename, opts_], callback);
14431446
};
14441447

1445-
/** @todo Implement statfs */
1446-
public statfsSync: FsSynchronousApi['statfsSync'] = notImplemented;
1447-
/** @todo Implement statfs */
1448-
public statfs: FsCallbackApi['statfs'] = notImplemented;
1448+
private _statfs(filename: string): StatFs<number>;
1449+
private _statfs(filename: string, bigint: false): StatFs<number>;
1450+
private _statfs(filename: string, bigint: true): StatFs<bigint>;
1451+
private _statfs(filename: string, bigint = false): StatFs {
1452+
// Verify the path exists to match Node.js behavior
1453+
this._core.getResolvedLinkOrThrow(filename, 'statfs');
1454+
return StatFs.build(this._core, bigint);
1455+
}
1456+
1457+
statfsSync(path: PathLike): StatFs<number>;
1458+
statfsSync(path: PathLike, options: { bigint: false }): StatFs<number>;
1459+
statfsSync(path: PathLike, options: { bigint: true }): StatFs<bigint>;
1460+
statfsSync(path: PathLike, options?: opts.IStafsOptions): StatFs {
1461+
const { bigint = false } = getStatfsOptions(options);
1462+
return this._statfs(pathToFilename(path), bigint as any);
1463+
}
1464+
1465+
statfs(path: PathLike, callback: misc.TCallback<StatFs>): void;
1466+
statfs(path: PathLike, options: opts.IStafsOptions, callback: misc.TCallback<StatFs>): void;
1467+
statfs(path: PathLike, a: misc.TCallback<StatFs> | opts.IStafsOptions, b?: misc.TCallback<StatFs>): void {
1468+
const [{ bigint = false }, callback] = getStatfsOptsAndCb(a, b);
1469+
this.wrapAsync(this._statfs, [pathToFilename(path), bigint], callback);
1470+
}
14491471
/** @todo Implement openAsBlob */
14501472
public openAsBlob: FsCallbackApi['openAsBlob'] = notImplemented;
14511473

0 commit comments

Comments
 (0)