Skip to content

Commit 2aad186

Browse files
committed
feat: 🎸 improve handling of file OPEN flags
1 parent d7dc7df commit 2aad186

File tree

15 files changed

+200
-50
lines changed

15 files changed

+200
-50
lines changed

src/nfs/v4/Nfsv4Decoder.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
22
import {XdrDecoder} from '../../xdr/XdrDecoder';
3-
import {Nfsv4Op, Nfsv4CbOp, Nfsv4FType, Nfsv4DelegType, Nfsv4Stat} from './constants';
3+
import {Nfsv4Op, Nfsv4CbOp, Nfsv4FType, Nfsv4DelegType, Nfsv4Stat, Nfsv4CreateMode, Nfsv4OpenFlags} from './constants';
44
import {Nfsv4DecodingError} from './errors';
55
import * as msg from './messages';
66
import * as structs from './structs';
@@ -343,6 +343,32 @@ export class Nfsv4Decoder {
343343
}
344344
}
345345

346+
private readOpenHow(): structs.Nfsv4OpenHow {
347+
const xdr = this.xdr;
348+
const opentype = xdr.readUnsignedInt();
349+
if (opentype === Nfsv4OpenFlags.OPEN4_NOCREATE) return new structs.Nfsv4OpenHow(opentype);
350+
const mode = xdr.readUnsignedInt();
351+
switch (mode) {
352+
case Nfsv4CreateMode.UNCHECKED4:
353+
case Nfsv4CreateMode.GUARDED4: {
354+
const createattrs = this.readFattr();
355+
return new structs.Nfsv4OpenHow(
356+
opentype,
357+
new structs.Nfsv4CreateHow(mode, new structs.Nfsv4CreateAttrs(createattrs)),
358+
);
359+
}
360+
case Nfsv4CreateMode.EXCLUSIVE4: {
361+
const createverf = this.readVerifier();
362+
return new structs.Nfsv4OpenHow(
363+
opentype,
364+
new structs.Nfsv4CreateHow(mode, new structs.Nfsv4CreateVerf(createverf)),
365+
);
366+
}
367+
default:
368+
throw new Nfsv4DecodingError(`Unknown create mode: ${mode}`);
369+
}
370+
}
371+
346372
private readOpenDelegation(): structs.Nfsv4OpenDelegation {
347373
const xdr = this.xdr;
348374
const delegationType = xdr.readUnsignedInt() as Nfsv4DelegType;
@@ -652,7 +678,7 @@ export class Nfsv4Decoder {
652678
const shareAccess = xdr.readUnsignedInt();
653679
const shareDeny = xdr.readUnsignedInt();
654680
const owner = this.readOpenOwner();
655-
const openhow = xdr.readUnsignedInt();
681+
const openhow = this.readOpenHow();
656682
const claim = this.readOpenClaim();
657683
return new msg.Nfsv4OpenRequest(seqid, shareAccess, shareDeny, owner, openhow, claim);
658684
}

src/nfs/v4/__tests__/roundtrip.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ describe('roundtrip all NFSv4 operations', () => {
337337
const {encoder, decoder} = makeCodec();
338338
const owner = new structs.Nfsv4OpenOwner(BigInt(11), new Uint8Array([11]));
339339
const claim = new structs.Nfsv4OpenClaim(0, new structs.Nfsv4OpenClaimNull(''));
340-
const req = new msg.Nfsv4OpenRequest(1, 2, 3, owner, 0, claim);
340+
const req = new msg.Nfsv4OpenRequest(1, 2, 3, owner, new structs.Nfsv4OpenHow(0), claim);
341341
const creq = new msg.Nfsv4CompoundRequest('', 0, [req]);
342342
const encoded = encoder.encodeCompound(creq, true);
343343
const decoded = decoder.decodeCompound(new Reader(encoded), true) as msg.Nfsv4CompoundRequest;

src/nfs/v4/builder.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {attrNumsToBitmap} from './attributes';
22
import * as msg from './messages';
33
import * as structs from './structs';
4-
import {Nfsv4FType} from './constants';
4+
import {Nfsv4CreateMode, Nfsv4FType, Nfsv4OpenFlags} from './constants';
55

66
/**
77
* Static builder helpers for NFS v4 operations.
@@ -252,15 +252,15 @@ export const nfs = {
252252
* @param shareAccess - Share access mode (OPEN4_SHARE_ACCESS_*)
253253
* @param shareDeny - Share deny mode (OPEN4_SHARE_DENY_*)
254254
* @param owner - Open owner (clientid + owner bytes)
255-
* @param openhow - Open mode (0 for OPEN4_NOCREATE)
255+
* @param openhow - Open how structure (use OpenHow helper)
256256
* @param claim - Open claim (use OpenClaim helper)
257257
*/
258258
OPEN(
259259
seqid: number,
260260
shareAccess: number,
261261
shareDeny: number,
262262
owner: structs.Nfsv4OpenOwner,
263-
openhow: number,
263+
openhow: structs.Nfsv4OpenHow,
264264
claim: structs.Nfsv4OpenClaim,
265265
): msg.Nfsv4OpenRequest {
266266
return new msg.Nfsv4OpenRequest(seqid, shareAccess, shareDeny, owner, openhow, claim);
@@ -455,6 +455,42 @@ export const nfs = {
455455
return new structs.Nfsv4OpenClaim(0, new structs.Nfsv4OpenClaimNull(filename));
456456
},
457457

458+
/**
459+
* Create Nfsv4OpenHow for OPEN4_NOCREATE (open existing file).
460+
*/
461+
OpenHowNoCreate(): structs.Nfsv4OpenHow {
462+
return new structs.Nfsv4OpenHow(Nfsv4OpenFlags.OPEN4_NOCREATE);
463+
},
464+
465+
/**
466+
* Create Nfsv4OpenHow for OPEN4_CREATE with UNCHECKED4 mode.
467+
* @param createattrs - Optional file attributes to set on create
468+
*/
469+
OpenHowCreateUnchecked(createattrs?: structs.Nfsv4Fattr): structs.Nfsv4OpenHow {
470+
const attrs = createattrs || new structs.Nfsv4Fattr(new structs.Nfsv4Bitmap([]), new Uint8Array(0));
471+
const how = new structs.Nfsv4CreateHow(Nfsv4CreateMode.UNCHECKED4, new structs.Nfsv4CreateAttrs(attrs));
472+
return new structs.Nfsv4OpenHow(Nfsv4OpenFlags.OPEN4_CREATE, how);
473+
},
474+
475+
/**
476+
* Create Nfsv4OpenHow for OPEN4_CREATE with GUARDED4 mode.
477+
* @param createattrs - Optional file attributes to set on create
478+
*/
479+
OpenHowCreateGuarded(createattrs?: structs.Nfsv4Fattr): structs.Nfsv4OpenHow {
480+
const attrs = createattrs || new structs.Nfsv4Fattr(new structs.Nfsv4Bitmap([]), new Uint8Array(0));
481+
const how = new structs.Nfsv4CreateHow(Nfsv4CreateMode.GUARDED4, new structs.Nfsv4CreateAttrs(attrs));
482+
return new structs.Nfsv4OpenHow(Nfsv4OpenFlags.OPEN4_CREATE, how);
483+
},
484+
485+
/**
486+
* Create Nfsv4OpenHow for OPEN4_CREATE with EXCLUSIVE4 mode.
487+
* @param verifier - 8-byte verifier for exclusive create
488+
*/
489+
OpenHowCreateExclusive(verifier: structs.Nfsv4Verifier): structs.Nfsv4OpenHow {
490+
const how = new structs.Nfsv4CreateHow(Nfsv4CreateMode.EXCLUSIVE4, new structs.Nfsv4CreateVerf(verifier));
491+
return new structs.Nfsv4OpenHow(Nfsv4OpenFlags.OPEN4_CREATE, how);
492+
},
493+
458494
/**
459495
* Create Nfsv4LockOwner (lock owner identifier).
460496
* @param clientid - Client ID

src/nfs/v4/client/Nfsv4FsClient.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ export class Nfsv4FsClient implements NfsFsClient {
8686
const openOwner = nfs.OpenOwner(BigInt(1), new Uint8Array([1, 2, 3, 4]));
8787
const claim = nfs.OpenClaimNull(filename);
8888
operations.push(
89-
nfs.OPEN(0, Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ, Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE, openOwner, 0, claim),
89+
nfs.OPEN(
90+
0,
91+
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
92+
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
93+
openOwner,
94+
nfs.OpenHowNoCreate(),
95+
claim,
96+
),
9097
);
9198
const openResponse = await this.fs.compound(operations);
9299
if (openResponse.status !== Nfsv4Stat.NFS4_OK) {
@@ -146,7 +153,7 @@ export class Nfsv4FsClient implements NfsFsClient {
146153
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE,
147154
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
148155
openOwner,
149-
Nfsv4OpenFlags.OPEN4_CREATE,
156+
nfs.OpenHowCreateUnchecked(),
150157
claim,
151158
),
152159
);
@@ -397,7 +404,14 @@ export class Nfsv4FsClient implements NfsFsClient {
397404
const openOwner = nfs.OpenOwner(BigInt(1), new Uint8Array([1, 2, 3, 4]));
398405
const claim = nfs.OpenClaimNull(filename);
399406
operations.push(
400-
nfs.OPEN(0, Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE, Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE, openOwner, 0, claim),
407+
nfs.OPEN(
408+
0,
409+
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE,
410+
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
411+
openOwner,
412+
nfs.OpenHowNoCreate(),
413+
claim,
414+
),
401415
);
402416
const attrNums = [Nfsv4Attr.FATTR4_SIZE];
403417
const attrMask = this.attrNumsToBitmap(attrNums);
@@ -831,7 +845,9 @@ export class Nfsv4FsClient implements NfsFsClient {
831845
break;
832846
}
833847
}
834-
operations.push(nfs.OPEN(openFlags, access, Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE, openOwner, 0, claim));
848+
operations.push(
849+
nfs.OPEN(openFlags, access, Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE, openOwner, nfs.OpenHowNoCreate(), claim),
850+
);
835851
const openResponse = await this.fs.compound(operations);
836852
if (openResponse.status !== Nfsv4Stat.NFS4_OK) {
837853
throw new Error(`Failed to open file: ${openResponse.status}`);

src/nfs/v4/format.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -606,13 +606,17 @@ export const formatNfsv4Request = (req: msg.Nfsv4Request, tab: string = ''): str
606606
} else if (req instanceof msg.Nfsv4NverifyRequest) {
607607
return 'NVERIFY' + printTree(tab, [(tab) => `attrs = ${formatNfsv4Bitmap(req.objAttributes.attrmask)}`]);
608608
} else if (req instanceof msg.Nfsv4OpenRequest) {
609-
return (
610-
'OPEN' +
611-
printTree(tab, [
612-
(tab) => `seqid = ${req.seqid}`,
613-
(tab) => `claim = ${formatNfsv4OpenClaimType(req.claim.claimType)}`,
614-
])
615-
);
609+
const items: Array<(tab: string) => string> = [
610+
(tab) => `seqid = ${req.seqid}`,
611+
(tab) => `shareAccess = ${formatNfsv4OpenAccess(req.shareAccess)}`,
612+
(tab) => `shareDeny = ${formatNfsv4OpenDeny(req.shareDeny)}`,
613+
(tab) => `opentype = ${formatNfsv4OpenFlags(req.openhow.opentype)}`,
614+
];
615+
if (req.openhow.how) {
616+
items.push((tab) => `createmode = ${formatNfsv4CreateMode(req.openhow.how!.mode)}`);
617+
}
618+
items.push((tab) => `claim = ${formatNfsv4OpenClaimType(req.claim.claimType)}`);
619+
return 'OPEN' + printTree(tab, items);
616620
} else if (req instanceof msg.Nfsv4OpenattrRequest) {
617621
return 'OPENATTR' + printTree(tab, [(tab) => `createdir = ${req.createdir}`]);
618622
} else if (req instanceof msg.Nfsv4OpenConfirmRequest) {

src/nfs/v4/messages.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ export class Nfsv4OpenRequest implements XdrType {
553553
public readonly shareAccess: number,
554554
public readonly shareDeny: number,
555555
public readonly owner: structs.Nfsv4OpenOwner,
556-
public readonly openhow: number,
556+
public readonly openhow: structs.Nfsv4OpenHow,
557557
public readonly claim: structs.Nfsv4OpenClaim,
558558
) {}
559559

@@ -563,7 +563,7 @@ export class Nfsv4OpenRequest implements XdrType {
563563
xdr.writeUnsignedInt(this.shareAccess);
564564
xdr.writeUnsignedInt(this.shareDeny);
565565
this.owner.encode(xdr);
566-
xdr.writeUnsignedInt(this.openhow);
566+
this.openhow.encode(xdr);
567567
this.claim.encode(xdr);
568568
}
569569
}

src/nfs/v4/server/operations/node/Nfsv4OperationsNode.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Nfsv4LockType,
1212
Nfsv4OpenFlags,
1313
Nfsv4FType,
14+
Nfsv4CreateMode,
1415
} from '../../../constants';
1516
import {Nfsv4OperationCtx, Nfsv4Operations} from '../Nfsv4Operations';
1617
import * as msg from '../../../messages';
@@ -582,6 +583,8 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
582583
const claimNull = request.claim.claim as struct.Nfsv4OpenClaimNull;
583584
const filename = claimNull.file;
584585
const filePath = NodePath.join(currentPathAbsolute, filename);
586+
const opentype = request.openhow.opentype;
587+
const isCreate = opentype === Nfsv4OpenFlags.OPEN4_CREATE;
585588
let fileExists = false;
586589
try {
587590
const stats = await this.promises.lstat(filePath);
@@ -591,7 +594,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
591594
fileExists = true;
592595
} catch (err) {
593596
if (isErrCode('ENOENT', err)) {
594-
if (request.openhow !== Nfsv4OpenFlags.OPEN4_CREATE) {
597+
if (!isCreate) {
595598
return new msg.Nfsv4OpenResponse(Nfsv4Stat.NFS4ERR_NOENT);
596599
}
597600
} else {
@@ -605,8 +608,12 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
605608
let flags = 0;
606609
const isWrite = (request.shareAccess & Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE) !== 0;
607610
const isRead = (request.shareAccess & Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ) !== 0;
608-
if (request.openhow === 1) {
611+
if (isCreate) {
609612
flags = this.fs.constants.O_CREAT;
613+
const createHow = request.openhow.how;
614+
if (createHow && createHow.mode === Nfsv4CreateMode.EXCLUSIVE4) {
615+
flags |= this.fs.constants.O_EXCL;
616+
}
610617
}
611618
if (isRead && isWrite) {
612619
flags |= this.fs.constants.O_RDWR;

src/nfs/v4/server/operations/node/__tests__/CLOSE.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('CLOSE operation', () => {
1414
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
1515
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
1616
openOwner,
17-
0,
17+
nfs.OpenHowNoCreate(),
1818
claim,
1919
);
2020
const openResponse = await client.compound([nfs.PUTROOTFH(), openReq]);
@@ -41,7 +41,7 @@ describe('CLOSE operation', () => {
4141
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
4242
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
4343
openOwner,
44-
0,
44+
nfs.OpenHowNoCreate(),
4545
claim,
4646
);
4747
const openResponse = await client.compound([nfs.PUTROOTFH(), openReq]);
@@ -65,7 +65,7 @@ describe('CLOSE operation', () => {
6565
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
6666
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
6767
openOwner,
68-
0,
68+
nfs.OpenHowNoCreate(),
6969
claim,
7070
);
7171
const openResponse = await client.compound([nfs.PUTROOTFH(), openReq]);
@@ -87,7 +87,7 @@ describe('CLOSE operation', () => {
8787
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
8888
Nfsv4OpenDeny.OPEN4_SHARE_DENY_WRITE,
8989
openOwner1,
90-
0,
90+
nfs.OpenHowNoCreate(),
9191
claim1,
9292
);
9393
const openResponse1 = await client.compound([nfs.PUTROOTFH(), openReq1]);
@@ -101,7 +101,7 @@ describe('CLOSE operation', () => {
101101
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_WRITE,
102102
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
103103
openOwner2,
104-
0,
104+
nfs.OpenHowNoCreate(),
105105
claim2,
106106
);
107107
const openResponse2 = await client.compound([nfs.PUTROOTFH(), openReq2]);
@@ -127,7 +127,7 @@ describe('CLOSE operation', () => {
127127
Nfsv4OpenAccess.OPEN4_SHARE_ACCESS_READ,
128128
Nfsv4OpenDeny.OPEN4_SHARE_DENY_NONE,
129129
openOwner,
130-
0,
130+
nfs.OpenHowNoCreate(),
131131
claim,
132132
);
133133
const openResponse = await client.compound([nfs.PUTROOTFH(), openReq]);

src/nfs/v4/server/operations/node/__tests__/COMMIT.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ describe('COMMIT operation', () => {
88
const {client, stop, vol} = await setupNfsClientServerTestbed();
99
const openOwner = nfs.OpenOwner(BigInt(1), new Uint8Array([1]));
1010
const claim = nfs.OpenClaimNull('file.txt');
11-
const openRes = await client.compound([nfs.PUTROOTFH(), nfs.OPEN(0, 2, 0, openOwner, 0, claim)]);
11+
const openRes = await client.compound([
12+
nfs.PUTROOTFH(),
13+
nfs.OPEN(0, 2, 0, openOwner, nfs.OpenHowNoCreate(), claim),
14+
]);
1215
const stateid = (openRes.resarray[1] as any).resok.stateid;
1316
const data = new Uint8Array(Buffer.from('COMMITTED'));
1417
const writeReq = nfs.WRITE(stateid, BigInt(0), Nfsv4StableHow.UNSTABLE4, data);

0 commit comments

Comments
 (0)