@jsonjoy.com/json-pack
Version:
High-performance JSON serialization library
812 lines • 41.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Nfsv4FsClient = void 0;
const tslib_1 = require("tslib");
const builder_1 = require("../builder");
const structs = tslib_1.__importStar(require("../structs"));
const Writer_1 = require("@jsonjoy.com/buffers/lib/Writer");
const Reader_1 = require("@jsonjoy.com/buffers/lib/Reader");
const XdrEncoder_1 = require("../../../xdr/XdrEncoder");
const XdrDecoder_1 = require("../../../xdr/XdrDecoder");
const NfsFsStats_1 = require("./NfsFsStats");
const NfsFsDir_1 = require("./NfsFsDir");
const NfsFsDirent_1 = require("./NfsFsDirent");
const NfsFsFileHandle_1 = require("./NfsFsFileHandle");
class Nfsv4FsClient {
constructor(fs) {
this.fs = fs;
this.openOwnerSeqids = new Map();
this.defaultOpenOwnerId = new Uint8Array([1, 2, 3, 4]);
this.closeStateid = async (openOwner, stateid) => {
const key = this.makeOpenOwnerKey(openOwner);
const previousSeqid = this.openOwnerSeqids.get(key);
const seqid = this.nextOpenOwnerSeqid(openOwner);
const response = await this.fs.compound([builder_1.nfs.CLOSE(seqid, stateid)]);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
if (previousSeqid !== undefined) {
this.openOwnerSeqids.set(key, previousSeqid);
}
else {
this.openOwnerSeqids.delete(key);
}
throw new Error(`Failed to close file: ${response.status}`);
}
};
this.readFile = async (id, options) => {
const encoding = typeof options === 'string' ? options : options?.encoding;
const path = typeof id === 'string' ? id : id.toString();
const parts = this.parsePath(path);
const operations = this.navigateToParent(parts);
const filename = parts[parts.length - 1];
const openOwner = this.createDefaultOpenOwner();
const claim = builder_1.nfs.OpenClaimNull(filename);
const openSeqid = this.nextOpenOwnerSeqid(openOwner);
operations.push(builder_1.nfs.OPEN(openSeqid, 1 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ */, 0 /* Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE */, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
const openResponse = await this.fs.compound(operations);
if (openResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to open file: ${openResponse.status}`);
}
const openRes = openResponse.resarray[openResponse.resarray.length - 1];
if (openRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !openRes.resok) {
throw new Error(`Failed to open file: ${openRes.status}`);
}
const stateid = openRes.resok.stateid;
const chunks = [];
let offset = BigInt(0);
const chunkSize = 65536;
try {
while (true) {
const readResponse = await this.fs.compound([builder_1.nfs.READ(offset, chunkSize, stateid)]);
if (readResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to read file: ${readResponse.status}`);
}
const readRes = readResponse.resarray[0];
if (readRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !readRes.resok) {
throw new Error(`Failed to read file: ${readRes.status}`);
}
if (readRes.resok.data.length > 0) {
chunks.push(readRes.resok.data);
offset += BigInt(readRes.resok.data.length);
}
if (readRes.resok.eof)
break;
}
}
finally {
await this.closeStateid(openOwner, stateid);
}
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const result = new Uint8Array(totalLength);
let position = 0;
for (const chunk of chunks) {
result.set(chunk, position);
position += chunk.length;
}
return this.decodeData(result, encoding);
};
this.writeFile = async (id, data, options) => {
const path = typeof id === 'string' ? id : id.toString();
const parts = this.parsePath(path);
const operations = this.navigateToParent(parts);
const filename = parts[parts.length - 1];
const openOwner = this.createDefaultOpenOwner();
const claim = builder_1.nfs.OpenClaimNull(filename);
const openSeqid = this.nextOpenOwnerSeqid(openOwner);
operations.push(builder_1.nfs.OPEN(openSeqid, 2 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE */, 0 /* Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE */, openOwner, builder_1.nfs.OpenHowCreateUnchecked(), claim));
const writer = new Writer_1.Writer(16);
const xdr = new XdrEncoder_1.XdrEncoder(writer);
xdr.writeUnsignedHyper(BigInt(0));
const attrVals = writer.flush();
const truncateAttrs = builder_1.nfs.Fattr([4 /* Nfsv4Attr.FATTR4_SIZE */], attrVals);
const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
operations.push(builder_1.nfs.SETATTR(stateid, truncateAttrs));
const openResponse = await this.fs.compound(operations);
if (openResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to open file: ${openResponse.status}`);
}
const openRes = openResponse.resarray[openResponse.resarray.length - 2];
if (openRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !openRes.resok) {
throw new Error(`Failed to open file: ${openRes.status}`);
}
const openStateid = openRes.resok.stateid;
const buffer = this.encodeData(data);
const chunkSize = 65536;
try {
let offset = BigInt(0);
for (let i = 0; i < buffer.length; i += chunkSize) {
const chunk = buffer.slice(i, Math.min(i + chunkSize, buffer.length));
const writeResponse = await this.fs.compound([
builder_1.nfs.WRITE(openStateid, offset, 2 /* Nfsv4StableHow.FILE_SYNC4 */, chunk),
]);
if (writeResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to write file: ${writeResponse.status}`);
}
const writeRes = writeResponse.resarray[0];
if (writeRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !writeRes.resok) {
throw new Error(`Failed to write file: ${writeRes.status}`);
}
offset += BigInt(writeRes.resok.count);
}
}
finally {
await this.closeStateid(openOwner, openStateid);
}
};
this.stat = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const attrNums = [
1 /* Nfsv4Attr.FATTR4_TYPE */,
4 /* Nfsv4Attr.FATTR4_SIZE */,
20 /* Nfsv4Attr.FATTR4_FILEID */,
33 /* Nfsv4Attr.FATTR4_MODE */,
35 /* Nfsv4Attr.FATTR4_NUMLINKS */,
45 /* Nfsv4Attr.FATTR4_SPACE_USED */,
47 /* Nfsv4Attr.FATTR4_TIME_ACCESS */,
53 /* Nfsv4Attr.FATTR4_TIME_MODIFY */,
52 /* Nfsv4Attr.FATTR4_TIME_METADATA */,
];
const attrMask = this.attrNumsToBitmap(attrNums);
operations.push(builder_1.nfs.GETATTR(attrMask));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to stat file: ${response.status}`);
}
const getattrRes = response.resarray[response.resarray.length - 1];
if (getattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !getattrRes.resok) {
throw new Error(`Failed to get attributes: ${getattrRes.status}`);
}
const fattr = getattrRes.resok.objAttributes;
const reader = new Reader_1.Reader();
reader.reset(fattr.attrVals);
const xdr = new XdrDecoder_1.XdrDecoder(reader);
let fileType = 1 /* Nfsv4FType.NF4REG */;
let size = 0;
let fileid = 0;
let mode = 0;
let nlink = 1;
let spaceUsed = 0;
let atime = new Date(0);
let mtime = new Date(0);
let ctime = new Date(0);
const returnedMask = fattr.attrmask.mask;
for (let i = 0; i < returnedMask.length; i++) {
const word = returnedMask[i];
if (!word)
continue;
for (let bit = 0; bit < 32; bit++) {
if (!(word & (1 << bit)))
continue;
const attrNum = i * 32 + bit;
switch (attrNum) {
case 1 /* Nfsv4Attr.FATTR4_TYPE */:
fileType = xdr.readUnsignedInt();
break;
case 4 /* Nfsv4Attr.FATTR4_SIZE */:
size = Number(xdr.readUnsignedHyper());
break;
case 20 /* Nfsv4Attr.FATTR4_FILEID */:
fileid = Number(xdr.readUnsignedHyper());
break;
case 33 /* Nfsv4Attr.FATTR4_MODE */:
mode = xdr.readUnsignedInt();
break;
case 35 /* Nfsv4Attr.FATTR4_NUMLINKS */:
nlink = xdr.readUnsignedInt();
break;
case 45 /* Nfsv4Attr.FATTR4_SPACE_USED */:
spaceUsed = Number(xdr.readUnsignedHyper());
break;
case 47 /* Nfsv4Attr.FATTR4_TIME_ACCESS */: {
const seconds = Number(xdr.readHyper());
const nseconds = xdr.readUnsignedInt();
atime = new Date(seconds * 1000 + nseconds / 1000000);
break;
}
case 53 /* Nfsv4Attr.FATTR4_TIME_MODIFY */: {
const seconds = Number(xdr.readHyper());
const nseconds = xdr.readUnsignedInt();
mtime = new Date(seconds * 1000 + nseconds / 1000000);
break;
}
case 52 /* Nfsv4Attr.FATTR4_TIME_METADATA */: {
const seconds = Number(xdr.readHyper());
const nseconds = xdr.readUnsignedInt();
ctime = new Date(seconds * 1000 + nseconds / 1000000);
break;
}
}
}
}
const blocks = Math.ceil(spaceUsed / 512);
return new NfsFsStats_1.NfsFsStats(0, 0, 0, 4096, fileid, size, blocks, atime, mtime, ctime, mtime, atime.getTime(), mtime.getTime(), ctime.getTime(), mtime.getTime(), 0, mode, nlink, fileType);
};
this.lstat = async (path, options) => {
return this.stat(path, options);
};
this.mkdir = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
if (parts.length === 0) {
throw new Error('Cannot create root directory');
}
const operations = this.navigateToParent(parts);
const dirname = parts[parts.length - 1];
const createType = builder_1.nfs.CreateTypeDir();
const emptyAttrs = builder_1.nfs.Fattr([], new Uint8Array(0));
operations.push(builder_1.nfs.CREATE(createType, dirname, emptyAttrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create directory: ${response.status}`);
}
const createRes = response.resarray[response.resarray.length - 1];
if (createRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create directory: ${createRes.status}`);
}
return undefined;
};
this.readdir = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const withFileTypes = typeof options === 'object' && options?.withFileTypes;
const encoding = typeof options === 'string' ? options : options?.encoding;
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const attrNums = withFileTypes ? [1 /* Nfsv4Attr.FATTR4_TYPE */] : [];
const attrMask = this.attrNumsToBitmap(attrNums);
operations.push(builder_1.nfs.READDIR(attrMask));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to read directory: ${response.status}`);
}
const readdirRes = response.resarray[response.resarray.length - 1];
if (readdirRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !readdirRes.resok) {
throw new Error(`Failed to read directory: ${readdirRes.status}`);
}
const entries = [];
const dirents = [];
const entryList = readdirRes.resok.entries;
for (let i = 0; i < entryList.length; i++) {
const entry = entryList[i];
const name = entry.name;
if (withFileTypes) {
const fattr = entry.attrs;
const reader = new Reader_1.Reader();
reader.reset(fattr.attrVals);
const xdr = new XdrDecoder_1.XdrDecoder(reader);
let fileType = 1 /* Nfsv4FType.NF4REG */;
const returnedMask = fattr.attrmask.mask;
for (let i = 0; i < returnedMask.length; i++) {
const word = returnedMask[i];
if (!word)
continue;
for (let bit = 0; bit < 32; bit++) {
if (!(word & (1 << bit)))
continue;
const attrNum = i * 32 + bit;
if (attrNum === 1 /* Nfsv4Attr.FATTR4_TYPE */) {
fileType = xdr.readUnsignedInt();
}
}
}
dirents.push(new NfsFsDirent_1.NfsFsDirent(name, fileType));
}
else {
entries.push(name);
}
}
if (withFileTypes) {
return dirents;
}
if (encoding && encoding !== 'utf8') {
return entries.map((name) => Buffer.from(name, 'utf8'));
}
return entries;
};
this.appendFile = async (path, data, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToParent(parts);
const filename = parts[parts.length - 1];
const openOwner = this.createDefaultOpenOwner();
const claim = builder_1.nfs.OpenClaimNull(filename);
const openSeqid = this.nextOpenOwnerSeqid(openOwner);
operations.push(builder_1.nfs.OPEN(openSeqid, 2 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE */, 0 /* Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE */, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
const attrNums = [4 /* Nfsv4Attr.FATTR4_SIZE */];
const attrMask = this.attrNumsToBitmap(attrNums);
operations.push(builder_1.nfs.GETATTR(attrMask));
const openResponse = await this.fs.compound(operations);
if (openResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to open file: ${openResponse.status}`);
}
const openRes = openResponse.resarray[openResponse.resarray.length - 2];
if (openRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !openRes.resok) {
throw new Error(`Failed to open file: ${openRes.status}`);
}
const getattrRes = openResponse.resarray[openResponse.resarray.length - 1];
if (getattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !getattrRes.resok) {
throw new Error(`Failed to get attributes: ${getattrRes.status}`);
}
const fattr = getattrRes.resok.objAttributes;
const reader = new Reader_1.Reader();
reader.reset(fattr.attrVals);
const xdr = new XdrDecoder_1.XdrDecoder(reader);
const currentSize = Number(xdr.readUnsignedHyper());
const openStateid = openRes.resok.stateid;
const buffer = this.encodeData(data);
const chunkSize = 65536;
try {
let offset = BigInt(currentSize);
for (let i = 0; i < buffer.length; i += chunkSize) {
const chunk = buffer.slice(i, Math.min(i + chunkSize, buffer.length));
const writeResponse = await this.fs.compound([
builder_1.nfs.WRITE(openStateid, offset, 2 /* Nfsv4StableHow.FILE_SYNC4 */, chunk),
]);
if (writeResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to write file: ${writeResponse.status}`);
}
const writeRes = writeResponse.resarray[0];
if (writeRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !writeRes.resok) {
throw new Error(`Failed to write file: ${writeRes.status}`);
}
offset += BigInt(writeRes.resok.count);
}
}
finally {
await this.closeStateid(openOwner, openStateid);
}
};
this.truncate = async (path, len = 0) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const writer = new Writer_1.Writer(16);
const xdr = new XdrEncoder_1.XdrEncoder(writer);
xdr.writeUnsignedHyper(BigInt(len));
const attrVals = writer.flush();
const sizeAttrs = builder_1.nfs.Fattr([4 /* Nfsv4Attr.FATTR4_SIZE */], attrVals);
const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
operations.push(builder_1.nfs.SETATTR(stateid, sizeAttrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to truncate file: ${response.status}`);
}
const setattrRes = response.resarray[response.resarray.length - 1];
if (setattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to truncate file: ${setattrRes.status}`);
}
};
this.unlink = async (path) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
if (parts.length === 0) {
throw new Error('Cannot unlink root directory');
}
const operations = this.navigateToParent(parts);
const filename = parts[parts.length - 1];
operations.push(builder_1.nfs.REMOVE(filename));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to unlink file: ${response.status}`);
}
const removeRes = response.resarray[response.resarray.length - 1];
if (removeRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to unlink file: ${removeRes.status}`);
}
};
this.rmdir = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
if (parts.length === 0) {
throw new Error('Cannot remove root directory');
}
const operations = this.navigateToParent(parts);
const dirname = parts[parts.length - 1];
operations.push(builder_1.nfs.REMOVE(dirname));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to remove directory: ${response.status}`);
}
const removeRes = response.resarray[response.resarray.length - 1];
if (removeRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to remove directory: ${removeRes.status}`);
}
};
this.rm = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
if (parts.length === 0) {
throw new Error('Cannot remove root directory');
}
const force = options?.force ?? false;
const recursive = options?.recursive ?? false;
if (recursive) {
try {
const stats = await this.stat(path);
if (stats.isDirectory()) {
const entries = await this.readdir(path);
for (const entry of entries) {
const entryPath = pathStr + '/' + entry;
await this.rm(entryPath, options);
}
}
}
catch (err) {
if (!force)
throw err;
return;
}
}
try {
const operations = this.navigateToParent(parts);
const name = parts[parts.length - 1];
operations.push(builder_1.nfs.REMOVE(name));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
if (!force)
throw new Error(`Failed to remove: ${response.status}`);
return;
}
const removeRes = response.resarray[response.resarray.length - 1];
if (removeRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
if (!force)
throw new Error(`Failed to remove: ${removeRes.status}`);
}
}
catch (err) {
if (!force)
throw err;
}
};
this.access = async (path, mode = 0) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
let accessMask = 0;
if (mode === 0) {
accessMask = 1 /* Nfsv4Access.ACCESS4_READ */;
}
else {
if (mode & 4)
accessMask |= 1 /* Nfsv4Access.ACCESS4_READ */;
if (mode & 2)
accessMask |= 4 /* Nfsv4Access.ACCESS4_MODIFY */;
if (mode & 1)
accessMask |= 32 /* Nfsv4Access.ACCESS4_EXECUTE */;
}
operations.push(builder_1.nfs.ACCESS(accessMask));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Access denied: ${response.status}`);
}
const accessRes = response.resarray[response.resarray.length - 1];
if (accessRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Access denied: ${accessRes.status}`);
}
};
this.rename = async (oldPath, newPath) => {
const oldPathStr = typeof oldPath === 'string' ? oldPath : oldPath.toString();
const newPathStr = typeof newPath === 'string' ? newPath : newPath.toString();
const oldParts = this.parsePath(oldPathStr);
const newParts = this.parsePath(newPathStr);
if (oldParts.length === 0 || newParts.length === 0) {
throw new Error('Cannot rename root directory');
}
const operations = [];
operations.push(builder_1.nfs.PUTROOTFH());
for (const part of oldParts.slice(0, -1)) {
operations.push(builder_1.nfs.LOOKUP(part));
}
operations.push(builder_1.nfs.SAVEFH());
operations.push(builder_1.nfs.PUTROOTFH());
for (const part of newParts.slice(0, -1)) {
operations.push(builder_1.nfs.LOOKUP(part));
}
const oldname = oldParts[oldParts.length - 1];
const newname = newParts[newParts.length - 1];
operations.push(builder_1.nfs.RENAME(oldname, newname));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to rename: ${response.status}`);
}
const renameRes = response.resarray[response.resarray.length - 1];
if (renameRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to rename: ${renameRes.status}`);
}
};
this.copyFile = async (src, dest, flags) => {
const data = await this.readFile(src);
await this.writeFile(dest, data);
};
this.realpath = async (path, options) => {
const encoding = typeof options === 'string' ? options : options?.encoding;
const pathStr = typeof path === 'string' ? path : path.toString();
const normalized = '/' + this.parsePath(pathStr).join('/');
if (!encoding || encoding === 'utf8') {
return normalized;
}
return Buffer.from(normalized, 'utf8');
};
this.link = async (existingPath, newPath) => {
const existingPathStr = typeof existingPath === 'string' ? existingPath : existingPath.toString();
const newPathStr = typeof newPath === 'string' ? newPath : newPath.toString();
const existingParts = this.parsePath(existingPathStr);
const newParts = this.parsePath(newPathStr);
if (newParts.length === 0) {
throw new Error('Cannot create link at root');
}
const operations = this.navigateToPath(existingParts);
operations.push(builder_1.nfs.SAVEFH());
operations.push(builder_1.nfs.PUTROOTFH());
for (const part of newParts.slice(0, -1)) {
operations.push(builder_1.nfs.LOOKUP(part));
}
const newname = newParts[newParts.length - 1];
operations.push(builder_1.nfs.LINK(newname));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create link: ${response.status}`);
}
const linkRes = response.resarray[response.resarray.length - 1];
if (linkRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create link: ${linkRes.status}`);
}
};
this.symlink = async (target, path, type) => {
const targetStr = typeof target === 'string' ? target : target.toString();
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
if (parts.length === 0) {
throw new Error('Cannot create symlink at root');
}
const operations = this.navigateToParent(parts);
const linkname = parts[parts.length - 1];
const createType = new structs.Nfsv4CreateType(5 /* Nfsv4FType.NF4LNK */, new structs.Nfsv4CreateTypeLink(targetStr));
const emptyAttrs = builder_1.nfs.Fattr([], new Uint8Array(0));
operations.push(builder_1.nfs.CREATE(createType, linkname, emptyAttrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create symlink: ${response.status}`);
}
const createRes = response.resarray[response.resarray.length - 1];
if (createRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to create symlink: ${createRes.status}`);
}
};
this.utimes = async (path, atime, mtime) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const atimeMs = typeof atime === 'number' ? atime : atime instanceof Date ? atime.getTime() : Date.now();
const mtimeMs = typeof mtime === 'number' ? mtime : mtime instanceof Date ? mtime.getTime() : Date.now();
const writer = new Writer_1.Writer(64);
const xdr = new XdrEncoder_1.XdrEncoder(writer);
xdr.writeUnsignedInt(1);
xdr.writeHyper(BigInt(Math.floor(atimeMs / 1000)));
xdr.writeUnsignedInt((atimeMs % 1000) * 1000000);
xdr.writeUnsignedInt(1);
xdr.writeHyper(BigInt(Math.floor(mtimeMs / 1000)));
xdr.writeUnsignedInt((mtimeMs % 1000) * 1000000);
const attrVals = writer.flush();
const timeAttrs = builder_1.nfs.Fattr([48 /* Nfsv4Attr.FATTR4_TIME_ACCESS_SET */, 54 /* Nfsv4Attr.FATTR4_TIME_MODIFY_SET */], attrVals);
const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
operations.push(builder_1.nfs.SETATTR(stateid, timeAttrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to set times: ${response.status}`);
}
const setattrRes = response.resarray[response.resarray.length - 1];
if (setattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to set times: ${setattrRes.status}`);
}
};
this.readlink = async (path, options) => {
const encoding = typeof options === 'string' ? options : options?.encoding;
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
operations.push(builder_1.nfs.READLINK());
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to read link: ${response.status}`);
}
const readlinkRes = response.resarray[response.resarray.length - 1];
if (readlinkRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !readlinkRes.resok) {
throw new Error(`Failed to read link: ${readlinkRes.status}`);
}
if (!encoding || encoding === 'utf8') {
return readlinkRes.resok.link;
}
return Buffer.from(readlinkRes.resok.link, 'utf8');
};
this.opendir = async (path, options) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
return new NfsFsDir_1.NfsFsDir(pathStr, this.fs, operations);
};
this.mkdtemp = async (prefix, options) => {
const encoding = typeof options === 'string' ? options : options?.encoding;
const randomSuffix = Math.random().toString(36).substring(2, 8);
const dirName = prefix + randomSuffix;
await this.mkdir(dirName);
if (!encoding || encoding === 'utf8')
return dirName;
return Buffer.from(dirName, 'utf8');
};
this.chmod = async (path, mode) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const modeValue = typeof mode === 'number' ? mode : parseInt(mode.toString(), 8);
const writer = new Writer_1.Writer(8);
const xdr = new XdrEncoder_1.XdrEncoder(writer);
xdr.writeUnsignedInt(modeValue);
const attrVals = writer.flush();
const attrs = builder_1.nfs.Fattr([33 /* Nfsv4Attr.FATTR4_MODE */], attrVals);
const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
operations.push(builder_1.nfs.SETATTR(stateid, attrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to chmod: ${response.status}`);
}
const setattrRes = response.resarray[response.resarray.length - 1];
if (setattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to chmod: ${setattrRes.status}`);
}
};
this.chown = async (path, uid, gid) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToPath(parts);
const writer = new Writer_1.Writer(64);
const xdr = new XdrEncoder_1.XdrEncoder(writer);
xdr.writeStr(uid.toString());
xdr.writeStr(gid.toString());
const attrVals = writer.flush();
const attrs = builder_1.nfs.Fattr([36 /* Nfsv4Attr.FATTR4_OWNER */, 37 /* Nfsv4Attr.FATTR4_OWNER_GROUP */], attrVals);
const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
operations.push(builder_1.nfs.SETATTR(stateid, attrs));
const response = await this.fs.compound(operations);
if (response.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to chown: ${response.status}`);
}
const setattrRes = response.resarray[response.resarray.length - 1];
if (setattrRes.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to chown: ${setattrRes.status}`);
}
};
this.lchmod = async (path, mode) => {
return this.chmod(path, mode);
};
this.lchown = async (path, uid, gid) => {
return this.chown(path, uid, gid);
};
this.lutimes = async (path, atime, mtime) => {
return this.utimes(path, atime, mtime);
};
this.open = async (path, flags, mode) => {
const pathStr = typeof path === 'string' ? path : path.toString();
const parts = this.parsePath(pathStr);
const operations = this.navigateToParent(parts);
const filename = parts[parts.length - 1];
const openOwner = this.createDefaultOpenOwner();
const claim = builder_1.nfs.OpenClaimNull(filename);
let access = 1 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ */;
const openSeqid = this.nextOpenOwnerSeqid(openOwner);
if (typeof flags === 'string') {
if (flags.includes('r') && flags.includes('+')) {
access = 3 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_BOTH */;
}
else if (flags.includes('w') || flags.includes('a')) {
access = 2 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE */;
if (flags.includes('+')) {
access = 3 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_BOTH */;
}
}
}
else if (typeof flags === 'number') {
const O_RDONLY = 0;
const O_WRONLY = 1;
const O_RDWR = 2;
const O_ACCMODE = 3;
const accessMode = flags & O_ACCMODE;
switch (accessMode) {
case O_RDONLY:
access = 1 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ */;
break;
case O_WRONLY:
access = 2 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE */;
break;
case O_RDWR:
access = 3 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_BOTH */;
break;
}
}
operations.push(builder_1.nfs.OPEN(openSeqid, access, 0 /* Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE */, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
const openResponse = await this.fs.compound(operations);
if (openResponse.status !== 0 /* Nfsv4Stat.NFS4_OK */) {
throw new Error(`Failed to open file: ${openResponse.status}`);
}
const openRes = openResponse.resarray[openResponse.resarray.length - 1];
if (openRes.status !== 0 /* Nfsv4Stat.NFS4_OK */ || !openRes.resok) {
throw new Error(`Failed to open file: ${openRes.status}`);
}
const stateid = openRes.resok.stateid;
const fd = Math.floor(Math.random() * 1000000);
return new NfsFsFileHandle_1.NfsFsFileHandle(fd, pathStr, this, stateid, openOwner);
};
this.statfs = (path, options) => {
throw new Error('Not implemented.');
};
this.watch = (filename, options) => {
throw new Error('Not implemented.');
};
this.glob = (pattern, options) => {
throw new Error('Not implemented.');
};
}
makeOpenOwnerKey(owner) {
return `${owner.clientid}:${Buffer.from(owner.owner).toString('hex')}`;
}
nextOpenOwnerSeqid(owner) {
const key = this.makeOpenOwnerKey(owner);
const last = this.openOwnerSeqids.get(key);
const next = last === undefined ? 0 : last === 0xffffffff ? 1 : (last + 1) >>> 0;
this.openOwnerSeqids.set(key, next);
return next;
}
createDefaultOpenOwner() {
return builder_1.nfs.OpenOwner(BigInt(1), new Uint8Array(this.defaultOpenOwnerId));
}
attrNumsToBitmap(attrNums) {
const bitmap = [];
for (const attrNum of attrNums) {
const wordIndex = Math.floor(attrNum / 32);
const bitIndex = attrNum % 32;
while (bitmap.length <= wordIndex) {
bitmap.push(0);
}
bitmap[wordIndex] |= 1 << bitIndex;
}
return bitmap;
}
parsePath(path) {
const normalized = path.replace(/^\/+/, '').replace(/\/+$/, '');
if (!normalized)
return [];
return normalized.split('/').filter((part) => part.length > 0);
}
navigateToParent(parts) {
const operations = [builder_1.nfs.PUTROOTFH()];
for (const part of parts.slice(0, -1)) {
operations.push(builder_1.nfs.LOOKUP(part));
}
return operations;
}
navigateToPath(parts) {
const operations = [builder_1.nfs.PUTROOTFH()];
for (const part of parts) {
operations.push(builder_1.nfs.LOOKUP(part));
}
return operations;
}
encodeData(data) {
if (data instanceof Uint8Array)
return data;
if (data instanceof ArrayBuffer)
return new Uint8Array(data);
if (typeof data === 'string')
return new TextEncoder().encode(data);
if (Buffer.isBuffer(data))
return new Uint8Array(data);
throw new Error('Unsupported data type');
}
decodeData(data, encoding) {
if (!encoding || encoding === 'buffer')
return Buffer.from(data);
return new TextDecoder(encoding).decode(data);
}
}
exports.Nfsv4FsClient = Nfsv4FsClient;
//# sourceMappingURL=Nfsv4FsClient.js.map