@jsonjoy.com/json-pack
Version:
High-performance JSON serialization library
1,045 lines • 70.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Nfsv4OperationsNode = void 0;
const tslib_1 = require("tslib");
const NodePath = tslib_1.__importStar(require("node:path"));
const node_crypto_1 = require("node:crypto");
const msg = tslib_1.__importStar(require("../../../messages"));
const struct = tslib_1.__importStar(require("../../../structs"));
const cmpUint8Array_1 = require("@jsonjoy.com/buffers/lib/cmpUint8Array");
const ClientRecord_1 = require("../ClientRecord");
const OpenFileState_1 = require("../OpenFileState");
const OpenOwnerState_1 = require("../OpenOwnerState");
const LockOwnerState_1 = require("../LockOwnerState");
const ByteRangeLock_1 = require("../ByteRangeLock");
const LockStateid_1 = require("../LockStateid");
const FilesystemStats_1 = require("../FilesystemStats");
const fh_1 = require("./fh");
const util_1 = require("./util");
const attrs_1 = require("./attrs");
const attributes_1 = require("../../../attributes");
const Writer_1 = require("@jsonjoy.com/buffers/lib/Writer");
const XdrEncoder_1 = require("../../../../../xdr/XdrEncoder");
const XdrDecoder_1 = require("../../../../../xdr/XdrDecoder");
/**
* NFS v4 Operations implementation for Node.js `fs` filesystem.
*/
class Nfsv4OperationsNode {
constructor(opts) {
/**
* Lease time in seconds.
* Per RFC 7530 §9.5, this is the time a client has to renew its lease
* before the server may reclaim its state. Default is 90 seconds.
*/
this.leaseTime = 90;
/** Confirmed clients. */
this.clients = new Map();
/** Clients pending SETCLIENTID_CONFIRM confirmation. */
this.pendingClients = new Map();
/** Next client ID to assign. */
this.nextClientId = 1n;
/** Boot stamp, identifies server instance, 16 bits. */
this.bootStamp = Math.round(Math.random() * 0xffff);
/** Next stateid sequence number. */
this.nextStateidSeqid = 1;
/** Map from stateid (as string key) to open file state. */
this.openFiles = new Map();
/** Map from open-owner key to owner state. */
this.openOwners = new Map();
/** Map from lock key to byte-range lock. */
this.locks = new Map();
/** Map from lock-owner key to lock-owner state. */
this.lockOwners = new Map();
/** Map from lock stateid 'other' field to lock stateid state. Per RFC 7530, one stateid per lock-owner per file. */
this.lockStateids = new Map();
/**
* Server-wide monotonic change counter for directory change_info.
* Incremented on every mutating operation (RENAME, REMOVE, CREATE, etc.).
* Used to populate change_info4 before/after values for client cache validation.
*/
this.changeCounter = 0n;
/**
* Default filesystem statistics: 2TB available space, 2M available inodes.
*/
this.defaultFsStats = async () => {
const twoTB = BigInt(2 * 1024 * 1024 * 1024 * 1024); // 2TB
const twoM = BigInt(2 * 1000 * 1000); // 2M inodes
return new FilesystemStats_1.FilesystemStats(twoTB, twoTB, twoTB * 2n, twoM, twoM, twoM * 2n);
};
this.fs = opts.fs;
this.promises = this.fs.promises;
this.dir = opts.dir;
this.fh = new fh_1.FileHandleMapper(this.bootStamp, this.dir);
this.maxClients = opts.maxClients ?? 1000;
this.maxPendingClients = opts.maxPendingClients ?? 1000;
this.fsStats = opts.fsStats ?? this.defaultFsStats;
}
findClientByIdString(map, clientIdString) {
for (const entry of map.entries())
if ((0, cmpUint8Array_1.cmpUint8Array)(entry[1].clientIdString, clientIdString))
return entry;
return;
}
enforceClientLimit() {
if (this.clients.size <= this.maxClients)
return;
const firstKey = this.clients.keys().next().value;
if (firstKey !== undefined)
this.clients.delete(firstKey);
}
enforcePendingClientLimit() {
if (this.pendingClients.size < this.maxPendingClients)
return;
const firstKey = this.pendingClients.keys().next().value;
if (firstKey !== undefined)
this.pendingClients.delete(firstKey);
}
makeOpenOwnerKey(clientid, owner) {
return `${clientid}:${Buffer.from(owner).toString('hex')}`;
}
/**
* Validates a seqid from a client request against the owner's current seqid.
* Per RFC 7530 §9.1.7, the server expects seqid = last_seqid + 1 for new operations,
* or seqid = last_seqid for replayed requests (idempotent retry).
*
* @param requestSeqid - seqid from the client request
* @param ownerSeqid - current seqid stored for the owner
* @returns 'valid' if seqid matches expected next value, 'replay' if it matches last value, 'invalid' otherwise
*/
validateSeqid(requestSeqid, ownerSeqid) {
const nextSeqid = ownerSeqid === 0xffffffff ? 1 : ownerSeqid + 1;
if (requestSeqid === nextSeqid)
return 'valid';
if (requestSeqid === ownerSeqid)
return 'replay';
return 'invalid';
}
/**
* Renews the lease for a client.
* Per RFC 7530 §9.5, any stateful operation renews the client's lease.
*
* @param clientid - The client ID whose lease should be renewed
*/
renewClientLease(clientid) {
const client = this.clients.get(clientid);
if (client) {
client.lastRenew = Date.now();
}
}
makeStateidKey(stateid) {
return `${stateid.seqid}:${Buffer.from(stateid.other).toString('hex')}`;
}
createStateid() {
const seqid = this.nextStateidSeqid++;
const other = (0, node_crypto_1.randomBytes)(12);
return new struct.Nfsv4Stateid(seqid, other);
}
canAccessFile(path, shareAccess, shareDeny) {
for (const openFile of this.openFiles.values()) {
if (openFile.path !== path)
continue;
if ((openFile.shareDeny & shareAccess) !== 0)
return false;
if ((shareDeny & openFile.shareAccess) !== 0)
return false;
}
return true;
}
makeLockOwnerKey(clientid, owner) {
return `${clientid}:${Buffer.from(owner).toString('hex')}`;
}
makeOpenRequestKey(ownerKey, currentPath, request) {
const writer = new Writer_1.Writer(256);
const encoder = new XdrEncoder_1.XdrEncoder(writer);
request.encode(encoder);
const requestBytes = writer.flush();
const requestHex = Buffer.from(requestBytes).toString('hex');
return `OPEN:${ownerKey}:${currentPath}:${requestHex}`;
}
makeLockRequestKey(lockOwnerKey, filePath, locktype, offset, length, seqid) {
return `LOCK:${lockOwnerKey}:${filePath}:${locktype}:${offset.toString()}:${length.toString()}:${seqid}`;
}
makeLockuRequestKey(lockOwnerKey, stateid, offset, length, seqid) {
const stateidKey = this.makeStateidKey(stateid);
return `LOCKU:${lockOwnerKey}:${stateidKey}:${offset.toString()}:${length.toString()}:${seqid}`;
}
makeLockKey(stateid, offset, length) {
return `${this.makeStateidKey(stateid)}:${offset}:${length}`;
}
makeLockStateidKey(lockOwnerKey, path) {
return `${lockOwnerKey}:${path}`;
}
getOrCreateLockStateid(lockOwnerKey, path) {
const key = this.makeLockStateidKey(lockOwnerKey, path);
let lockStateid = this.lockStateids.get(key);
if (!lockStateid) {
const other = (0, node_crypto_1.randomBytes)(12);
lockStateid = new LockStateid_1.LockStateid(other, 1, lockOwnerKey, path);
this.lockStateids.set(key, lockStateid);
const otherKey = Buffer.from(other).toString('hex');
this.lockStateids.set(otherKey, lockStateid);
}
return lockStateid;
}
findLockStateidByOther(other) {
const otherKey = Buffer.from(other).toString('hex');
return this.lockStateids.get(otherKey);
}
hasConflictingLock(path, locktype, offset, length, ownerKey) {
const isWriteLock = locktype === 2 /* Nfsv4LockType.WRITE_LT */;
for (const lock of this.locks.values()) {
if (lock.path !== path)
continue;
if (!lock.overlaps(offset, length))
continue;
if (lock.lockOwnerKey === ownerKey)
continue;
if (isWriteLock || lock.locktype === 2 /* Nfsv4LockType.WRITE_LT */)
return true;
}
return false;
}
/**
* Establishes client ID or updates callback information.
* Returns a client ID and confirmation verifier for SETCLIENTID_CONFIRM.
*/
async SETCLIENTID(request, ctx) {
const principal = ctx.getPrincipal();
const verifier = request.client.verifier.data;
const clientIdString = request.client.id;
const callback = request.callback;
const callbackIdent = request.callbackIdent;
const confirmedClientEntry = this.findClientByIdString(this.clients, clientIdString);
let clientid = 0n;
if (confirmedClientEntry) {
const existingRecord = confirmedClientEntry[1];
if (existingRecord.principal !== principal)
return new msg.Nfsv4SetclientidResponse(10017 /* Nfsv4Stat.NFS4ERR_CLID_INUSE */);
this.pendingClients.delete(clientid);
clientid = confirmedClientEntry[0];
const verifierMatch = (0, cmpUint8Array_1.cmpUint8Array)(existingRecord.verifier, verifier);
if (verifierMatch) {
// The client is re-registering with the same ID string and verifier.
// Update callback information, return existing client ID and issue
// new confirm verifier.
}
else {
// The client is re-registering with the same ID string but different verifier.
clientid = this.nextClientId++;
}
}
else {
const pendingClientEntry = this.findClientByIdString(this.pendingClients, clientIdString);
if (pendingClientEntry) {
const existingRecord = pendingClientEntry[1];
if (existingRecord.principal !== principal)
return new msg.Nfsv4SetclientidResponse(10017 /* Nfsv4Stat.NFS4ERR_CLID_INUSE */);
const verifierMatch = (0, cmpUint8Array_1.cmpUint8Array)(existingRecord.verifier, verifier);
if (verifierMatch && existingRecord.cache) {
// The client is re-registering with the same ID string and verifier.
// Return cached response.
return existingRecord.cache;
}
}
// New client ID string. Create new client record.
clientid = this.nextClientId++;
}
const setclientidConfirm = (0, node_crypto_1.randomBytes)(8);
const verifierStruct = new struct.Nfsv4Verifier(setclientidConfirm);
const body = new msg.Nfsv4SetclientidResOk(clientid, verifierStruct);
const response = new msg.Nfsv4SetclientidResponse(0 /* Nfsv4Stat.NFS4_OK */, body);
const newRecord = new ClientRecord_1.ClientRecord(principal, verifier, clientIdString, callback, callbackIdent, setclientidConfirm, response);
// Remove any existing pending records with same ID string.
for (const [id, entry] of this.pendingClients.entries())
if ((0, cmpUint8Array_1.cmpUint8Array)(entry.clientIdString, clientIdString))
this.pendingClients.delete(id);
this.enforcePendingClientLimit();
this.pendingClients.set(clientid, newRecord);
return response;
}
/**
* Confirms a client ID established by SETCLIENTID.
* Transitions unconfirmed client record to confirmed state.
*/
async SETCLIENTID_CONFIRM(request, ctx) {
const { clients, pendingClients } = this;
const clientid = request.clientid;
const setclientidConfirm = request.setclientidConfirm.data;
const pendingRecord = pendingClients.get(clientid);
if (!pendingRecord) {
const confirmedRecord = this.clients.get(clientid);
if (confirmedRecord && (0, cmpUint8Array_1.cmpUint8Array)(confirmedRecord.setclientidConfirm, setclientidConfirm))
return new msg.Nfsv4SetclientidConfirmResponse(0 /* Nfsv4Stat.NFS4_OK */);
return new msg.Nfsv4SetclientidConfirmResponse(10022 /* Nfsv4Stat.NFS4ERR_STALE_CLIENTID */);
}
const principal = ctx.getPrincipal();
if (pendingRecord.principal !== principal)
return new msg.Nfsv4SetclientidConfirmResponse(10017 /* Nfsv4Stat.NFS4ERR_CLID_INUSE */);
if (!(0, cmpUint8Array_1.cmpUint8Array)(pendingRecord.setclientidConfirm, setclientidConfirm))
return new msg.Nfsv4SetclientidConfirmResponse(10022 /* Nfsv4Stat.NFS4ERR_STALE_CLIENTID */);
const oldConfirmed = this.findClientByIdString(this.clients, pendingRecord.clientIdString);
if (oldConfirmed) {
const clientid2 = oldConfirmed[0];
this.clients.delete(clientid2);
pendingClients.delete(clientid2);
}
this.clients.delete(clientid);
pendingClients.delete(clientid);
// Remove any existing pending records with same ID string.
const clientIdString = pendingRecord.clientIdString;
for (const [id, entry] of pendingClients.entries())
if ((0, cmpUint8Array_1.cmpUint8Array)(entry.clientIdString, clientIdString))
pendingClients.delete(id);
for (const [id, entry] of clients.entries())
if ((0, cmpUint8Array_1.cmpUint8Array)(entry.clientIdString, clientIdString))
clients.delete(id);
this.enforceClientLimit();
clients.set(clientid, pendingRecord);
return new msg.Nfsv4SetclientidConfirmResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async ILLEGAL(request, ctx) {
ctx.connection.logger.log('ILLEGAL', request);
return new msg.Nfsv4IllegalResponse(10044 /* Nfsv4Stat.NFS4ERR_OP_ILLEGAL */);
}
async PUTROOTFH(request, ctx) {
ctx.cfh = fh_1.ROOT_FH;
return new msg.Nfsv4PutrootfhResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async PUTPUBFH(request, ctx) {
ctx.cfh = fh_1.ROOT_FH;
return new msg.Nfsv4PutpubfhResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async PUTFH(request, ctx) {
const fh = request.object.data;
if (fh.length > 128 /* Nfsv4Const.FHSIZE */)
throw 10001 /* Nfsv4Stat.NFS4ERR_BADHANDLE */;
const valid = this.fh.validate(fh);
if (!valid)
throw 10001 /* Nfsv4Stat.NFS4ERR_BADHANDLE */;
ctx.cfh = fh;
return new msg.Nfsv4PutfhResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async GETFH(request, ctx) {
const cfh = ctx.cfh;
if (!cfh)
throw 10020 /* Nfsv4Stat.NFS4ERR_NOFILEHANDLE */;
const fh = new struct.Nfsv4Fh(cfh);
const body = new msg.Nfsv4GetfhResOk(fh);
return new msg.Nfsv4GetfhResponse(0 /* Nfsv4Stat.NFS4_OK */, body);
}
async RESTOREFH(request, ctx) {
if (!ctx.sfh)
throw 10030 /* Nfsv4Stat.NFS4ERR_RESTOREFH */;
ctx.cfh = ctx.sfh;
return new msg.Nfsv4RestorefhResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async SAVEFH(request, ctx) {
if (!ctx.cfh)
throw 10020 /* Nfsv4Stat.NFS4ERR_NOFILEHANDLE */;
ctx.sfh = ctx.cfh;
return new msg.Nfsv4SavefhResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
absolutePath(path) {
const dir = this.dir;
if (path === dir)
return dir;
if (path.startsWith(dir + NodePath.sep) || path.startsWith(dir + '/'))
return path;
const absolutePath = NodePath.join(dir, path);
if (absolutePath.length < dir.length)
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
if (!absolutePath.startsWith(dir))
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
return absolutePath;
}
async LOOKUP(request, ctx) {
const fh = this.fh;
const currentPath = fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const component = request.objname;
if (component.length === 0)
throw 22 /* Nfsv4Stat.NFS4ERR_INVAL */;
const promises = this.promises;
let stats;
try {
stats = await promises.stat(currentPathAbsolute);
}
catch (err) {
if ((0, util_1.isErrCode)('ENOENT', err))
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
throw 5 /* Nfsv4Stat.NFS4ERR_IO */;
}
if (stats.isSymbolicLink())
throw 10029 /* Nfsv4Stat.NFS4ERR_SYMLINK */;
if (!stats.isDirectory())
throw 20 /* Nfsv4Stat.NFS4ERR_NOTDIR */;
const targetAbsolutePath = NodePath.join(currentPathAbsolute, component);
try {
const targetStats = await promises.stat(targetAbsolutePath);
if (!targetStats)
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
}
catch (err) {
if ((0, util_1.isErrCode)('ENOENT', err))
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
if ((0, util_1.isErrCode)('EACCES', err))
throw 13 /* Nfsv4Stat.NFS4ERR_ACCESS */;
throw 5 /* Nfsv4Stat.NFS4ERR_IO */;
}
fh.setCfh(ctx, targetAbsolutePath);
return new msg.Nfsv4LookupResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async LOOKUPP(request, ctx) {
const fh = this.fh;
const currentPath = fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const promises = this.promises;
let stats;
try {
stats = await promises.stat(currentPathAbsolute);
}
catch (err) {
if ((0, util_1.isErrCode)('ENOENT', err))
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
throw 5 /* Nfsv4Stat.NFS4ERR_IO */;
}
if (!stats.isDirectory())
throw 20 /* Nfsv4Stat.NFS4ERR_NOTDIR */;
const parentAbsolutePath = NodePath.dirname(currentPathAbsolute);
if (parentAbsolutePath.length < this.dir.length)
throw 2 /* Nfsv4Stat.NFS4ERR_NOENT */;
fh.setCfh(ctx, parentAbsolutePath);
return new msg.Nfsv4LookuppResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async GETATTR(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const requestedAttrNums = (0, attributes_1.parseBitmask)(request.attrRequest.mask);
let stats;
if ((0, attributes_1.requiresLstat)(requestedAttrNums)) {
try {
if (ctx.connection.debug)
ctx.connection.logger.log('lstat', currentPathAbsolute);
stats = await this.promises.lstat(currentPathAbsolute);
}
catch (error) {
throw (0, util_1.normalizeNodeFsError)(error, ctx.connection.logger);
}
}
let fsStats;
if ((0, attributes_1.requiresFsStats)(requestedAttrNums)) {
try {
fsStats = await this.fsStats();
}
catch (error) {
ctx.connection.logger.error(error);
}
}
const attrs = (0, attrs_1.encodeAttrs)(request.attrRequest, stats, currentPath, ctx.cfh, this.leaseTime, fsStats);
return new msg.Nfsv4GetattrResponse(0 /* Nfsv4Stat.NFS4_OK */, new msg.Nfsv4GetattrResOk(attrs));
}
async ACCESS(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const promises = this.promises;
let stats;
try {
stats = await promises.lstat(currentPathAbsolute);
}
catch (error) {
throw (0, util_1.normalizeNodeFsError)(error, ctx.connection.logger);
}
const requestedAccess = request.access;
const isDirectory = stats.isDirectory();
const mode = stats.mode;
let supported = 0;
let access = 0;
if (requestedAccess & 1 /* Nfsv4Access.ACCESS4_READ */) {
supported |= 1 /* Nfsv4Access.ACCESS4_READ */;
if (mode & 0o444)
access |= 1 /* Nfsv4Access.ACCESS4_READ */;
}
if (requestedAccess & 2 /* Nfsv4Access.ACCESS4_LOOKUP */) {
supported |= 2 /* Nfsv4Access.ACCESS4_LOOKUP */;
if (isDirectory && mode & 0o111)
access |= 2 /* Nfsv4Access.ACCESS4_LOOKUP */;
}
if (requestedAccess & 4 /* Nfsv4Access.ACCESS4_MODIFY */) {
supported |= 4 /* Nfsv4Access.ACCESS4_MODIFY */;
if (mode & 0o222)
access |= 4 /* Nfsv4Access.ACCESS4_MODIFY */;
}
if (requestedAccess & 8 /* Nfsv4Access.ACCESS4_EXTEND */) {
supported |= 8 /* Nfsv4Access.ACCESS4_EXTEND */;
if (mode & 0o222)
access |= 8 /* Nfsv4Access.ACCESS4_EXTEND */;
}
if (requestedAccess & 16 /* Nfsv4Access.ACCESS4_DELETE */) {
if (!isDirectory) {
supported |= 0;
}
else {
supported |= 16 /* Nfsv4Access.ACCESS4_DELETE */;
if (mode & 0o222)
access |= 16 /* Nfsv4Access.ACCESS4_DELETE */;
}
}
if (requestedAccess & 32 /* Nfsv4Access.ACCESS4_EXECUTE */) {
supported |= 32 /* Nfsv4Access.ACCESS4_EXECUTE */;
if (!isDirectory && mode & 0o111)
access |= 32 /* Nfsv4Access.ACCESS4_EXECUTE */;
}
const body = new msg.Nfsv4AccessResOk(supported, access);
return new msg.Nfsv4AccessResponse(0 /* Nfsv4Stat.NFS4_OK */, body);
}
async READDIR(request, ctx) {
const fh = this.fh;
const currentPath = fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const promises = this.promises;
let stats;
try {
stats = await promises.lstat(currentPathAbsolute);
}
catch (error) {
throw (0, util_1.normalizeNodeFsError)(error, ctx.connection.logger);
}
if (!stats.isDirectory())
throw 20 /* Nfsv4Stat.NFS4ERR_NOTDIR */;
const cookie = request.cookie;
const requestedCookieverf = request.cookieverf.data;
const maxcount = request.maxcount;
const attrRequest = request.attrRequest;
let cookieverf;
if (cookie === 0n) {
cookieverf = new Uint8Array(8);
const changeTime = BigInt(Math.floor(stats.mtimeMs * 1000000));
const view = new DataView(cookieverf.buffer);
view.setBigUint64(0, changeTime, false);
}
else {
cookieverf = new Uint8Array(8);
const changeTime = BigInt(Math.floor(stats.mtimeMs * 1000000));
const view = new DataView(cookieverf.buffer);
view.setBigUint64(0, changeTime, false);
if (!(0, cmpUint8Array_1.cmpUint8Array)(requestedCookieverf, cookieverf))
throw 10027 /* Nfsv4Stat.NFS4ERR_NOT_SAME */;
}
let dirents;
try {
dirents = await promises.readdir(currentPathAbsolute, { withFileTypes: true });
}
catch (error) {
throw (0, util_1.normalizeNodeFsError)(error, ctx.connection.logger);
}
const entries = [];
let totalBytes = 0;
const overheadPerEntry = 32;
let startIndex = 0;
if (cookie > 0n) {
startIndex = Number(cookie) - 2;
if (startIndex < 0)
startIndex = 0;
if (startIndex > dirents.length)
startIndex = dirents.length;
}
let eof = true;
const fsStats = await this.fsStats();
for (let i = startIndex; i < dirents.length; i++) {
const dirent = dirents[i];
const name = dirent.name;
const entryCookie = BigInt(i + 3);
const entryPath = NodePath.join(currentPathAbsolute, name);
let entryStats;
try {
entryStats = await promises.lstat(entryPath);
}
catch (_error) {
continue;
}
const entryFh = fh.encode(entryPath);
const attrs = (0, attrs_1.encodeAttrs)(attrRequest, entryStats, entryPath, entryFh, this.leaseTime, fsStats);
const nameBytes = Buffer.byteLength(name, 'utf8');
const attrBytes = attrs.attrVals.length;
const entryBytes = overheadPerEntry + nameBytes + attrBytes;
if (totalBytes + entryBytes > maxcount && entries.length > 0) {
eof = false;
break;
}
const entry = new struct.Nfsv4Entry(entryCookie, name, attrs);
entries.push(entry);
totalBytes += entryBytes;
}
const cookieverf2 = new struct.Nfsv4Verifier(cookieverf);
const body = new msg.Nfsv4ReaddirResOk(cookieverf2, entries, eof);
return new msg.Nfsv4ReaddirResponse(0 /* Nfsv4Stat.NFS4_OK */, body);
}
async OPEN(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const ownerKey = this.makeOpenOwnerKey(request.owner.clientid, request.owner.owner);
this.renewClientLease(request.owner.clientid);
let ownerState = this.openOwners.get(ownerKey);
let replayCandidate = false;
let previousSeqid = ownerState?.seqid ?? 0;
if (!ownerState) {
ownerState = new OpenOwnerState_1.OpenOwnerState(request.owner.clientid, request.owner.owner, 0);
this.openOwners.set(ownerKey, ownerState);
previousSeqid = 0;
}
else {
const seqidValidation = this.validateSeqid(request.seqid, ownerState.seqid);
if (seqidValidation === 'invalid') {
if (request.seqid === 0) {
ownerState.seqid = 0;
previousSeqid = 0;
}
else {
return new msg.Nfsv4OpenResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
}
else if (seqidValidation === 'replay') {
replayCandidate = true;
}
}
if (request.claim.claimType !== 0 /* Nfsv4OpenClaimType.CLAIM_NULL */) {
return new msg.Nfsv4OpenResponse(10004 /* Nfsv4Stat.NFS4ERR_NOTSUPP */);
}
const claimNull = request.claim.claim;
const filename = claimNull.file;
const filePath = NodePath.join(currentPathAbsolute, filename);
const requestKey = this.makeOpenRequestKey(ownerKey, filePath, request);
if (replayCandidate) {
if (ownerState.lastRequestKey === requestKey && ownerState.lastResponse) {
return ownerState.lastResponse;
}
return new msg.Nfsv4OpenResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
ownerState.seqid = request.seqid;
const opentype = request.openhow.opentype;
const isCreate = opentype === 1 /* Nfsv4OpenFlags.OPEN4_CREATE */;
let fileExists = false;
try {
const stats = await this.promises.lstat(filePath);
if (!stats.isFile()) {
const response = new msg.Nfsv4OpenResponse(21 /* Nfsv4Stat.NFS4ERR_ISDIR */);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
fileExists = true;
}
catch (err) {
if ((0, util_1.isErrCode)('ENOENT', err)) {
if (!isCreate) {
const response = new msg.Nfsv4OpenResponse(2 /* Nfsv4Stat.NFS4ERR_NOENT */);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
}
else {
const status = (0, util_1.normalizeNodeFsError)(err, ctx.connection.logger);
const response = new msg.Nfsv4OpenResponse(status);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
}
if (fileExists && !this.canAccessFile(filePath, request.shareAccess, request.shareDeny)) {
ownerState.seqid = previousSeqid;
const response = new msg.Nfsv4OpenResponse(10015 /* Nfsv4Stat.NFS4ERR_SHARE_DENIED */);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
let flags = 0;
const isWrite = (request.shareAccess & 2 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE */) !== 0;
const isRead = (request.shareAccess & 1 /* Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ */) !== 0;
if (isCreate) {
flags = this.fs.constants.O_CREAT;
const createHow = request.openhow.how;
if (createHow && createHow.mode === 2 /* Nfsv4CreateMode.EXCLUSIVE4 */) {
flags |= this.fs.constants.O_EXCL;
}
}
if (isRead && isWrite) {
flags |= this.fs.constants.O_RDWR;
}
else if (isWrite) {
flags |= this.fs.constants.O_WRONLY;
}
else {
flags |= this.fs.constants.O_RDONLY;
}
try {
const fd = await this.promises.open(filePath, flags, 0o644);
const stateid = this.createStateid();
const stateidKey = this.makeStateidKey(stateid);
const openFile = new OpenFileState_1.OpenFileState(stateid, filePath, fd, request.shareAccess, request.shareDeny, ownerKey, ownerState.seqid, false);
this.openFiles.set(stateidKey, openFile);
ownerState.opens.add(stateidKey);
const fh = this.fh.encode(filePath);
ctx.cfh = fh;
const before = this.changeCounter;
const after = ++this.changeCounter;
const cinfo = new struct.Nfsv4ChangeInfo(true, before, after);
const attrset = new struct.Nfsv4Bitmap([]);
const delegation = new struct.Nfsv4OpenDelegation(0 /* Nfsv4DelegType.OPEN_DELEGATE_NONE */);
const resok = new msg.Nfsv4OpenResOk(stateid, cinfo, 0, attrset, delegation);
const response = new msg.Nfsv4OpenResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
catch (err) {
const status = (0, util_1.normalizeNodeFsError)(err, ctx.connection.logger);
const response = new msg.Nfsv4OpenResponse(status);
ownerState.lastResponse = response;
ownerState.lastRequestKey = requestKey;
return response;
}
}
async OPENATTR(request, ctx) {
return new msg.Nfsv4OpenattrResponse(10004 /* Nfsv4Stat.NFS4ERR_NOTSUPP */);
}
async OPEN_CONFIRM(request, ctx) {
const stateidKey = this.makeStateidKey(request.openStateid);
const openFile = this.openFiles.get(stateidKey);
if (!openFile)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const ownerState = this.openOwners.get(openFile.openOwnerKey);
if (!ownerState)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const seqidValidation = this.validateSeqid(request.seqid, ownerState.seqid);
if (seqidValidation === 'invalid')
throw 10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */;
if (seqidValidation === 'replay') {
const newStateid = new struct.Nfsv4Stateid(openFile.stateid.seqid, openFile.stateid.other);
const resok = new msg.Nfsv4OpenConfirmResOk(newStateid);
return new msg.Nfsv4OpenConfirmResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
ownerState.seqid = request.seqid;
openFile.confirmed = true;
const newSeqid = this.nextStateidSeqid++;
const newStateid = new struct.Nfsv4Stateid(newSeqid, openFile.stateid.other);
const oldKey = stateidKey;
const newKey = this.makeStateidKey(newStateid);
const updatedOpenFile = new OpenFileState_1.OpenFileState(newStateid, openFile.path, openFile.fd, openFile.shareAccess, openFile.shareDeny, openFile.openOwnerKey, ownerState.seqid, true);
this.openFiles.delete(oldKey);
this.openFiles.set(newKey, updatedOpenFile);
ownerState.opens.delete(oldKey);
ownerState.opens.add(newKey);
const resok = new msg.Nfsv4OpenConfirmResOk(newStateid);
return new msg.Nfsv4OpenConfirmResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
async OPEN_DOWNGRADE(request, ctx) {
const stateidKey = this.makeStateidKey(request.openStateid);
const openFile = this.openFiles.get(stateidKey);
if (!openFile)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const ownerState = this.openOwners.get(openFile.openOwnerKey);
if (!ownerState)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const seqidValidation = this.validateSeqid(request.seqid, ownerState.seqid);
if (seqidValidation === 'invalid')
throw 10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */;
if (seqidValidation === 'replay') {
const newStateid = new struct.Nfsv4Stateid(openFile.stateid.seqid, openFile.stateid.other);
const resok = new msg.Nfsv4OpenDowngradeResOk(newStateid);
return new msg.Nfsv4OpenDowngradeResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
ownerState.seqid = request.seqid;
if ((request.shareAccess & ~openFile.shareAccess) !== 0)
throw 22 /* Nfsv4Stat.NFS4ERR_INVAL */;
if ((request.shareDeny & ~openFile.shareDeny) !== 0)
throw 22 /* Nfsv4Stat.NFS4ERR_INVAL */;
const newSeqid = this.nextStateidSeqid++;
const newStateid = new struct.Nfsv4Stateid(newSeqid, openFile.stateid.other);
const oldKey = stateidKey;
const newKey = this.makeStateidKey(newStateid);
const updatedOpenFile = new OpenFileState_1.OpenFileState(newStateid, openFile.path, openFile.fd, request.shareAccess, request.shareDeny, openFile.openOwnerKey, ownerState.seqid, openFile.confirmed);
this.openFiles.delete(oldKey);
this.openFiles.set(newKey, updatedOpenFile);
ownerState.opens.delete(oldKey);
ownerState.opens.add(newKey);
const resok = new msg.Nfsv4OpenDowngradeResOk(newStateid);
return new msg.Nfsv4OpenDowngradeResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
async CLOSE(request, ctx) {
const stateidKey = this.makeStateidKey(request.openStateid);
const openFile = this.openFiles.get(stateidKey);
if (!openFile) {
return new msg.Nfsv4CloseResponse(0 /* Nfsv4Stat.NFS4_OK */, new msg.Nfsv4CloseResOk(request.openStateid));
}
const ownerState = this.openOwners.get(openFile.openOwnerKey);
if (!ownerState) {
return new msg.Nfsv4CloseResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
}
this.renewClientLease(ownerState.clientid);
const seqidValidation = this.validateSeqid(request.seqid, ownerState.seqid);
if (seqidValidation === 'invalid') {
return new msg.Nfsv4CloseResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
if (seqidValidation === 'replay') {
const newStateid = new struct.Nfsv4Stateid(openFile.stateid.seqid, openFile.stateid.other);
const resok = new msg.Nfsv4CloseResOk(newStateid);
return new msg.Nfsv4CloseResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
ownerState.seqid = request.seqid;
try {
const handle = openFile.fd;
if (handle && typeof handle.close === 'function') {
await handle.close();
}
}
catch (err) {
const status = (0, util_1.normalizeNodeFsError)(err, ctx.connection.logger);
if (status !== 2 /* Nfsv4Stat.NFS4ERR_NOENT */) {
return new msg.Nfsv4CloseResponse(status);
}
}
ownerState.opens.delete(stateidKey);
if (ownerState.opens.size === 0) {
this.openOwners.delete(openFile.openOwnerKey);
}
this.openFiles.delete(stateidKey);
const newSeqid = this.nextStateidSeqid++;
const newStateid = new struct.Nfsv4Stateid(newSeqid, openFile.stateid.other);
const resok = new msg.Nfsv4CloseResOk(newStateid);
return new msg.Nfsv4CloseResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
async SECINFO(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const currentPathAbsolute = this.absolutePath(currentPath);
const filePath = NodePath.join(currentPathAbsolute, request.name);
try {
await this.promises.lstat(filePath);
}
catch (err) {
if ((0, util_1.isErrCode)(err, 'ENOENT')) {
return new msg.Nfsv4SecinfoResponse(2 /* Nfsv4Stat.NFS4ERR_NOENT */);
}
const status = (0, util_1.normalizeNodeFsError)(err, ctx.connection.logger);
return new msg.Nfsv4SecinfoResponse(status);
}
const flavors = [new struct.Nfsv4SecInfoFlavor(1)];
const resok = new msg.Nfsv4SecinfoResOk(flavors);
return new msg.Nfsv4SecinfoResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
async LOCK(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const { locktype, offset, length, locker } = request;
if (!locker.newLockOwner) {
const existingOwner = locker.owner;
const stateidKey = this.makeStateidKey(existingOwner.lockStateid);
let existingLockOwnerKey;
for (const lock of this.locks.values()) {
if (this.makeStateidKey(lock.stateid) === stateidKey) {
existingLockOwnerKey = lock.lockOwnerKey;
break;
}
}
if (!existingLockOwnerKey) {
return new msg.Nfsv4LockResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
}
const lockOwnerState = this.lockOwners.get(existingLockOwnerKey);
if (!lockOwnerState) {
return new msg.Nfsv4LockResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
}
this.renewClientLease(lockOwnerState.clientid);
const seqidValidation = this.validateSeqid(existingOwner.lockSeqid, lockOwnerState.seqid);
const requestKey = this.makeLockRequestKey(existingLockOwnerKey, currentPath, locktype, offset, length, existingOwner.lockSeqid);
if (seqidValidation === 'invalid') {
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
if (seqidValidation === 'replay') {
if (lockOwnerState.lastRequestKey !== requestKey) {
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
if (lockOwnerState.lastResponse)
return lockOwnerState.lastResponse;
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
lockOwnerState.seqid = existingOwner.lockSeqid;
if (this.hasConflictingLock(currentPath, locktype, offset, length, existingLockOwnerKey)) {
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
const denied = new msg.Nfsv4LockResDenied(offset, length, locktype, conflictOwner);
return new msg.Nfsv4LockResponse(10010 /* Nfsv4Stat.NFS4ERR_DENIED */, undefined, denied);
}
const lockStateid = this.getOrCreateLockStateid(existingLockOwnerKey, currentPath);
const stateid = lockStateid.incrementAndGetStateid();
const lock = new ByteRangeLock_1.ByteRangeLock(stateid, currentPath, locktype, offset, length, existingLockOwnerKey);
const lockKey = this.makeLockKey(stateid, offset, length);
this.locks.set(lockKey, lock);
lockOwnerState.locks.add(lockKey);
const resok = new msg.Nfsv4LockResOk(stateid);
const response = new msg.Nfsv4LockResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
lockOwnerState.lastResponse = response;
lockOwnerState.lastRequestKey = requestKey;
return response;
}
const newOwner = locker.owner;
const openToLock = newOwner.openToLockOwner;
const lockOwnerData = openToLock.lockOwner;
const openStateidKey = this.makeStateidKey(openToLock.openStateid);
const openFile = this.openFiles.get(openStateidKey);
if (!openFile) {
return new msg.Nfsv4LockResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
}
const openOwnerKey = openFile.openOwnerKey;
const openOwnerState = this.openOwners.get(openOwnerKey);
if (!openOwnerState) {
return new msg.Nfsv4LockResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
}
this.renewClientLease(lockOwnerData.clientid);
const seqidValidation = this.validateSeqid(openToLock.openSeqid, openOwnerState.seqid);
if (seqidValidation === 'invalid') {
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
if (seqidValidation === 'replay') {
for (const [_key, lock] of this.locks.entries()) {
if (lock.lockOwnerKey === this.makeLockOwnerKey(lockOwnerData.clientid, lockOwnerData.owner) &&
lock.path === currentPath &&
lock.offset === offset &&
lock.length === length) {
const resok = new msg.Nfsv4LockResOk(lock.stateid);
return new msg.Nfsv4LockResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
}
}
openOwnerState.seqid = openToLock.openSeqid;
const lockOwnerKey = this.makeLockOwnerKey(lockOwnerData.clientid, lockOwnerData.owner);
const lockRequestKey = this.makeLockRequestKey(lockOwnerKey, currentPath, locktype, offset, length, openToLock.lockSeqid);
if (this.hasConflictingLock(currentPath, locktype, offset, length, lockOwnerKey)) {
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
const denied = new msg.Nfsv4LockResDenied(offset, length, locktype, conflictOwner);
return new msg.Nfsv4LockResponse(10010 /* Nfsv4Stat.NFS4ERR_DENIED */, undefined, denied);
}
let lockOwnerState = this.lockOwners.get(lockOwnerKey);
if (!lockOwnerState) {
if (openToLock.lockSeqid !== 0) {
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
lockOwnerState = new LockOwnerState_1.LockOwnerState(lockOwnerData.clientid, lockOwnerData.owner, 0);
this.lockOwners.set(lockOwnerKey, lockOwnerState);
}
else {
const lockSeqidValidation = this.validateSeqid(openToLock.lockSeqid, lockOwnerState.seqid);
if (lockSeqidValidation === 'invalid') {
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
if (lockSeqidValidation === 'replay') {
if (lockOwnerState.lastRequestKey === lockRequestKey && lockOwnerState.lastResponse) {
return lockOwnerState.lastResponse;
}
return new msg.Nfsv4LockResponse(10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */);
}
}
lockOwnerState.seqid = openToLock.lockSeqid;
const lockStateid = this.getOrCreateLockStateid(lockOwnerKey, currentPath);
const stateid = lockStateid.incrementAndGetStateid();
const lock = new ByteRangeLock_1.ByteRangeLock(stateid, currentPath, locktype, offset, length, lockOwnerKey);
const lockKey = this.makeLockKey(stateid, offset, length);
this.locks.set(lockKey, lock);
lockOwnerState.locks.add(lockKey);
const resok = new msg.Nfsv4LockResOk(stateid);
const response = new msg.Nfsv4LockResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
lockOwnerState.lastResponse = response;
lockOwnerState.lastRequestKey = lockRequestKey;
return response;
}
async LOCKT(request, ctx) {
const currentPath = this.fh.currentPath(ctx);
const { locktype, offset, length, owner } = request;
const ownerKey = this.makeLockOwnerKey(owner.clientid, owner.owner);
if (this.hasConflictingLock(currentPath, locktype, offset, length, ownerKey)) {
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
const denied = new msg.Nfsv4LocktResDenied(offset, length, locktype, conflictOwner);
return new msg.Nfsv4LocktResponse(10010 /* Nfsv4Stat.NFS4ERR_DENIED */, denied);
}
return new msg.Nfsv4LocktResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async LOCKU(request, ctx) {
const { lockStateid, offset, length, seqid } = request;
const lockStateidState = this.findLockStateidByOther(lockStateid.other);
if (!lockStateidState)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const ownerKey = lockStateidState.lockOwnerKey;
const lockOwnerState = this.lockOwners.get(ownerKey);
if (!lockOwnerState)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
this.renewClientLease(lockOwnerState.clientid);
const currentPath = this.fh.currentPath(ctx);
if (lockStateidState.path !== currentPath)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
const requestKey = this.makeLockuRequestKey(ownerKey, lockStateid, offset, length, seqid);
const seqidValidation = this.validateSeqid(seqid, lockOwnerState.seqid);
if (seqidValidation === 'invalid') {
throw 10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */;
}
if (seqidValidation === 'replay') {
if (lockOwnerState.lastRequestKey === requestKey && lockOwnerState.lastResponse) {
return lockOwnerState.lastResponse;
}
throw 10026 /* Nfsv4Stat.NFS4ERR_BAD_SEQID */;
}
lockOwnerState.seqid = seqid;
const lockKey = this.makeLockKey(lockStateid, offset, length);
const lock = this.locks.get(lockKey);
if (lock) {
this.locks.delete(lockKey);
lockOwnerState.locks.delete(lockKey);
}
const stateid = lockStateidState.incrementAndGetStateid();
const resok = new msg.Nfsv4LockuResOk(stateid);
const response = new msg.Nfsv4LockuResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
lockOwnerState.lastResponse = response;
lockOwnerState.lastRequestKey = requestKey;
return response;
}
async RELEASE_LOCKOWNER(request, ctx) {
const { lockOwner } = request;
const ownerKey = this.makeLockOwnerKey(lockOwner.clientid, lockOwner.owner);
const lockOwnerState = this.lockOwners.get(ownerKey);
if (!lockOwnerState)
throw 10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */;
for (const lockKey of lockOwnerState.locks)
this.locks.delete(lockKey);
this.lockOwners.delete(ownerKey);
return new msg.Nfsv4ReleaseLockOwnerResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async RENEW(request, ctx) {
const clientid = request.clientid;
const client = this.clients.get(clientid);
if (!client)
throw 10022 /* Nfsv4Stat.NFS4ERR_STALE_CLIENTID */;
return new msg.Nfsv4RenewResponse(0 /* Nfsv4Stat.NFS4_OK */);
}
async READ(request, ctx) {
const stateidKey = this.makeStateidKey(request.stateid);
const openFile = this.openFiles.get(stateidKey);
if (!openFile)
return new msg.Nfsv4ReadResponse(10025 /* Nfsv4Stat.NFS4ERR_BAD_STATEID */);
const fdHandle = openFile.fd;
// If we have an fd-like handle, use its .read; otherwise open the path
let fd;
let openedHere = false;
try {
if (fdHandle && typeof fdHandle.read === 'function') {
fd = fdHandle;
}
else {
fd = await this.promises.open(openFile.path, this.fs.constants.O_RDONLY);
openedHere = true;
}
const buf = Buffer.alloc(request.count);
const { bytesRead } = await fd.read(buf, 0, request.count, Number(request.offset));
const eof = bytesRead < request.count;
const data = buf.slice(0, bytesRead);
const resok = new msg.Nfsv4ReadResOk(eof, data);
return new msg.Nfsv4ReadResponse(0 /* Nfsv4Stat.NFS4_OK */, resok);
}
catch (err) {
const status = (0, util_1.normalizeNodeFsError)(err, ctx.connection.logger);
retu