@@ -374,47 +374,163 @@ export class Nfsv4FsClient implements NfsFsClient {
374374 return entries ;
375375 }
376376
377- public readonly appendFile = ( path : misc . TFileHandle , data : misc . TData , options ?: opts . IAppendFileOptions | string ) : Promise < void > => {
378- throw new Error ( 'Not implemented.' ) ;
379- } ;
377+ public async appendFile ( path : misc . TFileHandle , data : misc . TData , options ?: opts . IAppendFileOptions | string ) : Promise < void > {
378+ const pathStr = typeof path === 'string' ? path : path . toString ( ) ;
379+ const parts = this . parsePath ( pathStr ) ;
380+ const operations : msg . Nfsv4Request [ ] = [ nfs . PUTROOTFH ( ) ] ;
381+ for ( const part of parts . slice ( 0 , - 1 ) ) {
382+ operations . push ( nfs . LOOKUP ( part ) ) ;
383+ }
384+ const filename = parts [ parts . length - 1 ] ;
385+ const openOwner = nfs . OpenOwner ( BigInt ( 1 ) , new Uint8Array ( [ 1 , 2 , 3 , 4 ] ) ) ;
386+ const claim = nfs . OpenClaimNull ( filename ) ;
387+ operations . push (
388+ nfs . OPEN (
389+ 0 ,
390+ Nfsv4OpenAccess . OPEN4_SHARE_ACCESS_WRITE ,
391+ Nfsv4OpenDeny . OPEN4_SHARE_DENY_NONE ,
392+ openOwner ,
393+ 0 ,
394+ claim ,
395+ ) ,
396+ ) ;
397+ const attrNums = [ Nfsv4Attr . FATTR4_SIZE ] ;
398+ const attrMask = this . attrNumsToBitmap ( attrNums ) ;
399+ operations . push ( nfs . GETATTR ( attrMask ) ) ;
400+ const openResponse = await this . nfs . compound ( operations ) ;
401+ if ( openResponse . status !== Nfsv4Stat . NFS4_OK ) {
402+ throw new Error ( `Failed to open file: ${ openResponse . status } ` ) ;
403+ }
404+ const openRes = openResponse . resarray [ openResponse . resarray . length - 2 ] as msg . Nfsv4OpenResponse ;
405+ if ( openRes . status !== Nfsv4Stat . NFS4_OK || ! openRes . resok ) {
406+ throw new Error ( `Failed to open file: ${ openRes . status } ` ) ;
407+ }
408+ const getattrRes = openResponse . resarray [ openResponse . resarray . length - 1 ] as msg . Nfsv4GetattrResponse ;
409+ if ( getattrRes . status !== Nfsv4Stat . NFS4_OK || ! getattrRes . resok ) {
410+ throw new Error ( `Failed to get attributes: ${ getattrRes . status } ` ) ;
411+ }
412+ const fattr = getattrRes . resok . objAttributes ;
413+ const reader = new Reader ( ) ;
414+ reader . reset ( fattr . attrVals ) ;
415+ const xdr = new XdrDecoder ( reader ) ;
416+ const currentSize = Number ( xdr . readUnsignedHyper ( ) ) ;
417+ const openStateid = openRes . resok . stateid ;
418+ const buffer = this . encodeData ( data ) ;
419+ const chunkSize = 65536 ;
420+ try {
421+ let offset = BigInt ( currentSize ) ;
422+ for ( let i = 0 ; i < buffer . length ; i += chunkSize ) {
423+ const chunk = buffer . slice ( i , Math . min ( i + chunkSize , buffer . length ) ) ;
424+ const writeResponse = await this . nfs . compound ( [
425+ nfs . WRITE ( openStateid , offset , Nfsv4StableHow . FILE_SYNC4 , chunk ) ,
426+ ] ) ;
427+ if ( writeResponse . status !== Nfsv4Stat . NFS4_OK ) {
428+ throw new Error ( `Failed to write file: ${ writeResponse . status } ` ) ;
429+ }
430+ const writeRes = writeResponse . resarray [ 0 ] as msg . Nfsv4WriteResponse ;
431+ if ( writeRes . status !== Nfsv4Stat . NFS4_OK || ! writeRes . resok ) {
432+ throw new Error ( `Failed to write file: ${ writeRes . status } ` ) ;
433+ }
434+ offset += BigInt ( writeRes . resok . count ) ;
435+ }
436+ } finally {
437+ await this . nfs . compound ( [ nfs . CLOSE ( 0 , openStateid ) ] ) ;
438+ }
439+ }
380440
381- public readonly access = ( path : misc . PathLike , mode ?: number ) : Promise < void > => {
382- throw new Error ( 'Not implemented.' ) ;
383- } ;
441+ public async truncate ( path : misc . PathLike , len : number = 0 ) : Promise < void > {
442+ const pathStr = typeof path === 'string' ? path : path . toString ( ) ;
443+ const parts = this . parsePath ( pathStr ) ;
444+ const operations : msg . Nfsv4Request [ ] = [ nfs . PUTROOTFH ( ) ] ;
445+ for ( const part of parts ) {
446+ operations . push ( nfs . LOOKUP ( part ) ) ;
447+ }
448+ const writer = new Writer ( 16 ) ;
449+ const xdr = new XdrEncoder ( writer ) ;
450+ xdr . writeUnsignedHyper ( BigInt ( len ) ) ;
451+ const attrVals = writer . flush ( ) ;
452+ const sizeAttrs = nfs . Fattr ( [ Nfsv4Attr . FATTR4_SIZE ] , attrVals ) ;
453+ const stateid = nfs . Stateid ( 0 , new Uint8Array ( 12 ) ) ;
454+ operations . push ( nfs . SETATTR ( stateid , sizeAttrs ) ) ;
455+ const response = await this . nfs . compound ( operations ) ;
456+ if ( response . status !== Nfsv4Stat . NFS4_OK ) {
457+ throw new Error ( `Failed to truncate file: ${ response . status } ` ) ;
458+ }
459+ const setattrRes = response . resarray [ response . resarray . length - 1 ] as msg . Nfsv4SetattrResponse ;
460+ if ( setattrRes . status !== Nfsv4Stat . NFS4_OK ) {
461+ throw new Error ( `Failed to truncate file: ${ setattrRes . status } ` ) ;
462+ }
463+ }
384464
385- public readonly copyFile = ( src : misc . PathLike , dest : misc . PathLike , flags ?: misc . TFlagsCopy ) : Promise < void > => {
386- throw new Error ( 'Not implemented.' ) ;
387- } ;
465+ public async unlink ( path : misc . PathLike ) : Promise < void > {
466+ const pathStr = typeof path === 'string' ? path : path . toString ( ) ;
467+ const parts = this . parsePath ( pathStr ) ;
468+ if ( parts . length === 0 ) {
469+ throw new Error ( 'Cannot unlink root directory' ) ;
470+ }
471+ const operations : msg . Nfsv4Request [ ] = [ nfs . PUTROOTFH ( ) ] ;
472+ for ( const part of parts . slice ( 0 , - 1 ) ) {
473+ operations . push ( nfs . LOOKUP ( part ) ) ;
474+ }
475+ const filename = parts [ parts . length - 1 ] ;
476+ operations . push ( nfs . REMOVE ( filename ) ) ;
477+ const response = await this . nfs . compound ( operations ) ;
478+ if ( response . status !== Nfsv4Stat . NFS4_OK ) {
479+ throw new Error ( `Failed to unlink file: ${ response . status } ` ) ;
480+ }
481+ const removeRes = response . resarray [ response . resarray . length - 1 ] as msg . Nfsv4RemoveResponse ;
482+ if ( removeRes . status !== Nfsv4Stat . NFS4_OK ) {
483+ throw new Error ( `Failed to unlink file: ${ removeRes . status } ` ) ;
484+ }
485+ }
388486
389- public readonly link = ( existingPath : misc . PathLike , newPath : misc . PathLike ) : Promise < void > => {
390- throw new Error ( 'Not implemented.' ) ;
391- } ;
487+ public async rmdir ( path : misc . PathLike , options ?: opts . IRmdirOptions ) : Promise < void > {
488+ const pathStr = typeof path === 'string' ? path : path . toString ( ) ;
489+ const parts = this . parsePath ( pathStr ) ;
490+ if ( parts . length === 0 ) {
491+ throw new Error ( 'Cannot remove root directory' ) ;
492+ }
493+ const operations : msg . Nfsv4Request [ ] = [ nfs . PUTROOTFH ( ) ] ;
494+ for ( const part of parts . slice ( 0 , - 1 ) ) {
495+ operations . push ( nfs . LOOKUP ( part ) ) ;
496+ }
497+ const dirname = parts [ parts . length - 1 ] ;
498+ operations . push ( nfs . REMOVE ( dirname ) ) ;
499+ const response = await this . nfs . compound ( operations ) ;
500+ if ( response . status !== Nfsv4Stat . NFS4_OK ) {
501+ throw new Error ( `Failed to remove directory: ${ response . status } ` ) ;
502+ }
503+ const removeRes = response . resarray [ response . resarray . length - 1 ] as msg . Nfsv4RemoveResponse ;
504+ if ( removeRes . status !== Nfsv4Stat . NFS4_OK ) {
505+ throw new Error ( `Failed to remove directory: ${ removeRes . status } ` ) ;
506+ }
507+ }
392508
393- public readonly realpath = ( path : misc . PathLike , options ?: opts . IRealpathOptions | string ) : Promise < misc . TDataOut > => {
509+ public readonly access = ( path : misc . PathLike , mode ?: number ) : Promise < void > => {
394510 throw new Error ( 'Not implemented.' ) ;
395511 } ;
396512
397513 public readonly rename = ( oldPath : misc . PathLike , newPath : misc . PathLike ) : Promise < void > => {
398514 throw new Error ( 'Not implemented.' ) ;
399515 } ;
400516
401- public readonly rmdir = ( path : misc . PathLike , options ?: opts . IRmdirOptions ) : Promise < void > => {
517+ public readonly copyFile = ( src : misc . PathLike , dest : misc . PathLike , flags ?: misc . TFlagsCopy ) : Promise < void > => {
402518 throw new Error ( 'Not implemented.' ) ;
403519 } ;
404520
405- public readonly truncate = ( path : misc . PathLike , len ?: number ) : Promise < void > => {
521+ public readonly realpath = ( path : misc . PathLike , options ?: opts . IRealpathOptions | string ) : Promise < misc . TDataOut > => {
406522 throw new Error ( 'Not implemented.' ) ;
407523 } ;
408524
409- public readonly unlink = ( path : misc . PathLike ) : Promise < void > => {
525+ public readonly link = ( existingPath : misc . PathLike , newPath : misc . PathLike ) : Promise < void > => {
410526 throw new Error ( 'Not implemented.' ) ;
411527 } ;
412528
413- public readonly utimes = ( path : misc . PathLike , atime : misc . TTime , mtime : misc . TTime ) : Promise < void > => {
529+ public readonly symlink = ( target : misc . PathLike , path : misc . PathLike , type ? : misc . symlink . Type ) : Promise < void > => {
414530 throw new Error ( 'Not implemented.' ) ;
415531 } ;
416532
417- public readonly symlink = ( target : misc . PathLike , path : misc . PathLike , type ? : misc . symlink . Type ) : Promise < void > => {
533+ public readonly utimes = ( path : misc . PathLike , atime : misc . TTime , mtime : misc . TTime ) : Promise < void > => {
418534 throw new Error ( 'Not implemented.' ) ;
419535 } ;
420536
0 commit comments