mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
net/nfs: NFSv4.1 and 4.2 sessions; version 4 takes the newest the server offers
The v4 client spoke 4.0 alone, which servers that have dropped it - Windows Server among them - refuse. With version 4 chosen it now asks for 4.2, then 4.1, and settles on 4.0 only when the server answers neither. From 4.1 the client identifies itself with EXCHANGE_ID, opens a session with CREATE_SESSION and says RECLAIM_COMPLETE, and every request after that leads with a SEQUENCE on a session slot, which also keeps the lease alive. Pipelined READs each take a slot of their own, as many as the server grants. A session lost while idle is set up again once; the server lets a session lapse with its lease when the connection closes. The Ganesha matrix runs every case against servers offering 4.0, 4.1, 4.2 and all three, each round on a fresh export, and checks the version the client settled on.
This commit is contained in:
1 parent
0da22ec5f9
commit
c9852fd7e3
7 files changed
+392
-111
No files matched your search
+1
-1
@@ -95,7 +95,7 @@
|
||||
- NETWORK: MCP server: AI assistants can drive RetroArch through the Model Context Protocol, every network command a tool (off by default, see docs/mcp-server.md)
|
||||
- NETWORK: Built-in TLS 1.2/1.3 client; mbedTLS and BearSSL are no longer bundled (--enable-mbedtls / --enable-bearssl use a system one)
|
||||
- NETWORK: Built-in SMB2/3 client with signing, AES-CCM/GCM encryption and Kerberos; libsmb2 is no longer bundled (--enable-libsmb uses a system one)
|
||||
- NETWORK: Built-in NFSv3 and NFSv4.0 client for loading content from nfs:// exports
|
||||
- NETWORK: Built-in NFSv3 and NFSv4 (4.0, 4.1, 4.2) client for loading content from nfs:// exports
|
||||
- NETWORK: Optional SMB/NFS read-ahead with background prefetch, for large disc images over a slow link (off by default)
|
||||
- NETWORK: Saved passwords are encrypted in retroarch-keychain.cfg; an optional passphrase lets the keychain move to another machine (see docs/network-stack.md)
|
||||
- OVERLAY: Allow overlays to position the viewport
|
||||
|
||||
@@ -68,9 +68,11 @@ keeps its connection. A wired connection is more reliable than Wi-Fi.
|
||||
## Loading content from NFS exports
|
||||
|
||||
The NFS client supports NFSv3, which finds the NFS and MOUNT services
|
||||
through the server's portmapper, and NFSv4.0, which connects straight to
|
||||
through the server's portmapper, and NFSv4, which connects straight to
|
||||
the NFS port and addresses the export as a path in the server's
|
||||
pseudo-filesystem. It authenticates with AUTH_UNIX, as the user
|
||||
pseudo-filesystem. With version 4 chosen, the client speaks the newest
|
||||
of 4.2, 4.1 and 4.0 the server offers, so servers that have dropped 4.0
|
||||
work too. It authenticates with AUTH_UNIX, as the user
|
||||
RetroArch runs as; platforms without user IDs, such as Windows and the
|
||||
consoles, send user and group 1000.
|
||||
|
||||
@@ -81,7 +83,7 @@ consoles, send user and group 1000.
|
||||
| `nfs_server` | | Server IP address or host name. |
|
||||
| `nfs_export` | | Exported path, for example `/export/roms`. For NFSv4 the same path works: when the server roots its v4 namespace lower down (Linux `fsid=0`), the leading part is dropped until the directory is found. Empty to give the export in the address instead. |
|
||||
| `nfs_subdir` | | Directory under the export. Optional. |
|
||||
| `nfs_version` | `3` | 3 or 4. |
|
||||
| `nfs_version` | `3` | 3, or 4 for the newest of 4.2, 4.1 and 4.0 the server offers. |
|
||||
| `nfs_port` | `0` | NFS service port; 0 asks the portmapper. |
|
||||
| `nfs_mount_port` | `0` | MOUNT service port (NFSv3); 0 asks the portmapper. |
|
||||
| `nfs_num_contexts` | `4` | Connections kept open, 1 to 16. |
|
||||
|
||||
+1
-1
@@ -36925,7 +36925,7 @@ MSG_HASH(
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_NFS_CLIENT_VERSION,
|
||||
"3 uses the portmapper and MOUNT service; 4 connects straight to the NFS port and takes the export as the server's pseudo-filesystem path."
|
||||
"3 uses the portmapper and MOUNT service; 4 connects straight to the NFS port and takes the export as the server's pseudo-filesystem path, speaking the newest of 4.2, 4.1 and 4.0 the server offers."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_NFS_CLIENT_READAHEAD,
|
||||
|
||||
@@ -121,6 +121,10 @@ const char *rnfs_get_error(const struct rnfs_ctx *c);
|
||||
/* Remote calls this connection has made, for tests that count round
|
||||
* trips. */
|
||||
uint32_t rnfs_get_call_count(const struct rnfs_ctx *c);
|
||||
|
||||
/* For NFSv4: the minor version in use - the newest of 2, 1 and 0 the
|
||||
* server speaks, chosen when connecting. */
|
||||
unsigned rnfs_get_minor_version(const struct rnfs_ctx *c);
|
||||
/* Last nfsstat3, for callers that map them. */
|
||||
uint32_t rnfs_get_status(const struct rnfs_ctx *c);
|
||||
int rnfs_get_fd(const struct rnfs_ctx *c);
|
||||
|
||||
+305
-59
@@ -87,12 +87,23 @@
|
||||
#define OP_SETCLIENTID_CONFIRM 36
|
||||
#define OP_RESTOREFH 31
|
||||
#define OP_WRITE_OP 38
|
||||
/* NFSv4.1 (RFC 8881): sessions */
|
||||
#define OP_EXCHANGE_ID 42
|
||||
#define OP_CREATE_SESSION 43
|
||||
#define OP_DESTROY_SESSION 44
|
||||
#define OP_SEQUENCE 53
|
||||
#define OP_DESTROY_CLIENTID 57
|
||||
#define OP_RECLAIM_COMPLETE 58
|
||||
#define NFS4ERR_STALE_CLIENTID 10022
|
||||
#define NFS4ERR_EXPIRED 10011
|
||||
#define NFS4ERR_BAD_STATEID 10025
|
||||
#define NFS4ERR_OLD_STATEID 10024
|
||||
#define NFS4ERR_GRACE 10013
|
||||
#define NFS4ERR_DELAY 10008
|
||||
#define NFS4ERR_MINOR_VERS_MISMATCH 10021
|
||||
#define NFS4ERR_BADSESSION 10052
|
||||
#define NFS4ERR_COMPLETE_ALREADY 10054
|
||||
#define NFS4ERR_DEADSESSION 10078
|
||||
#define NF4DIR 2
|
||||
/* attribute bits: type(1) size(4) in word 0, time_modify(53) in word 1 */
|
||||
#define FATTR4_W0_TYPE (1u << 1)
|
||||
@@ -152,6 +163,9 @@ struct rnfs_dir
|
||||
};
|
||||
|
||||
#define RNFS_DCACHE_SIZE 16
|
||||
/* NFSv4.1 session slots asked for: one per READ the pipeline keeps in
|
||||
* flight (RNFS_PIPELINE). */
|
||||
#define RNFS_SLOTS 8
|
||||
#define RNFS_DCACHE_PATH 256
|
||||
|
||||
struct rnfs_ctx
|
||||
@@ -177,6 +191,14 @@ struct rnfs_ctx
|
||||
uint64_t clientid;
|
||||
uint32_t open_seq;
|
||||
uint8_t owner[8];
|
||||
/* NFSv4.1 and later: the minor version in use, and the session every
|
||||
* request after its setup runs in - one slot per request in flight,
|
||||
* each with its own sequence number */
|
||||
uint8_t minor;
|
||||
uint8_t have_session;
|
||||
uint8_t sessionid[16];
|
||||
uint32_t slots; /* granted, at most RNFS_SLOTS */
|
||||
uint32_t slot_seq[RNFS_SLOTS];
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
char error[128];
|
||||
@@ -533,61 +555,127 @@ static void nfs4_unique(uint8_t *out, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
static int nfs4_session_setup(struct rnfs_ctx *c);
|
||||
|
||||
/* The COMPOUND header: an empty tag, the minor version in use, and the
|
||||
* operation count - one more when a SEQUENCE leads. */
|
||||
static void nfs4_head(struct xdr *x, const struct rnfs_ctx *c, unsigned nops,
|
||||
int sequenced)
|
||||
{
|
||||
xdr_u32(x, 0); /* tag: empty */
|
||||
xdr_u32(x, c->minor);
|
||||
xdr_u32(x, nops + (sequenced ? 1 : 0));
|
||||
}
|
||||
|
||||
/* SEQUENCE on @slot, whose sequence number advances; the server holds
|
||||
* each slot's last request, so a number reused is answered from that
|
||||
* reply instead of carried out. */
|
||||
static void nfs4_sequence(struct xdr *x, struct rnfs_ctx *c, unsigned slot)
|
||||
{
|
||||
xdr_u32(x, OP_SEQUENCE);
|
||||
memcpy(x->p, c->sessionid, 16); x->p += 16;
|
||||
xdr_u32(x, ++c->slot_seq[slot]);
|
||||
xdr_u32(x, slot);
|
||||
xdr_u32(x, c->slots - 1); /* highest slot in use */
|
||||
xdr_u32(x, 0); /* cachethis: no */
|
||||
}
|
||||
|
||||
/* The SEQUENCE result: its status. When the server refused the
|
||||
* SEQUENCE itself it did not take the number, so it is given back. */
|
||||
static uint32_t nfs4_sequence_res(struct xdr *r, struct rnfs_ctx *c,
|
||||
unsigned slot)
|
||||
{
|
||||
uint32_t st;
|
||||
xdr_get_u32(r); /* opnum */
|
||||
st = xdr_get_u32(r);
|
||||
if (st != NFS3_OK)
|
||||
{
|
||||
c->slot_seq[slot]--;
|
||||
return st;
|
||||
}
|
||||
r->p += 16; /* sessionid */
|
||||
xdr_get_u32(r); xdr_get_u32(r); /* sequenceid, slotid */
|
||||
xdr_get_u32(r); xdr_get_u32(r); /* highest, target highest */
|
||||
xdr_get_u32(r); /* status flags */
|
||||
return r->fail ? 1 : NFS3_OK;
|
||||
}
|
||||
|
||||
/* Session setup operations run outside a session; everything else in
|
||||
* one, once there is one. */
|
||||
static int nfs4_sequenced(const struct rnfs_ctx *c, const uint8_t *ops)
|
||||
{
|
||||
uint32_t op;
|
||||
if (!c->have_session)
|
||||
return 0;
|
||||
op = ((uint32_t)ops[0] << 24) | ((uint32_t)ops[1] << 16)
|
||||
| ((uint32_t)ops[2] << 8) | ops[3];
|
||||
return op != OP_EXCHANGE_ID && op != OP_CREATE_SESSION
|
||||
&& op != OP_DESTROY_SESSION && op != OP_DESTROY_CLIENTID;
|
||||
}
|
||||
|
||||
/* One COMPOUND: @ops is the body after tag / minorversion / numops.
|
||||
* The reply is positioned after the tag and numres; each result's
|
||||
* opnum and status are read by the callers with nfs4_res(). */
|
||||
* The reply is positioned after the tag and numres - and, in a session,
|
||||
* after the SEQUENCE result, so callers read their own results the same
|
||||
* in every minor version with nfs4_res(). */
|
||||
static int nfs4_compound(struct rnfs_ctx *c, const uint8_t *ops, size_t ops_len,
|
||||
unsigned nops, struct xdr *reply)
|
||||
{
|
||||
uint8_t *args = c->rx; /* free until the reply lands */
|
||||
struct xdr x;
|
||||
size_t tl;
|
||||
x.p = args; x.end = c->rx + c->buf_size; x.fail = 0;
|
||||
xdr_u32(&x, 0); /* tag: empty */
|
||||
xdr_u32(&x, 0); /* minorversion */
|
||||
xdr_u32(&x, nops);
|
||||
if (x.fail || (size_t)(x.end - x.p) < ops_len)
|
||||
return -1;
|
||||
memcpy(x.p, ops, ops_len); x.p += ops_len;
|
||||
/* NFS4ERR_GRACE while the server recovers after a restart and
|
||||
* NFS4ERR_DELAY are "ask again shortly", not failures: the request
|
||||
* is repeated a second apart, on a fixed bound so a stuck server
|
||||
* still cannot block a caller forever - the one wait a v4 client
|
||||
* cannot avoid, and only ever right after a server came up. A lost
|
||||
* session (the lease ran out while idle) is set up again, once. */
|
||||
unsigned tries = 15;
|
||||
int renewed = 0;
|
||||
|
||||
if (c->fd < 0)
|
||||
{
|
||||
rnfs_err(c, "not connected");
|
||||
return -1;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
/* NFS4ERR_GRACE while the server recovers after a restart and
|
||||
* NFS4ERR_DELAY are "ask again shortly", not failures: the
|
||||
* request is repeated, a second apart, for as long as the
|
||||
* context timeout allows - the one wait a v4 client cannot
|
||||
* avoid, and only ever right after a server came up. */
|
||||
/* GRACE/DELAY is a transient, self-clearing server state (only
|
||||
* ever just after the server started); retry on a fixed bound
|
||||
* so a stuck server still cannot block a caller forever. */
|
||||
unsigned tries = 15;
|
||||
size_t alen = (size_t)(x.p - args);
|
||||
uint8_t *keep = (uint8_t*)malloc(alen);
|
||||
if (!keep)
|
||||
uint8_t *args = c->rx; /* free until the reply lands */
|
||||
int sequenced = nfs4_sequenced(c, ops);
|
||||
struct xdr x;
|
||||
size_t tl;
|
||||
uint32_t sst = NFS3_OK;
|
||||
|
||||
x.p = args; x.end = c->rx + c->buf_size; x.fail = 0;
|
||||
nfs4_head(&x, c, nops, sequenced);
|
||||
if (sequenced)
|
||||
nfs4_sequence(&x, c, 0);
|
||||
if (x.fail || (size_t)(x.end - x.p) < ops_len)
|
||||
return -1;
|
||||
memcpy(keep, args, alen);
|
||||
for (;;)
|
||||
memcpy(x.p, ops, ops_len); x.p += ops_len;
|
||||
|
||||
if (rnfs_rpc(c, c->fd, NFS_PROG, 4, NFS4_COMPOUND, args,
|
||||
(size_t)(x.p - args), reply) != 0)
|
||||
return -1;
|
||||
c->status = xdr_get_u32(reply); /* overall status */
|
||||
xdr_get_opaque(reply, &tl); /* tag */
|
||||
xdr_get_u32(reply); /* numres */
|
||||
if (sequenced)
|
||||
sst = nfs4_sequence_res(reply, c, 0);
|
||||
|
||||
if ( sequenced && !renewed
|
||||
&& (sst == NFS4ERR_BADSESSION || sst == NFS4ERR_DEADSESSION
|
||||
|| sst == NFS4ERR_EXPIRED || sst == NFS4ERR_STALE_CLIENTID))
|
||||
{
|
||||
if (rnfs_rpc(c, c->fd, NFS_PROG, 4, NFS4_COMPOUND, keep, alen, reply) != 0)
|
||||
{
|
||||
free(keep);
|
||||
renewed = 1;
|
||||
c->have_session = 0;
|
||||
if (nfs4_session_setup(c) != 0)
|
||||
return -1;
|
||||
}
|
||||
c->status = xdr_get_u32(reply); /* overall status */
|
||||
if ((c->status == NFS4ERR_GRACE || c->status == NFS4ERR_DELAY) && tries--)
|
||||
{
|
||||
nfs4_wait_ms(1000);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
free(keep);
|
||||
if ((c->status == NFS4ERR_GRACE || c->status == NFS4ERR_DELAY) && tries--)
|
||||
{
|
||||
nfs4_wait_ms(1000);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
xdr_get_opaque(reply, &tl); /* tag */
|
||||
xdr_get_u32(reply); /* numres */
|
||||
return reply->fail ? -1 : 0;
|
||||
}
|
||||
|
||||
@@ -698,6 +786,133 @@ static int nfs4_setclientid(struct rnfs_ctx *c)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NFSv4.1+: the client's identity (EXCHANGE_ID), a session to run in
|
||||
* (CREATE_SESSION) and the word that it reclaims nothing from before
|
||||
* (RECLAIM_COMPLETE, which servers wait for before new opens).
|
||||
*
|
||||
* Returns: 0; 1 when the server does not speak this minor version;
|
||||
* -1 on any other failure. */
|
||||
static int nfs4_session_setup(struct rnfs_ctx *c)
|
||||
{
|
||||
uint8_t ops[256];
|
||||
struct xdr x, r;
|
||||
uint8_t verf[8];
|
||||
char id[32];
|
||||
uint32_t seq, st, i;
|
||||
size_t n;
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
|
||||
c->have_session = 0;
|
||||
nfs4_unique(verf, 8);
|
||||
nfs4_unique(c->owner, 8);
|
||||
memcpy(id, "retroarch-", 10);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
id[10 + i * 2] = hex[verf[i] >> 4];
|
||||
id[10 + i * 2 + 1] = hex[verf[i] & 15];
|
||||
}
|
||||
id[26] = '\0';
|
||||
|
||||
x.p = ops; x.end = ops + sizeof(ops); x.fail = 0;
|
||||
xdr_u32(&x, OP_EXCHANGE_ID);
|
||||
memcpy(x.p, verf, 8); x.p += 8; /* co_verifier */
|
||||
xdr_string(&x, id); /* co_ownerid */
|
||||
xdr_u32(&x, 0x00010000); /* EXCHGID4_FLAG_USE_NON_PNFS */
|
||||
xdr_u32(&x, 0); /* state protection: none */
|
||||
xdr_u32(&x, 0); /* client implementation id: none */
|
||||
if (x.fail || nfs4_compound(c, ops, (size_t)(x.p - ops), 1, &r) != 0)
|
||||
return -1;
|
||||
if (c->status == NFS4ERR_MINOR_VERS_MISMATCH)
|
||||
return 1;
|
||||
if (c->status != NFS3_OK || nfs4_res(&r) != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "exchange_id refused");
|
||||
return -1;
|
||||
}
|
||||
c->clientid = xdr_get_u64(&r);
|
||||
seq = xdr_get_u32(&r);
|
||||
|
||||
x.p = ops; x.fail = 0;
|
||||
xdr_u32(&x, OP_CREATE_SESSION);
|
||||
xdr_u64(&x, c->clientid);
|
||||
xdr_u32(&x, seq);
|
||||
xdr_u32(&x, 0); /* flags: no persistence, no back channel use */
|
||||
/* fore channel: this client's buffers, one slot per pipelined READ */
|
||||
xdr_u32(&x, 0); /* header padding */
|
||||
xdr_u32(&x, (uint32_t)c->buf_size); /* max request */
|
||||
xdr_u32(&x, (uint32_t)c->buf_size); /* max response */
|
||||
xdr_u32(&x, 4096); /* max response cached */
|
||||
xdr_u32(&x, 16); /* max operations */
|
||||
xdr_u32(&x, RNFS_SLOTS); /* max requests */
|
||||
xdr_u32(&x, 0); /* no RDMA */
|
||||
/* back channel: not served, kept small */
|
||||
xdr_u32(&x, 0);
|
||||
xdr_u32(&x, 4096);
|
||||
xdr_u32(&x, 4096);
|
||||
xdr_u32(&x, 0);
|
||||
xdr_u32(&x, 2);
|
||||
xdr_u32(&x, 1);
|
||||
xdr_u32(&x, 0);
|
||||
xdr_u32(&x, 0x40000000); /* callback program: none we serve */
|
||||
xdr_u32(&x, 1); /* one callback security flavor: */
|
||||
xdr_u32(&x, 0); /* AUTH_NONE */
|
||||
if (x.fail || nfs4_compound(c, ops, (size_t)(x.p - ops), 1, &r) != 0)
|
||||
return -1;
|
||||
if (c->status != NFS3_OK || nfs4_res(&r) != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "create_session refused");
|
||||
return -1;
|
||||
}
|
||||
memcpy(c->sessionid, r.p, 16); r.p += 16;
|
||||
xdr_get_u32(&r); /* sequence */
|
||||
xdr_get_u32(&r); /* flags */
|
||||
xdr_get_u32(&r); /* fore: header padding */
|
||||
xdr_get_u32(&r); xdr_get_u32(&r); /* max request, max response */
|
||||
xdr_get_u32(&r); xdr_get_u32(&r); /* max cached, max operations */
|
||||
n = xdr_get_u32(&r); /* max requests: the slots granted */
|
||||
if (r.fail || !n)
|
||||
{
|
||||
rnfs_err(c, "create_session reply");
|
||||
return -1;
|
||||
}
|
||||
c->slots = (uint32_t)(n < RNFS_SLOTS ? n : RNFS_SLOTS);
|
||||
for (i = 0; i < RNFS_SLOTS; i++)
|
||||
c->slot_seq[i] = 0;
|
||||
c->have_session = 1;
|
||||
c->open_seq = 0;
|
||||
|
||||
x.p = ops; x.fail = 0;
|
||||
xdr_u32(&x, OP_RECLAIM_COMPLETE);
|
||||
xdr_u32(&x, 0); /* for every file system */
|
||||
if (nfs4_compound(c, ops, (size_t)(x.p - ops), 1, &r) != 0)
|
||||
return -1;
|
||||
st = nfs4_res(&r);
|
||||
if (st != NFS3_OK && st != NFS4ERR_COMPLETE_ALREADY)
|
||||
{
|
||||
rnfs_err(c, "reclaim_complete refused");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The newest minor version the server speaks, down to 4.0. */
|
||||
static int nfs4_establish(struct rnfs_ctx *c)
|
||||
{
|
||||
static const uint8_t minors[] = { 2, 1 };
|
||||
unsigned i;
|
||||
for (i = 0; i < sizeof(minors); i++)
|
||||
{
|
||||
int ret;
|
||||
c->minor = minors[i];
|
||||
if ((ret = nfs4_session_setup(c)) == 0)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
c->minor = 0;
|
||||
return nfs4_setclientid(c);
|
||||
}
|
||||
|
||||
/* The export root: PUTROOTFH then LOOKUP down the pseudo path. */
|
||||
/* PUTROOTFH, a LOOKUP per component of @path, GETFH: the handle of
|
||||
* @path in the server's pseudo-filesystem into c->root.
|
||||
@@ -1232,7 +1447,7 @@ int rnfs_connect(struct rnfs_ctx *c, const char *server, const char *export_path
|
||||
return -1;
|
||||
c->connected = 1;
|
||||
c->io_size = NFS3_MAX_IO;
|
||||
if (nfs4_setclientid(c) != 0 || nfs4_mount(c) != 0)
|
||||
if (nfs4_establish(c) != 0 || nfs4_mount(c) != 0)
|
||||
{
|
||||
rnfs_disconnect(c);
|
||||
return -1;
|
||||
@@ -1311,8 +1526,9 @@ void rnfs_disconnect(struct rnfs_ctx *c)
|
||||
{
|
||||
if (c->fd >= 0)
|
||||
socket_close(c->fd);
|
||||
c->fd = -1;
|
||||
c->connected = 0;
|
||||
c->fd = -1;
|
||||
c->connected = 0;
|
||||
c->have_session = 0; /* the server lets it lapse with the lease */
|
||||
}
|
||||
|
||||
/* ---- path resolution ---------------------------------------------- */
|
||||
@@ -1526,6 +1742,12 @@ static int rnfs_walk(struct rnfs_ctx *c, const char *path, struct rnfs_fh *fh,
|
||||
return rnfs_walk_from(c, norm, 0, fh, st, parent, last, last_len, &stale);
|
||||
}
|
||||
|
||||
/* The NFSv4 minor version the connection settled on (0, 1 or 2). */
|
||||
unsigned rnfs_get_minor_version(const struct rnfs_ctx *c)
|
||||
{
|
||||
return c->minor;
|
||||
}
|
||||
|
||||
/* RPCs this connection has made: for tests that count round trips. */
|
||||
uint32_t rnfs_get_call_count(const struct rnfs_ctx *c)
|
||||
{
|
||||
@@ -1671,18 +1893,18 @@ struct rnfs_file *rnfs_open(struct rnfs_ctx *c, const char *path, int flags)
|
||||
#define RNFS_PIPELINE 8
|
||||
|
||||
/* Send one READ for @n octets at @off: v3 as its own call, v4 as a
|
||||
* PUTFH+READ compound. */
|
||||
* PUTFH+READ compound - in a session led by a SEQUENCE on @slot. */
|
||||
static int rnfs_send_read(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
uint64_t off, size_t n, uint32_t *xid)
|
||||
uint64_t off, size_t n, unsigned slot, uint32_t *xid)
|
||||
{
|
||||
uint8_t args[NFS3_FHSIZE + 64];
|
||||
uint8_t args[NFS3_FHSIZE + 128];
|
||||
struct xdr x;
|
||||
x.p = args; x.end = args + sizeof(args); x.fail = 0;
|
||||
if (c->version == 4)
|
||||
{
|
||||
xdr_u32(&x, 0); /* tag: empty */
|
||||
xdr_u32(&x, 0); /* minorversion */
|
||||
xdr_u32(&x, 2); /* PUTFH, READ */
|
||||
nfs4_head(&x, c, 2, c->have_session); /* PUTFH, READ */
|
||||
if (c->have_session)
|
||||
nfs4_sequence(&x, c, slot);
|
||||
nfs4_put_fh(&x, &f->fh);
|
||||
xdr_u32(&x, OP_READ);
|
||||
memcpy(x.p, f->stateid, 16); x.p += 16;
|
||||
@@ -1702,7 +1924,7 @@ static int rnfs_send_read(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
|
||||
/* Parse a READ reply at the cursor: data span in @d/@dl, @eof; 1 on
|
||||
* success, 0 for a v4 GRACE/DELAY (ask again), -1 on failure. */
|
||||
static int rnfs_parse_read(struct rnfs_ctx *c, struct xdr *r,
|
||||
static int rnfs_parse_read(struct rnfs_ctx *c, struct xdr *r, unsigned slot,
|
||||
const uint8_t **d, size_t *dl, uint32_t *eof)
|
||||
{
|
||||
struct rnfs_stat st;
|
||||
@@ -1713,7 +1935,18 @@ static int rnfs_parse_read(struct rnfs_ctx *c, struct xdr *r,
|
||||
c->status = xdr_get_u32(r);
|
||||
xdr_get_opaque(r, &tl); /* tag */
|
||||
xdr_get_u32(r); /* numres */
|
||||
if (c->status == 10013 || c->status == 10008)
|
||||
if (c->have_session)
|
||||
{
|
||||
uint32_t sst = nfs4_sequence_res(r, c, slot);
|
||||
if (sst == NFS4ERR_DELAY)
|
||||
return 0;
|
||||
if (sst != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "read: session refused");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (c->status == NFS4ERR_GRACE || c->status == NFS4ERR_DELAY)
|
||||
return 0;
|
||||
if (c->status != NFS3_OK || nfs4_res(r) != NFS3_OK || nfs4_res(r) != NFS3_OK)
|
||||
{
|
||||
@@ -1746,7 +1979,12 @@ static int64_t rnfs_fetch(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
{
|
||||
uint32_t xids[RNFS_PIPELINE];
|
||||
size_t offs[RNFS_PIPELINE], lens[RNFS_PIPELINE];
|
||||
unsigned sslot[RNFS_PIPELINE]; /* the session slot each READ is on */
|
||||
unsigned inflight = 0, i, grace = 0, short_at = 0;
|
||||
/* in a session no more READs than slots, each on its own */
|
||||
unsigned depth = (c->version == 4 && c->have_session && c->slots < RNFS_PIPELINE)
|
||||
? c->slots : RNFS_PIPELINE;
|
||||
uint32_t busy = 0;
|
||||
size_t sent = 0, end = 0; /* contiguous data: up to the first short reply */
|
||||
int eof = 0, fail = 0;
|
||||
|
||||
@@ -1757,16 +1995,21 @@ static int64_t rnfs_fetch(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
}
|
||||
while ((sent < len && !eof && !fail) || inflight)
|
||||
{
|
||||
while (inflight < RNFS_PIPELINE && sent < len && !eof && !fail)
|
||||
while (inflight < depth && sent < len && !eof && !fail)
|
||||
{
|
||||
size_t n = len - sent;
|
||||
size_t n = len - sent;
|
||||
unsigned sl = 0;
|
||||
if (n > c->io_size)
|
||||
n = c->io_size;
|
||||
if (rnfs_send_read(c, f, off + sent, n, &xids[inflight]) != 0)
|
||||
while (sl < RNFS_PIPELINE - 1 && (busy & (1u << sl)))
|
||||
sl++;
|
||||
if (rnfs_send_read(c, f, off + sent, n, sl, &xids[inflight]) != 0)
|
||||
{
|
||||
fail = 1;
|
||||
break;
|
||||
}
|
||||
busy |= 1u << sl;
|
||||
sslot[inflight] = sl;
|
||||
offs[inflight] = sent;
|
||||
lens[inflight] = n;
|
||||
inflight++;
|
||||
@@ -1788,7 +2031,7 @@ static int64_t rnfs_fetch(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
slot = i;
|
||||
if (slot == RNFS_PIPELINE)
|
||||
continue;
|
||||
pr = rnfs_parse_read(c, &r, &d, &dl, &reof);
|
||||
pr = rnfs_parse_read(c, &r, sslot[slot], &d, &dl, &reof);
|
||||
if (pr == 0)
|
||||
{
|
||||
/* v4 GRACE/DELAY: wait and reissue this chunk, bounded */
|
||||
@@ -1800,7 +2043,8 @@ static int64_t rnfs_fetch(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
else
|
||||
{
|
||||
nfs4_wait_ms(1000);
|
||||
if (rnfs_send_read(c, f, off + offs[slot], lens[slot], &xids[slot]) != 0)
|
||||
if (rnfs_send_read(c, f, off + offs[slot], lens[slot],
|
||||
sslot[slot], &xids[slot]) != 0)
|
||||
fail = 1;
|
||||
continue;
|
||||
}
|
||||
@@ -1825,9 +2069,11 @@ static int64_t rnfs_fetch(struct rnfs_ctx *c, struct rnfs_file *f,
|
||||
}
|
||||
}
|
||||
}
|
||||
xids[slot] = xids[inflight - 1];
|
||||
offs[slot] = offs[inflight - 1];
|
||||
lens[slot] = lens[inflight - 1];
|
||||
busy &= ~(1u << sslot[slot]);
|
||||
xids[slot] = xids[inflight - 1];
|
||||
offs[slot] = offs[inflight - 1];
|
||||
lens[slot] = lens[inflight - 1];
|
||||
sslot[slot] = sslot[inflight - 1];
|
||||
inflight--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,57 +14,84 @@ RUN=${RUNNER:-}
|
||||
EXE=${EXE:-}
|
||||
PORT=20449
|
||||
D=$(mktemp -d); chmod 755 $D; mkdir -p $D/export $D/run; chmod 777 $D/export
|
||||
cat > $D/ganesha.conf << EOF
|
||||
make -s vfs_threads_test >/dev/null 2>&1 || true
|
||||
stop() {
|
||||
kill $(cat $D/run/g.pid 2>/dev/null) 2>/dev/null || pkill -x ganesha.nfsd 2>/dev/null || true
|
||||
i=0; while pgrep -x ganesha.nfsd >/dev/null 2>&1 && [ $i -lt 30 ]; do sleep 0.2; i=$((i + 1)); done
|
||||
rm -rf $D/run/* 2>/dev/null || true
|
||||
}
|
||||
cleanup() {
|
||||
stop
|
||||
rm -rf $D 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# One server per round, speaking the minor versions given; the client
|
||||
# must settle on the newest of them it knows (2, 1, then 0).
|
||||
round() {
|
||||
minors="$1"; want="$2"
|
||||
cat > $D/ganesha.conf << EOF
|
||||
NFS_Core_Param { NFS_Port = $PORT; Enable_UDP = false; Bind_addr = 127.0.0.1; Protocols = 4; Enable_NLM = false; Enable_RQUOTA = false; }
|
||||
NFSv4 { Minor_Versions = 0; Grace_Period = 2; Lease_Lifetime = 20; RecoveryBackend = fs_ng; RecoveryRoot = $D/run; }
|
||||
NFSv4 { Minor_Versions = $minors; Grace_Period = 2; Lease_Lifetime = 20; RecoveryBackend = fs_ng; RecoveryRoot = $D/run; }
|
||||
EXPORT_DEFAULTS { Protocols = 4; Transports = TCP; SecType = sys; Access_Type = RW; Squash = No_Root_Squash; }
|
||||
EXPORT { Export_Id = 77; Path = $D/export; Pseudo = /export; FSAL { Name = VFS; } }
|
||||
LOG { Default_Log_Level = EVENT; }
|
||||
EOF
|
||||
# ganesha dies if it is not fully detached from this shell
|
||||
setsid nohup $GANESHA -F -L $D/ganesha.log -f $D/ganesha.conf -p $D/run/g.pid -N NIV_EVENT > $D/out 2>&1 < /dev/null &
|
||||
cleanup() {
|
||||
kill $(cat $D/run/g.pid 2>/dev/null) 2>/dev/null || pkill -x ganesha.nfsd 2>/dev/null || true
|
||||
sleep 1
|
||||
rm -rf $D 2>/dev/null || true
|
||||
# each server starts on an empty export, as a fresh install would:
|
||||
# files left by the round before and removed behind a running
|
||||
# server's back would test its cache, not this client
|
||||
rm -rf $D/export/* 2>/dev/null || true
|
||||
# ganesha dies if it is not fully detached from this shell
|
||||
setsid nohup $GANESHA -F -L $D/ganesha.log -f $D/ganesha.conf -p $D/run/g.pid -N NIV_EVENT > $D/out 2>&1 < /dev/null &
|
||||
# wait for the port, then for the grace period to pass (the client
|
||||
# retries through it, but the negative case below should not wait)
|
||||
i=0; while [ $i -lt 50 ]; do
|
||||
$RUN ./nfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1 && break
|
||||
i=$((i + 1)); sleep 0.3
|
||||
done
|
||||
rm -rf $D/export/*
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: NFSv4 against nfs-ganesha offering 4.[$minors] (no portmapper, pseudo path)"
|
||||
else
|
||||
echo "FAIL: 4.[$minors]: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
if grep -q "minor version $want" $D/res; then
|
||||
echo "ok: settled on NFSv4.$want"
|
||||
else
|
||||
echo "FAIL: 4.[$minors] should settle on 4.$want: $(grep 'minor version' $D/res)"; exit 1
|
||||
fi
|
||||
# the real VFS on top, version 4
|
||||
if $RUN ./vfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: nfs:// (v4.$want) through vfs_implementation.c"
|
||||
else
|
||||
echo "FAIL: 4.[$minors]: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# pipelined reads with read-ahead on: one session slot per READ
|
||||
if [ -x ./vfs_threads_test$EXE ] && $RUN ./vfs_threads_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: concurrent threads through the nfs:// (v4.$want) backend"
|
||||
else
|
||||
echo "FAIL: 4.[$minors]: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# the export as a v3 client knows it - the directory's own path on the
|
||||
# server - above the v4 namespace root: its leading components are
|
||||
# dropped until the namespace has it (Linux fsid=0 roots it the same)
|
||||
rm -rf $D/export/*
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 $D/export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: NFSv4.$want export given by its server path, above the namespace root"
|
||||
else
|
||||
echo "FAIL: 4.[$minors]: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# a path under an export the namespace does have is not guessed at
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 /export/nonexistent $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "FAIL: 4.[$minors]: a wrong path under an existing export was accepted"; exit 1
|
||||
else
|
||||
echo "ok: wrong path under an export is refused"
|
||||
fi
|
||||
stop
|
||||
}
|
||||
trap cleanup EXIT
|
||||
# wait for the port, then for the grace period to pass (the client
|
||||
# retries through it, but the negative case below should not wait)
|
||||
i=0; while [ $i -lt 50 ]; do
|
||||
$RUN ./nfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1 && break
|
||||
i=$((i + 1)); sleep 0.3
|
||||
done
|
||||
rm -rf $D/export/*
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: NFSv4.0 against nfs-ganesha (no portmapper, pseudo path)"
|
||||
else
|
||||
echo "FAIL: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# the real VFS on top, version 4
|
||||
if $RUN ./vfs_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: nfs:// (v4) through vfs_implementation.c"
|
||||
else
|
||||
echo "FAIL: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
if make -s vfs_threads_test >/dev/null 2>&1 && $RUN ./vfs_threads_test$EXE 127.0.0.1 /export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: concurrent threads through the nfs:// (v4) backend"
|
||||
else
|
||||
echo "FAIL: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# the export as a v3 client knows it - the directory's own path on the
|
||||
# server - above the v4 namespace root: its leading components are
|
||||
# dropped until the namespace has it (Linux fsid=0 roots it the same)
|
||||
rm -rf $D/export/*
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 $D/export $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "ok: NFSv4 export given by its server path, above the namespace root"
|
||||
else
|
||||
echo "FAIL: $(cat $D/res)"; exit 1
|
||||
fi
|
||||
# a path under an export the namespace does have is not guessed at
|
||||
if $RUN ./nfs_test$EXE 127.0.0.1 /export/nonexistent $PORT 0 4 > $D/res 2>&1; then
|
||||
echo "FAIL: a wrong path under an existing export was accepted"; exit 1
|
||||
else
|
||||
echo "ok: wrong path under an export is refused"
|
||||
fi
|
||||
|
||||
round "0" 0
|
||||
round "1" 1
|
||||
round "2" 2
|
||||
round "0, 1, 2" 2
|
||||
echo "[pass] nfs_retro local ganesha matrix"
|
||||
@@ -151,6 +151,8 @@ int main(int argc, char **argv)
|
||||
printf("null latency: %.2f ms per request\n",
|
||||
(cpu_features_get_time_usec() - t0) / 1000.0 / 20);
|
||||
}
|
||||
if (argc > 5 && atoi(argv[5]) == 4)
|
||||
printf("minor version %u\n", rnfs_get_minor_version(c));
|
||||
rnfs_free(c);
|
||||
printf("ok: %s:%s (%u entries)\n", argv[1], argv[2], n);
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user