@@ -22,11 +22,12 @@ import {OpenFileState} from '../OpenFileState';
2222import { OpenOwnerState } from '../OpenOwnerState' ;
2323import { LockOwnerState } from '../LockOwnerState' ;
2424import { ByteRangeLock } from '../ByteRangeLock' ;
25+ import { FilesystemStats } from '../FilesystemStats' ;
2526import { FileHandleMapper , ROOT_FH } from './fh' ;
2627import { isErrCode , normalizeNodeFsError } from './util' ;
2728import { Nfsv4StableHow , Nfsv4Attr } from '../../../constants' ;
2829import { encodeAttrs } from './attrs' ;
29- import { parseBitmask , requiresLstat , attrNumsToBitmap } from '../../../attributes' ;
30+ import { parseBitmask , requiresLstat , attrNumsToBitmap , requiresFsStats } from '../../../attributes' ;
3031import { Writer } from '@jsonjoy.com/buffers/lib/Writer' ;
3132import { XdrEncoder } from '../../../../../xdr/XdrEncoder' ;
3233import { XdrDecoder } from '../../../../../xdr/XdrDecoder' ;
@@ -52,6 +53,12 @@ export interface Nfsv4OperationsNodeOpts {
5253 * @default 1000
5354 */
5455 maxPendingClients ?: number ;
56+
57+ /**
58+ * Optional function to provide filesystem statistics.
59+ * If not provided, defaults to 2TB available space and 2M available inodes.
60+ */
61+ fsStats ?: ( ) => Promise < FilesystemStats > ;
5562}
5663
5764/**
@@ -104,15 +111,30 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
104111 */
105112 protected changeCounter : bigint = 0n ;
106113
114+ /**
115+ * Function to retrieve filesystem statistics.
116+ */
117+ protected fsStats : ( ) => Promise < FilesystemStats > ;
118+
107119 constructor ( opts : Nfsv4OperationsNodeOpts ) {
108120 this . fs = opts . fs ;
109121 this . promises = this . fs . promises ;
110122 this . dir = opts . dir ;
111123 this . fh = new FileHandleMapper ( this . bootStamp , this . dir ) ;
112124 this . maxClients = opts . maxClients ?? 1000 ;
113125 this . maxPendingClients = opts . maxPendingClients ?? 1000 ;
126+ this . fsStats = opts . fsStats ?? this . defaultFsStats ;
114127 }
115128
129+ /**
130+ * Default filesystem statistics: 2TB available space, 2M available inodes.
131+ */
132+ protected defaultFsStats = async ( ) : Promise < FilesystemStats > => {
133+ const twoTB = BigInt ( 2 * 1024 * 1024 * 1024 * 1024 ) ; // 2TB
134+ const twoM = BigInt ( 2 * 1000 * 1000 ) ; // 2M inodes
135+ return new FilesystemStats ( twoTB , twoTB , twoTB * 2n , twoM , twoM , twoM * 2n ) ;
136+ } ;
137+
116138 protected findClientByIdString (
117139 map : Map < bigint , ClientRecord > ,
118140 clientIdString : Uint8Array ,
@@ -474,7 +496,15 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
474496 throw normalizeNodeFsError ( error , ctx . connection . logger ) ;
475497 }
476498 }
477- const attrs = encodeAttrs ( request . attrRequest , stats , currentPath , ctx . cfh ! , this . leaseTime ) ;
499+ let fsStats : FilesystemStats | undefined ;
500+ if ( requiresFsStats ( requestedAttrNums ) ) {
501+ try {
502+ fsStats = await this . fsStats ( ) ;
503+ } catch ( error : unknown ) {
504+ ctx . connection . logger . error ( error ) ;
505+ }
506+ }
507+ const attrs = encodeAttrs ( request . attrRequest , stats , currentPath , ctx . cfh ! , this . leaseTime , fsStats ) ;
478508 return new msg . Nfsv4GetattrResponse ( Nfsv4Stat . NFS4_OK , new msg . Nfsv4GetattrResOk ( attrs ) ) ;
479509 }
480510
@@ -570,6 +600,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
570600 if ( startIndex > dirents . length ) startIndex = dirents . length ;
571601 }
572602 let eof = true ;
603+ const fsStats = await this . fsStats ( ) ;
573604 for ( let i = startIndex ; i < dirents . length ; i ++ ) {
574605 const dirent = dirents [ i ] ;
575606 const name = dirent . name ;
@@ -582,7 +613,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
582613 continue ;
583614 }
584615 const entryFh = fh . encode ( entryPath ) ;
585- const attrs = encodeAttrs ( attrRequest , entryStats , entryPath , entryFh , this . leaseTime ) ;
616+ const attrs = encodeAttrs ( attrRequest , entryStats , entryPath , entryFh , this . leaseTime , fsStats ) ;
586617 const nameBytes = Buffer . byteLength ( name , 'utf8' ) ;
587618 const attrBytes = attrs . attrVals . length ;
588619 const entryBytes = overheadPerEntry + nameBytes + attrBytes ;
@@ -1380,10 +1411,18 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
13801411 const currentPathAbsolute = this . absolutePath ( currentPath ) ;
13811412 try {
13821413 const stats = await this . promises . lstat ( currentPathAbsolute ) ;
1414+ const fsStats = await this . fsStats ( ) ;
13831415 // request.objAttributes is a Nfsv4Fattr: use its attrmask when asking
13841416 // encodeAttrs to serialize the server's current attributes and compare
13851417 // raw attrVals bytes.
1386- const attrs = encodeAttrs ( request . objAttributes . attrmask , stats , currentPathAbsolute , ctx . cfh ! , this . leaseTime ) ;
1418+ const attrs = encodeAttrs (
1419+ request . objAttributes . attrmask ,
1420+ stats ,
1421+ currentPathAbsolute ,
1422+ ctx . cfh ! ,
1423+ this . leaseTime ,
1424+ fsStats ,
1425+ ) ;
13871426 if ( cmpUint8Array ( attrs . attrVals , request . objAttributes . attrVals ) )
13881427 return new msg . Nfsv4NverifyResponse ( Nfsv4Stat . NFS4ERR_NOT_SAME ) ;
13891428 return new msg . Nfsv4NverifyResponse ( Nfsv4Stat . NFS4_OK ) ;
@@ -1503,9 +1542,10 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
15031542 }
15041543 const stats = await this . promises . lstat ( currentPathAbsolute ) ;
15051544 const fh = this . fh . encode ( currentPath ) ;
1545+ const fsStats = await this . fsStats ( ) ;
15061546 // Return updated mode and size attributes
15071547 const returnMask = new struct . Nfsv4Bitmap ( attrNumsToBitmap ( [ Nfsv4Attr . FATTR4_MODE , Nfsv4Attr . FATTR4_SIZE ] ) ) ;
1508- const fattr = encodeAttrs ( returnMask , stats , currentPath , fh , this . leaseTime ) ;
1548+ const fattr = encodeAttrs ( returnMask , stats , currentPath , fh , this . leaseTime , fsStats ) ;
15091549 const resok = new msg . Nfsv4SetattrResOk ( returnMask ) ;
15101550 return new msg . Nfsv4SetattrResponse ( Nfsv4Stat . NFS4_OK , resok ) ;
15111551 } catch ( err : unknown ) {
@@ -1519,7 +1559,8 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
15191559 const currentPathAbsolute = this . absolutePath ( currentPath ) ;
15201560 try {
15211561 const stats = await this . promises . lstat ( currentPathAbsolute ) ;
1522- const attrs = encodeAttrs ( request . objAttributes . attrmask , stats , currentPath , ctx . cfh ! , this . leaseTime ) ;
1562+ const fsStats = await this . fsStats ( ) ;
1563+ const attrs = encodeAttrs ( request . objAttributes . attrmask , stats , currentPath , ctx . cfh ! , this . leaseTime , fsStats ) ;
15231564 if ( cmpUint8Array ( attrs . attrVals , request . objAttributes . attrVals ) )
15241565 return new msg . Nfsv4VerifyResponse ( Nfsv4Stat . NFS4_OK ) ;
15251566 return new msg . Nfsv4VerifyResponse ( Nfsv4Stat . NFS4ERR_NOT_SAME ) ;
0 commit comments