All files / json-pack/src/nfs/v4/client NfsFsDir.ts

81.31% Statements 74/91
47.36% Branches 9/19
85.71% Functions 6/7
81.92% Lines 68/83

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137    2x 2x     2x 2x         2x 6x 6x 6x     6x 6x 6x       7x 5x 5x 5x 5x 5x 5x 5x   5x 5x 5x 5x 5x 5x   5x 5x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 288x 9x 9x 9x       9x             6x 6x 6x 6x                                   5x 5x 1x 1x       1x   4x 4x 1x     1x   3x 3x     3x   1x       1x         1x     1x     1x       3x 3x 5x        
import type * as misc from 'memfs/lib/node/types/misc';
import type {Nfsv4Client} from './types';
import {NfsFsDirent} from './NfsFsDirent';
import {nfs} from '../builder';
import type * as msg from '../messages';
import {Nfsv4Stat, Nfsv4Attr, Nfsv4FType} from '../constants';
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
import {XdrDecoder} from '../../../xdr/XdrDecoder';
 
/**
 * Implements Node.js-like Dir interface for NFS v4 directory iteration.
 */
export class NfsFsDir implements misc.IDir {
  private entries: NfsFsDirent[] = [];
  private position: number = 0;
  private closed: boolean = false;
 
  constructor(
    public readonly path: string,
    private readonly nfs: Nfsv4Client,
    private readonly operations: msg.Nfsv4Request[],
  ) {}
 
  private async ensureLoaded(): Promise<void> {
    if (this.entries.length > 0 || this.closed) return;
    const attrNums = [Nfsv4Attr.FATTR4_TYPE];
    const attrMask: number[] = [];
    for (const attrNum of attrNums) {
      const wordIndex = Math.floor(attrNum / 32);
      const bitIndex = attrNum % 32;
      while (attrMask.length <= wordIndex) attrMask.push(0);
      attrMask[wordIndex] |= 1 << bitIndex;
    }
    const operations = [...this.operations];
    operations.push(nfs.READDIR(attrMask));
    const response = await this.nfs.compound(operations);
    Iif (response.status !== Nfsv4Stat.NFS4_OK) throw new Error(`Failed to read directory: ${response.status}`);
    const readdirRes = response.resarray[response.resarray.length - 1] as msg.Nfsv4ReaddirResponse;
    Iif (readdirRes.status !== Nfsv4Stat.NFS4_OK || !readdirRes.resok)
      throw new Error(`Failed to read directory: ${readdirRes.status}`);
    const entryList = readdirRes.resok.entries;
    for (let i = 0; i < entryList.length; i++) {
      const entry = entryList[i];
      const name = entry.name;
      const fattr = entry.attrs;
      const reader = new Reader();
      reader.reset(fattr.attrVals);
      const xdr = new XdrDecoder(reader);
      let fileType = Nfsv4FType.NF4REG;
      const returnedMask = fattr.attrmask.mask;
      for (let i = 0; i < returnedMask.length; i++) {
        const word = returnedMask[i];
        Iif (!word) continue;
        for (let bit = 0; bit < 32; bit++) {
          if (!(word & (1 << bit))) continue;
          const attrNum = i * 32 + bit;
          if (attrNum === Nfsv4Attr.FATTR4_TYPE) {
            fileType = xdr.readUnsignedInt();
          }
        }
      }
      this.entries.push(new NfsFsDirent(name, fileType));
    }
  }
 
  public async close(): Promise<void>;
  public async close(callback?: (err?: Error) => void): Promise<void>;
  public async close(callback?: (err?: Error) => void): Promise<void> {
    this.closed = true;
    this.entries = [];
    this.position = 0;
    Iif (callback) {
      try {
        callback();
      } catch (err) {
        callback(err as Error);
      }
    }
  }
 
  public closeSync(): void {
    this.closed = true;
    this.entries = [];
    this.position = 0;
  }
 
  public async read(): Promise<misc.IDirent | null>;
  public async read(callback?: (err: Error | null, dir?: misc.IDirent | null) => void): Promise<misc.IDirent | null>;
  public async read(callback?: (err: Error | null, dir?: misc.IDirent | null) => void): Promise<misc.IDirent | null> {
    try {
      if (this.closed) {
        const err = new Error('Directory is closed');
        Iif (callback) {
          callback(err, null);
          return null;
        }
        throw err;
      }
      await this.ensureLoaded();
      if (this.position >= this.entries.length) {
        Iif (callback) {
          callback(null, null);
        }
        return null;
      }
      const entry = this.entries[this.position++];
      Iif (callback) {
        callback(null, entry);
      }
      return entry;
    } catch (err) {
      Iif (callback) {
        callback(err as Error, null);
        return null;
      }
      throw err;
    }
  }
 
  public readSync(): misc.IDirent | null {
    Iif (this.closed) {
      throw new Error('Directory is closed');
    }
    Iif (this.position >= this.entries.length) {
      return null;
    }
    return this.entries[this.position++];
  }
 
  public async *[Symbol.asyncIterator](): AsyncIterableIterator<misc.IDirent> {
    await this.ensureLoaded();
    for (const entry of this.entries) {
      yield entry;
    }
  }
}