mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
nfs, smb: apply setting changes without a restart, log why a listing failed (#19982)
* nfs, smb: apply menu setting changes without a restart The clients took their settings at startup and after config_load only. Their numbers (NFS version, ports, timeout, read-ahead, connection count, SMB auth mode) are copies, so a menu change waited for a restart. Each change now hands the clients the current values. SMB keys its pools on the whole configuration, so its next connection uses the new values. The NFS pool matched a free connection by export alone, so a changed server, version or port could ride an old one. New settings now close the idle connections and advance a generation; a slot whose generation or server differs connects again. The pool is kept rather than replaced, so nfs_init_cfg() cannot free it under a thread that has just taken a slot. On version 4 an NFS port of 0 means 2049 and the mount port is unused; the sublabels say so. vfs_threads_test changes the ports while a prefetcher holds a slot. * nfs, smb: log why a network directory listing failed A failed file browser listing of an nfs:// or smb:// directory showed "Directory not found" and logged nothing. It now logs one error line with the URL, the settings the client ran with and the client's reason. The NFS client's reasons say what failed: the step and the server's status (v4.2 mount: LOOKUP roms: NFS4ERR_NOENT), the address a connect was refused at or timed out on, and RPC rejects such as PROG_MISMATCH or AUTH_TOOWEAK. A refusal (PERM, ACCESS, WRONGSEC, or a v3 MOUNT ACCES) adds that a Linux export needs 'insecure' for a client port above 1023, as this client's always is. A failed walk or redial keeps its own reason. Failures that never reach a server record one too: no export, no server in the URL, no SMB server set. SMB gets smb_get_last_error() to match nfs_get_last_error(). nfs_error_test checks the reasons without a server and against fake_nfs42.py. * nfs: open files keep the settings they were opened with An open nfs:// file or directory stored only its server and export, so after a settings change its next read reconnected with the new version and ports: a v3 handle sent over v4, or the stream's server asked on another server's port. Open files and directories now keep the version, ports and timeout they were opened with and reconnect with those. Each pool slot carries the parameters it connected with and is reused only on an exact match, which replaces the settings generation. Read-ahead is client-side and still follows the settings on every take. nfs_error_test reads an open file on across a port and version change. * smb: list a server's shares when no share is set Browsing smb://server with no share configured never listed anything. retro_vfs_opendir_impl rejected any SMB handle whose dir was NULL, which is exactly the handle retro_vfs_opendir_smb returns for a share list, and leaked it. The backend already reads and closes that handle, so the dispatcher now accepts it. The UWP dispatcher does the same and takes an entry's directory flag from the listing instead of a stat. vfs_test checks the share list through the dispatcher as well. * nfs: a file opened over a private connection keeps its export With no NFS Export set, the export comes from the URL and is found when connecting. When every pooled connection is busy, as it is once a few read-only streams' prefetchers hold one each, the open goes over a private connection and the file or directory handle recorded an empty export. Every later borrow then mounted "": refused on v3; on v4 the pseudo root, where the path under the export is not found, so such a stream never got its prefetcher. Record the export the connection was made with, through a new rnfs_get_export(). nfs_error_test opens a file by URL while a prefetcher holds the only slot. * nfs, smb: settings as immutable copies; pools keyed by the copy nfs_init_cfg() and smb_init_cfg() copy the settings they are given, strings included, and publish the copy through vfs_cfg_snapshot.h: a reader holds the copy it got between enter and leave, and a copy that was replaced is freed once nothing holds it - retro_retire.h's epochs where the pointer atomics are lock-free, a reference count under a short flag elsewhere. Menu edits may rewrite the settings in place while pool, prefetch and task threads read them. NFS resolves a URL against the held copy's export without copying it, and applies the timeout and read-ahead on every take: neither is part of what a connection must match. SMB pools remember the copy they were last matched to, so a take on that copy and share finds its pool without building or comparing a key. A READDIR failure names the result's own status. nfs_cfg_race_test and smb_cfg_race_test free the caller's struct right after handing it over, then resolve against it, and hand one struct over repeatedly while four threads stat and list; they need no server and run ahead of the server matrices in local_unfs3_test.sh and local_samba_test.sh. --------- Co-authored-by: LibretroAdmin <[email protected]>
This commit is contained in:
1 parent
688cc53eda
commit
188f37a97b
23 files changed
+1674
-253
No files matched your search
+2
-2
@@ -37963,7 +37963,7 @@ MSG_HASH(
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_NFS_CLIENT_PORT,
|
||||
"Port of the NFS service. 0 asks the server's portmapper."
|
||||
"Port of the NFS service. 0 asks the server's portmapper on version 3 and means 2049 on version 4."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_NFS_CLIENT_MOUNT_PORT,
|
||||
@@ -37971,7 +37971,7 @@ MSG_HASH(
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_NFS_CLIENT_MOUNT_PORT,
|
||||
"Port of the MOUNT service. 0 asks the server's portmapper."
|
||||
"Port of the MOUNT service. 0 asks the server's portmapper. Version 4 does not use it."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_NFS_CLIENT_VERSION,
|
||||
|
||||
@@ -140,6 +140,8 @@ int rnfs_get_read_plus(const struct rnfs_ctx *c);
|
||||
#define RNFS_STATUS_NONE ((uint32_t)0xFFFFFFFFUL)
|
||||
uint32_t rnfs_get_status(const struct rnfs_ctx *c);
|
||||
int rnfs_get_fd(const struct rnfs_ctx *c);
|
||||
/* The export as rnfs_connect() was given it. */
|
||||
const char *rnfs_get_export(const struct rnfs_ctx *c);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
|
||||
+312
-58
@@ -23,6 +23,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <net/net_nfs3.h>
|
||||
#include <net/net_compat.h>
|
||||
@@ -258,6 +259,146 @@ static void rnfs_err(struct rnfs_ctx *c, const char *msg)
|
||||
strlcpy(c->error, msg, sizeof(c->error));
|
||||
}
|
||||
|
||||
/* v3, MOUNT and v4 share these numbers. */
|
||||
static const char *rnfs_status_name(uint32_t st)
|
||||
{
|
||||
switch (st)
|
||||
{
|
||||
case 1: return "PERM";
|
||||
case 2: return "NOENT";
|
||||
case 5: return "IO";
|
||||
case 13: return "ACCESS";
|
||||
case 20: return "NOTDIR";
|
||||
case 22: return "INVAL";
|
||||
case 63: return "NAMETOOLONG";
|
||||
case 70: return "STALE";
|
||||
case 10001: return "BADHANDLE";
|
||||
case 10004: return "NOTSUPP";
|
||||
case 10006: return "SERVERFAULT";
|
||||
case 10016: return "WRONGSEC";
|
||||
case 10018: return "RESOURCE";
|
||||
case 10020: return "NOFILEHANDLE";
|
||||
case 10021: return "MINOR_VERS_MISMATCH";
|
||||
case 10036: return "BADXDR";
|
||||
case 10044: return "OP_ILLEGAL";
|
||||
default: break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Component @n of @path, cut to fit @out. */
|
||||
static void rnfs_component(const char *path, int n, char *out, size_t len)
|
||||
{
|
||||
size_t l;
|
||||
for (;;)
|
||||
{
|
||||
const char *e;
|
||||
while (*path == '/')
|
||||
path++;
|
||||
e = strchr(path, '/');
|
||||
l = e ? (size_t)(e - path) : strlen(path);
|
||||
if (n-- <= 0 || !*path)
|
||||
break;
|
||||
path += l;
|
||||
}
|
||||
if (l >= len)
|
||||
l = len - 1;
|
||||
memcpy(out, path, l);
|
||||
out[l] = '\0';
|
||||
}
|
||||
|
||||
/* @step answered @st (on component @comp of @path, given one). A Linux
|
||||
* server refuses a client on a port above 1023, as this one always is,
|
||||
* unless the export says 'insecure'; its mountd also answers ACCES for
|
||||
* a path it does not export. */
|
||||
static void rnfs_err_status(struct rnfs_ctx *c, const char *step,
|
||||
const char *path, int comp, uint32_t st)
|
||||
{
|
||||
char name[25];
|
||||
char num[24];
|
||||
int mnt = c->version != 4 && !strcmp(step, "MOUNT");
|
||||
const char *pfx = c->version == 4 ? "NFS4ERR_" : mnt ? "MNT3ERR_" : "NFS3ERR_";
|
||||
const char *sn = (st == 13 && c->version != 4) ? "ACCES" : rnfs_status_name(st);
|
||||
const char *hint = "";
|
||||
|
||||
if (!sn)
|
||||
{
|
||||
snprintf(num, sizeof(num), "status %u", (unsigned)st);
|
||||
sn = num;
|
||||
pfx = "";
|
||||
}
|
||||
if (mnt && st == 13)
|
||||
hint = "; not exported to us, or a Linux export needs 'insecure' (client port >1023)";
|
||||
else if (st == 1 || (c->version == 4 && (st == 13 || st == 10016)))
|
||||
hint = "; a Linux export needs 'insecure' (client port >1023)";
|
||||
name[0] = '\0';
|
||||
if (path)
|
||||
rnfs_component(path, comp, name, sizeof(name));
|
||||
if (c->version == 4)
|
||||
snprintf(c->error, sizeof(c->error), "v4.%u %s%s%s: %s%s%s",
|
||||
(unsigned)c->minor, step, *name ? " " : "", name, pfx, sn, hint);
|
||||
else
|
||||
snprintf(c->error, sizeof(c->error), "v3 %s%s%s: %s%s%s",
|
||||
step, *name ? " " : "", name, pfx, sn, hint);
|
||||
}
|
||||
|
||||
/* The OS error the socket call that just failed left, where the
|
||||
* platform keeps one; else 0. */
|
||||
static int rnfs_socket_error(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return WSAGetLastError();
|
||||
#elif defined(__PS3__) || defined(VITA) || defined(WIIU) \
|
||||
|| (defined(GEKKO) && !defined(GEKKO_NATIVE)) || defined(_3DS)
|
||||
return 0;
|
||||
#else
|
||||
return errno;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* A connect to @port that failed with @err after @waited usec. */
|
||||
static void rnfs_err_connect(struct rnfs_ctx *c, uint16_t port, int err,
|
||||
retro_time_t waited)
|
||||
{
|
||||
const char *why = NULL;
|
||||
#if defined(_WIN32)
|
||||
if (err == WSAECONNREFUSED)
|
||||
why = "refused";
|
||||
else if (err == WSAEHOSTUNREACH || err == WSAENETUNREACH)
|
||||
why = "unreachable";
|
||||
#else
|
||||
#ifdef ECONNREFUSED
|
||||
if (err == ECONNREFUSED)
|
||||
why = "refused";
|
||||
#endif
|
||||
#if defined(EHOSTUNREACH) && defined(ENETUNREACH)
|
||||
if (err == EHOSTUNREACH || err == ENETUNREACH)
|
||||
why = "unreachable";
|
||||
#endif
|
||||
#endif
|
||||
if (!why && waited >= (retro_time_t)c->timeout * 900000)
|
||||
snprintf(c->error, sizeof(c->error), "connect to %.64s:%u timed out after %us",
|
||||
c->server, (unsigned)port, c->timeout);
|
||||
else if (!why)
|
||||
snprintf(c->error, sizeof(c->error), "connect to %.64s:%u failed (error %d)",
|
||||
c->server, (unsigned)port, err);
|
||||
else
|
||||
snprintf(c->error, sizeof(c->error), "connect to %.64s:%u %s",
|
||||
c->server, (unsigned)port, why);
|
||||
}
|
||||
|
||||
/* A reply that did not come: silence for the whole timeout, or the
|
||||
* server hanging up. */
|
||||
static void rnfs_err_recv(struct rnfs_ctx *c, retro_time_t start)
|
||||
{
|
||||
if (cpu_features_get_time_usec() - start >= (retro_time_t)c->timeout * 900000)
|
||||
snprintf(c->error, sizeof(c->error), "no reply from %.64s in %us",
|
||||
c->server, c->timeout);
|
||||
else
|
||||
snprintf(c->error, sizeof(c->error), "%.64s closed the connection",
|
||||
c->server);
|
||||
}
|
||||
|
||||
/* XDR writer / reader over a byte buffer. */
|
||||
struct xdr
|
||||
{
|
||||
@@ -410,19 +551,22 @@ static void xdr_get_wcc_data(struct xdr *x)
|
||||
static int rnfs_tcp_connect(struct rnfs_ctx *c, uint16_t port)
|
||||
{
|
||||
struct addrinfo *addr = NULL;
|
||||
retro_time_t start;
|
||||
int fd = socket_init((void**)&addr, port, c->server, SOCKET_TYPE_STREAM, AF_UNSPEC);
|
||||
if (fd < 0 || !addr)
|
||||
{
|
||||
if (addr)
|
||||
freeaddrinfo_retro(addr);
|
||||
rnfs_err(c, "cannot resolve server");
|
||||
snprintf(c->error, sizeof(c->error), "cannot resolve %.64s", c->server);
|
||||
return -1;
|
||||
}
|
||||
start = cpu_features_get_time_usec();
|
||||
if (!socket_connect_with_timeout(fd, addr, (int)c->timeout * 1000))
|
||||
{
|
||||
int err = rnfs_socket_error();
|
||||
rnfs_err_connect(c, port, err, cpu_features_get_time_usec() - start);
|
||||
freeaddrinfo_retro(addr);
|
||||
socket_close(fd);
|
||||
rnfs_err(c, "connect failed");
|
||||
return -1;
|
||||
}
|
||||
freeaddrinfo_retro(addr);
|
||||
@@ -488,8 +632,14 @@ static int rnfs_rpc_send(struct rnfs_ctx *c, int fd, uint32_t prog, uint32_t ver
|
||||
* reply cursor placed after the accepted-reply header. */
|
||||
static int rnfs_rpc_recv(struct rnfs_ctx *c, int fd, uint32_t *xid_out, struct xdr *reply)
|
||||
{
|
||||
uint32_t rlen;
|
||||
int to = (int)c->timeout * 1000;
|
||||
static const char *const rpc_auth[] = { "AUTH_OK", "AUTH_BADCRED",
|
||||
"AUTH_REJECTEDCRED", "AUTH_BADVERF", "AUTH_REJECTEDVERF",
|
||||
"AUTH_TOOWEAK" };
|
||||
static const char *const rpc_accept[] = { "SUCCESS", "PROG_UNAVAIL",
|
||||
"PROG_MISMATCH", "PROC_UNAVAIL", "GARBAGE_ARGS", "SYSTEM_ERR" };
|
||||
uint32_t rlen, st;
|
||||
int to = (int)c->timeout * 1000;
|
||||
retro_time_t start = cpu_features_get_time_usec();
|
||||
|
||||
/* reply: one or more fragments, concatenated */
|
||||
rlen = 0;
|
||||
@@ -499,7 +649,7 @@ static int rnfs_rpc_recv(struct rnfs_ctx *c, int fd, uint32_t *xid_out, struct x
|
||||
uint32_t frag, last;
|
||||
if (!socket_receive_all_blocking_with_timeout(fd, hdr, 4, to))
|
||||
{
|
||||
rnfs_err(c, "receive failed or timed out");
|
||||
rnfs_err_recv(c, start);
|
||||
return -1;
|
||||
}
|
||||
last = hdr[0] & 0x80;
|
||||
@@ -512,7 +662,7 @@ static int rnfs_rpc_recv(struct rnfs_ctx *c, int fd, uint32_t *xid_out, struct x
|
||||
}
|
||||
if (!socket_receive_all_blocking_with_timeout(fd, c->rx + rlen, frag, to))
|
||||
{
|
||||
rnfs_err(c, "receive failed or timed out");
|
||||
rnfs_err_recv(c, start);
|
||||
return -1;
|
||||
}
|
||||
rlen += frag;
|
||||
@@ -523,9 +673,20 @@ static int rnfs_rpc_recv(struct rnfs_ctx *c, int fd, uint32_t *xid_out, struct x
|
||||
|
||||
reply->p = c->rx; reply->end = c->rx + rlen; reply->fail = 0;
|
||||
*xid_out = xdr_get_u32(reply);
|
||||
if (xdr_get_u32(reply) != 1 || xdr_get_u32(reply) != 0)
|
||||
if (xdr_get_u32(reply) != 1) /* REPLY */
|
||||
{
|
||||
rnfs_err(c, "rpc reply rejected");
|
||||
rnfs_err(c, "rpc reply malformed");
|
||||
return -1;
|
||||
}
|
||||
if (xdr_get_u32(reply) != 0) /* MSG_DENIED */
|
||||
{
|
||||
if (xdr_get_u32(reply) != 1) /* RPC_MISMATCH */
|
||||
rnfs_err(c, "rpc denied: RPC_MISMATCH");
|
||||
else if ((st = xdr_get_u32(reply)) < 6)
|
||||
snprintf(c->error, sizeof(c->error), "rpc denied: %s", rpc_auth[st]);
|
||||
else
|
||||
snprintf(c->error, sizeof(c->error), "rpc denied: auth error %u",
|
||||
(unsigned)st);
|
||||
return -1;
|
||||
}
|
||||
xdr_get_u32(reply); /* verifier flavor */
|
||||
@@ -533,9 +694,19 @@ static int rnfs_rpc_recv(struct rnfs_ctx *c, int fd, uint32_t *xid_out, struct x
|
||||
size_t vl;
|
||||
xdr_get_opaque(reply, &vl);
|
||||
}
|
||||
if (xdr_get_u32(reply) != 0 || reply->fail)
|
||||
if ((st = xdr_get_u32(reply)) != 0 || reply->fail)
|
||||
{
|
||||
rnfs_err(c, "rpc call not accepted");
|
||||
if (st == 2)
|
||||
{
|
||||
uint32_t lo = xdr_get_u32(reply);
|
||||
uint32_t hi = xdr_get_u32(reply);
|
||||
snprintf(c->error, sizeof(c->error),
|
||||
"rpc PROG_MISMATCH: server offers v%u-%u", (unsigned)lo, (unsigned)hi);
|
||||
}
|
||||
else if (st > 0 && st < 6)
|
||||
snprintf(c->error, sizeof(c->error), "rpc %s", rpc_accept[st]);
|
||||
else
|
||||
rnfs_err(c, "rpc call not accepted");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -596,7 +767,6 @@ static int rnfs_call(struct rnfs_ctx *c, uint32_t proc,
|
||||
free(held);
|
||||
/* what the failed mount answered is not this call's status */
|
||||
c->status = RNFS_STATUS_NONE;
|
||||
rnfs_err(c, "not connected");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -621,11 +791,11 @@ static uint16_t rnfs_getport(struct rnfs_ctx *c, uint32_t prog, uint32_t vers)
|
||||
return 0;
|
||||
x.p = args; x.end = args + sizeof(args); x.fail = 0;
|
||||
xdr_u32(&x, prog); xdr_u32(&x, vers); xdr_u32(&x, 6); xdr_u32(&x, 0);
|
||||
if (rnfs_rpc(c, fd, PMAP_PROG, PMAP_VERS, PMAP_GETPORT, args, 16, &reply) == 0)
|
||||
port = (uint16_t)xdr_get_u32(&reply);
|
||||
if (rnfs_rpc(c, fd, PMAP_PROG, PMAP_VERS, PMAP_GETPORT, args, 16, &reply) == 0
|
||||
&& !(port = (uint16_t)xdr_get_u32(&reply)))
|
||||
rnfs_err(c, prog == MOUNT_PROG ? "portmapper: no MOUNT v3 service"
|
||||
: "portmapper: no NFS v3 service");
|
||||
socket_close(fd);
|
||||
if (!port)
|
||||
rnfs_err(c, "portmapper has no such service");
|
||||
return port;
|
||||
}
|
||||
|
||||
@@ -747,7 +917,6 @@ static int nfs4_compound(struct rnfs_ctx *c, const uint8_t *ops, size_t ops_len,
|
||||
if (c->fd < 0 && rnfs_redial(c) != 0)
|
||||
{
|
||||
c->status = RNFS_STATUS_NONE;
|
||||
rnfs_err(c, "not connected");
|
||||
return -1;
|
||||
}
|
||||
for (;;)
|
||||
@@ -763,7 +932,10 @@ static int nfs4_compound(struct rnfs_ctx *c, const uint8_t *ops, size_t ops_len,
|
||||
if (sequenced)
|
||||
nfs4_sequence(&x, c, 0);
|
||||
if (x.fail || (size_t)(x.end - x.p) < ops_len)
|
||||
{
|
||||
rnfs_err(c, "request too large");
|
||||
return -1;
|
||||
}
|
||||
memcpy(x.p, ops, ops_len); x.p += ops_len;
|
||||
|
||||
if (rnfs_rpc(c, c->fd, NFS_PROG, 4, NFS4_COMPOUND, args,
|
||||
@@ -938,9 +1110,11 @@ static int nfs4_setclientid(struct rnfs_ctx *c)
|
||||
xdr_u32(&x, 1); /* callback_ident */
|
||||
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)
|
||||
st = nfs4_res(&r);
|
||||
if (c->status != NFS3_OK || st != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "setclientid refused");
|
||||
rnfs_err_status(c, "SETCLIENTID", NULL, 0,
|
||||
c->status != NFS3_OK ? c->status : st);
|
||||
return -1;
|
||||
}
|
||||
clientid = xdr_get_u64(&r);
|
||||
@@ -959,7 +1133,8 @@ static int nfs4_setclientid(struct rnfs_ctx *c)
|
||||
st = nfs4_res(&r);
|
||||
if (c->status != NFS3_OK || st != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "setclientid confirm refused");
|
||||
rnfs_err_status(c, "SETCLIENTID_CONFIRM", NULL, 0,
|
||||
c->status != NFS3_OK ? c->status : st);
|
||||
return -1;
|
||||
}
|
||||
c->clientid = clientid;
|
||||
@@ -1006,9 +1181,11 @@ static int nfs4_session_setup(struct rnfs_ctx *c)
|
||||
return -1;
|
||||
if (c->status == NFS4ERR_MINOR_VERS_MISMATCH)
|
||||
return 1;
|
||||
if (c->status != NFS3_OK || nfs4_res(&r) != NFS3_OK)
|
||||
st = nfs4_res(&r);
|
||||
if (c->status != NFS3_OK || st != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "exchange_id refused");
|
||||
rnfs_err_status(c, "EXCHANGE_ID", NULL, 0,
|
||||
c->status != NFS3_OK ? c->status : st);
|
||||
return -1;
|
||||
}
|
||||
c->clientid = xdr_get_u64(&r);
|
||||
@@ -1040,9 +1217,11 @@ static int nfs4_session_setup(struct rnfs_ctx *c)
|
||||
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)
|
||||
st = nfs4_res(&r);
|
||||
if (c->status != NFS3_OK || st != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "create_session refused");
|
||||
rnfs_err_status(c, "CREATE_SESSION", NULL, 0,
|
||||
c->status != NFS3_OK ? c->status : st);
|
||||
return -1;
|
||||
}
|
||||
xdr_get_fixed(&r, c->sessionid, 16);
|
||||
@@ -1054,7 +1233,7 @@ static int nfs4_session_setup(struct rnfs_ctx *c)
|
||||
n = xdr_get_u32(&r); /* max requests: the slots granted */
|
||||
if (r.fail || !n)
|
||||
{
|
||||
rnfs_err(c, "create_session reply");
|
||||
rnfs_err(c, "v4 CREATE_SESSION: bad reply");
|
||||
return -1;
|
||||
}
|
||||
c->slots = (uint32_t)(n < RNFS_SLOTS ? n : RNFS_SLOTS);
|
||||
@@ -1075,7 +1254,7 @@ static int nfs4_session_setup(struct rnfs_ctx *c)
|
||||
st = nfs4_res(&r);
|
||||
if (st != NFS3_OK && st != NFS4ERR_COMPLETE_ALREADY)
|
||||
{
|
||||
rnfs_err(c, "reclaim_complete refused");
|
||||
rnfs_err_status(c, "RECLAIM_COMPLETE", NULL, 0, st);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -1101,21 +1280,26 @@ static int nfs4_establish(struct rnfs_ctx *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.
|
||||
* @path in the server's pseudo-filesystem into c->root. With @note,
|
||||
* a refusal says which operation the server refused, and how.
|
||||
*
|
||||
* Returns: 0; -1 when the path is not there; -2 when the call itself
|
||||
* failed, which no other path would change. */
|
||||
static int nfs4_mount_path(struct rnfs_ctx *c, const char *path)
|
||||
static int nfs4_mount_path(struct rnfs_ctx *c, const char *path, int note)
|
||||
{
|
||||
size_t cap = NFS3_FHSIZE + 64 + strlen(path) * 2 + 32;
|
||||
uint8_t *ops = (uint8_t*)malloc(cap);
|
||||
struct xdr x, r;
|
||||
const char *p = path;
|
||||
unsigned n = 1, i;
|
||||
uint32_t st = NFS3_OK;
|
||||
int ret = -1;
|
||||
|
||||
if (!ops)
|
||||
return -1;
|
||||
{
|
||||
rnfs_err(c, "out of memory");
|
||||
return -2;
|
||||
}
|
||||
x.p = ops; x.end = ops + cap; x.fail = 0;
|
||||
xdr_u32(&x, OP_PUTROOTFH);
|
||||
for (;;)
|
||||
@@ -1139,12 +1323,23 @@ static int nfs4_mount_path(struct rnfs_ctx *c, const char *path)
|
||||
ret = -2;
|
||||
goto done;
|
||||
}
|
||||
for (i = 0; i < n - 1; i++)
|
||||
if (nfs4_res(&r) != NFS3_OK)
|
||||
goto done;
|
||||
if (nfs4_res(&r) != NFS3_OK || xdr_get_fh(&r, &c->root) != 0)
|
||||
goto done;
|
||||
ret = 0;
|
||||
for (i = 0; i < n; i++)
|
||||
if ((st = nfs4_res(&r)) != NFS3_OK)
|
||||
break;
|
||||
if (st == NFS3_OK && xdr_get_fh(&r, &c->root) == 0)
|
||||
ret = 0;
|
||||
else if (note && st == NFS3_OK)
|
||||
rnfs_err(c, "v4 mount: bad GETFH reply");
|
||||
else if (note)
|
||||
{
|
||||
const char *step = "mount: LOOKUP";
|
||||
if (i == 0)
|
||||
step = "mount: PUTROOTFH";
|
||||
else if (i == n - 1)
|
||||
step = "mount: GETFH";
|
||||
rnfs_err_status(c, step, (i == 0 || i == n - 1) ? NULL : path,
|
||||
(int)i - 1, st);
|
||||
}
|
||||
done:
|
||||
free(ops);
|
||||
return ret;
|
||||
@@ -1168,7 +1363,7 @@ static int nfs4_mount(struct rnfs_ctx *c)
|
||||
char first[256];
|
||||
const char *e;
|
||||
size_t len;
|
||||
int ret = nfs4_mount_path(c, p);
|
||||
int ret = nfs4_mount_path(c, p, 1);
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
if (ret == -2)
|
||||
@@ -1188,13 +1383,13 @@ static int nfs4_mount(struct rnfs_ctx *c)
|
||||
}
|
||||
memcpy(first, p, len);
|
||||
first[len] = '\0';
|
||||
if ((ret = nfs4_mount_path(c, first)) == 0)
|
||||
if ((ret = nfs4_mount_path(c, first, 0)) == 0)
|
||||
break; /* it is there: the rest is wrong */
|
||||
if (ret == -2)
|
||||
return -1;
|
||||
p = e;
|
||||
}
|
||||
rnfs_err(c, "export not found");
|
||||
/* the last whole path tried has said why */
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1214,10 +1409,14 @@ static int nfs4_walk_once(struct rnfs_ctx *c, const char *path, struct rnfs_fh *
|
||||
struct xdr x, r;
|
||||
const char *p = path;
|
||||
unsigned n = 1, i;
|
||||
uint32_t rs;
|
||||
int ret = -1;
|
||||
|
||||
if (!ops)
|
||||
{
|
||||
rnfs_err(c, "out of memory");
|
||||
return -1;
|
||||
}
|
||||
if (last)
|
||||
last[0] = '\0';
|
||||
x.p = ops; x.end = ops + cap; x.fail = 0;
|
||||
@@ -1233,7 +1432,10 @@ static int nfs4_walk_once(struct rnfs_ctx *c, const char *path, struct rnfs_fh *
|
||||
e = strchr(p, '/');
|
||||
len = e ? (size_t)(e - p) : strlen(p);
|
||||
if (len >= 256)
|
||||
{
|
||||
rnfs_err(c, "name too long");
|
||||
goto done;
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
q = e;
|
||||
@@ -1258,25 +1460,34 @@ static int nfs4_walk_once(struct rnfs_ctx *c, const char *path, struct rnfs_fh *
|
||||
if (x.fail || nfs4_compound(c, ops, (size_t)(x.p - ops), n, &r) != 0)
|
||||
goto done;
|
||||
for (i = 0; i < n - 2; i++)
|
||||
if (nfs4_res(&r) != NFS3_OK)
|
||||
if ((rs = nfs4_res(&r)) != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "not found");
|
||||
rnfs_err_status(c, i == 0 ? "PUTFH" : "LOOKUP",
|
||||
i == 0 ? NULL : path, (int)i - 1, rs);
|
||||
goto done;
|
||||
}
|
||||
if (nfs4_res(&r) != NFS3_OK || xdr_get_fh(&r, fh) != 0)
|
||||
if ((rs = nfs4_res(&r)) != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "not found");
|
||||
rnfs_err_status(c, "GETFH", NULL, 0, rs);
|
||||
goto done;
|
||||
}
|
||||
if (nfs4_res(&r) != NFS3_OK)
|
||||
if (xdr_get_fh(&r, fh) != 0)
|
||||
{
|
||||
rnfs_err(c, "getattr failed");
|
||||
rnfs_err(c, "v4 GETFH: bad reply");
|
||||
goto done;
|
||||
}
|
||||
if ((rs = nfs4_res(&r)) != NFS3_OK)
|
||||
{
|
||||
rnfs_err_status(c, "GETATTR", NULL, 0, rs);
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
struct rnfs_stat tmp;
|
||||
if (nfs4_get_fattr(&r, &tmp) != 0)
|
||||
{
|
||||
rnfs_err(c, "v4 GETATTR: bad reply");
|
||||
goto done;
|
||||
}
|
||||
if (st)
|
||||
*st = tmp;
|
||||
}
|
||||
@@ -1725,10 +1936,13 @@ static int nfs4_readdir_fill(struct rnfs_ctx *c, struct rnfs_dir *d)
|
||||
xdr_u32(&x, 2); xdr_u32(&x, FATTR4_W0_TYPE | FATTR4_W0_SIZE); xdr_u32(&x, FATTR4_W1_TIME_MODIFY);
|
||||
if (nfs4_compound(c, ops, (size_t)(x.p - ops), 2, &r) != 0)
|
||||
return -1;
|
||||
if (nfs4_res(&r) != NFS3_OK || nfs4_res(&r) != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "readdir failed");
|
||||
return -1;
|
||||
uint32_t st;
|
||||
if ((st = nfs4_res(&r)) != NFS3_OK || (st = nfs4_res(&r)) != NFS3_OK)
|
||||
{
|
||||
rnfs_err_status(c, "READDIR", NULL, 0, st);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return rnfs_readdir_take(d, &r);
|
||||
}
|
||||
@@ -1789,6 +2003,7 @@ void rnfs_set_ports(struct rnfs_ctx *c, uint16_t nfs_port, uint16_t mount_port)
|
||||
const char *rnfs_get_error(const struct rnfs_ctx *c) { return c->error; }
|
||||
uint32_t rnfs_get_status(const struct rnfs_ctx *c) { return c->status; }
|
||||
int rnfs_get_fd(const struct rnfs_ctx *c) { return c->fd; }
|
||||
const char *rnfs_get_export(const struct rnfs_ctx *c) { return c->export_path; }
|
||||
|
||||
int rnfs_connect(struct rnfs_ctx *c, const char *server, const char *export_path)
|
||||
{
|
||||
@@ -1841,9 +2056,14 @@ int rnfs_connect(struct rnfs_ctx *c, const char *server, const char *export_path
|
||||
}
|
||||
socket_close(mfd);
|
||||
c->status = xdr_get_u32(&reply);
|
||||
if (c->status != NFS3_OK || xdr_get_fh(&reply, &c->root) != 0)
|
||||
if (c->status != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, c->status == 13 ? "mount refused (access denied)" : "mount failed");
|
||||
rnfs_err_status(c, "MOUNT", NULL, 0, c->status);
|
||||
return -1;
|
||||
}
|
||||
if (xdr_get_fh(&reply, &c->root) != 0)
|
||||
{
|
||||
rnfs_err(c, "v3 MOUNT: bad reply");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1906,8 +2126,14 @@ static int rnfs_redial(struct rnfs_ctx *c)
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
int ret;
|
||||
if (c->redialing || !c->server[0])
|
||||
/* a dial that failed inside a dial has said why */
|
||||
if (c->redialing)
|
||||
return -1;
|
||||
if (!c->server[0])
|
||||
{
|
||||
rnfs_err(c, "not connected");
|
||||
return -1;
|
||||
}
|
||||
strlcpy(server, c->server, sizeof(server));
|
||||
strlcpy(export_path, c->export_path, sizeof(export_path));
|
||||
c->redialing = 1;
|
||||
@@ -2052,7 +2278,7 @@ static int rnfs_walk_from(struct rnfs_ctx *c, const char *norm, size_t start,
|
||||
/* a cached handle the server no longer knows */
|
||||
if (start && (c->status == NFS3ERR_STALE || c->status == NFS3ERR_BADHANDLE))
|
||||
*stale = 1;
|
||||
rnfs_err(c, c->status == NFS3ERR_NOENT ? "not found" : "lookup failed");
|
||||
rnfs_err_status(c, "LOOKUP", p, 0, c->status);
|
||||
return -1;
|
||||
}
|
||||
if (xdr_get_fh(&reply, fh) != 0)
|
||||
@@ -2203,16 +2429,30 @@ struct rnfs_file *rnfs_open(struct rnfs_ctx *c, const char *path, int flags)
|
||||
char last[256];
|
||||
struct rnfs_fh dir;
|
||||
|
||||
c->error[0] = '\0';
|
||||
if (!f)
|
||||
{
|
||||
rnfs_err(c, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
memset(&st, 0, sizeof(st));
|
||||
|
||||
if (c->version == 4)
|
||||
{
|
||||
if (flags & RNFS_O_CREAT)
|
||||
{
|
||||
if (nfs4_walk(c, path, &dir, NULL, 1, last, sizeof(last)) != 0 || !last[0]
|
||||
|| nfs4_open_create(c, &dir, last, (flags & RNFS_O_TRUNC) != 0, f) != 0)
|
||||
if (nfs4_walk(c, path, &dir, NULL, 1, last, sizeof(last)) != 0)
|
||||
{
|
||||
free(f);
|
||||
return NULL;
|
||||
}
|
||||
if (!last[0])
|
||||
{
|
||||
rnfs_err(c, "is a directory");
|
||||
free(f);
|
||||
return NULL;
|
||||
}
|
||||
if (nfs4_open_create(c, &dir, last, (flags & RNFS_O_TRUNC) != 0, f) != 0)
|
||||
{
|
||||
free(f);
|
||||
return NULL;
|
||||
@@ -2249,11 +2489,17 @@ struct rnfs_file *rnfs_open(struct rnfs_ctx *c, const char *path, int flags)
|
||||
{
|
||||
uint8_t args[NFS3_FHSIZE + 4 + 260 + 40];
|
||||
struct xdr x, reply;
|
||||
if (rnfs_walk(c, path, &dir, NULL, 1, last, sizeof(last)) != 0 || !last[0])
|
||||
if (rnfs_walk(c, path, &dir, NULL, 1, last, sizeof(last)) != 0)
|
||||
{
|
||||
free(f);
|
||||
return NULL;
|
||||
}
|
||||
if (!last[0])
|
||||
{
|
||||
rnfs_err(c, "is a directory");
|
||||
free(f);
|
||||
return NULL;
|
||||
}
|
||||
x.p = args; x.end = args + sizeof(args); x.fail = 0;
|
||||
xdr_fh(&x, &dir);
|
||||
xdr_string(&x, last);
|
||||
@@ -2865,18 +3111,26 @@ struct rnfs_dir *rnfs_opendir(struct rnfs_ctx *c, const char *path)
|
||||
{
|
||||
struct rnfs_dir *d = (struct rnfs_dir*)calloc(1, sizeof(*d));
|
||||
struct rnfs_stat st;
|
||||
int walked;
|
||||
c->error[0] = '\0';
|
||||
if (!d)
|
||||
return NULL;
|
||||
if ((c->version == 4 ? nfs4_walk(c, path, &d->fh, &st, 0, NULL, 0)
|
||||
: rnfs_walk(c, path, &d->fh, &st, 0, NULL, 0)) != 0 || !st.is_dir)
|
||||
{
|
||||
if (!c->error[0] || st.is_dir == 0)
|
||||
rnfs_err(c, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
walked = c->version == 4 ? nfs4_walk(c, path, &d->fh, &st, 0, NULL, 0)
|
||||
: rnfs_walk(c, path, &d->fh, &st, 0, NULL, 0);
|
||||
if (walked != 0 || !st.is_dir)
|
||||
{
|
||||
/* a failed walk has said why */
|
||||
if (walked == 0 || !c->error[0])
|
||||
rnfs_err(c, "not a directory");
|
||||
free(d);
|
||||
return NULL;
|
||||
}
|
||||
if (!(d->buf = (uint8_t*)malloc(NFS3_MAX_IO)))
|
||||
{
|
||||
rnfs_err(c, "out of memory");
|
||||
free(d);
|
||||
return NULL;
|
||||
}
|
||||
@@ -2914,7 +3168,7 @@ static int rnfs_readdir_fill(struct rnfs_ctx *c, struct rnfs_dir *d)
|
||||
}
|
||||
if (c->status != NFS3_OK)
|
||||
{
|
||||
rnfs_err(c, "readdir failed");
|
||||
rnfs_err_status(c, d->plain ? "READDIR" : "READDIRPLUS", NULL, 0, c->status);
|
||||
return -1;
|
||||
}
|
||||
xdr_get_post_op_attr(&reply, &st);
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (vfs_cfg_snapshot.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_VFS_CFG_SNAPSHOT_H__
|
||||
#define __LIBRETRO_SDK_VFS_CFG_SNAPSHOT_H__
|
||||
|
||||
/* A network backend's settings, published as copies that never change.
|
||||
*
|
||||
* The menu thread hands new settings over while pool, prefetch and task
|
||||
* threads read them, some across a connect. A writer builds a whole new
|
||||
* copy and swaps it in; a reader enters, uses the copy it got until it
|
||||
* leaves, and the copy it replaced is freed once nothing holds it, by
|
||||
* whichever side is last. Nothing waits on a reader.
|
||||
*
|
||||
* Where the pointer atomics are lock-free and there are threads, entry
|
||||
* and leave are retro_retire.h's epochs. Elsewhere each copy counts its
|
||||
* holders under a flag held for a few loads and stores, never across a
|
||||
* call, as x509.c's store does.
|
||||
*
|
||||
* A copy is one allocation starting with a vfs_cfg_hdr_t, freed with
|
||||
* free(). A zero-filled vfs_cfg_slot_t is ready. */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
#include <retro_inline.h>
|
||||
#include <queues/retro_retire.h>
|
||||
#ifdef HAVE_THREADS
|
||||
#include <rthreads/rthreads.h>
|
||||
#endif
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_RETIRE_LOCK_FREE)
|
||||
#define VFS_CFG_LOCK_FREE 1
|
||||
#endif
|
||||
|
||||
typedef struct vfs_cfg_hdr
|
||||
{
|
||||
mpsc_stack_node_t node; /* lock-free: on the retire list */
|
||||
unsigned refs; /* otherwise, under the flag: the
|
||||
* slot's own plus each holder's */
|
||||
} vfs_cfg_hdr_t;
|
||||
|
||||
typedef struct vfs_cfg_slot
|
||||
{
|
||||
#ifdef VFS_CFG_LOCK_FREE
|
||||
retro_atomic_ptr_t cur; /* vfs_cfg_hdr_t * */
|
||||
retro_retire_t retired;
|
||||
#else
|
||||
vfs_cfg_hdr_t *cur;
|
||||
retro_atomic_int_t busy;
|
||||
#endif
|
||||
} vfs_cfg_slot_t;
|
||||
|
||||
#ifdef VFS_CFG_LOCK_FREE
|
||||
static void vfs_cfg_free_node(mpsc_stack_node_t *node)
|
||||
{
|
||||
free(node);
|
||||
}
|
||||
|
||||
/* The published copy (NULL: none), valid until vfs_cfg_leave(). */
|
||||
static INLINE vfs_cfg_hdr_t *vfs_cfg_enter(vfs_cfg_slot_t *s)
|
||||
{
|
||||
retro_retire_enter(&s->retired);
|
||||
return (vfs_cfg_hdr_t*)retro_atomic_load_acquire_ptr(&s->cur);
|
||||
}
|
||||
|
||||
static INLINE void vfs_cfg_leave(vfs_cfg_slot_t *s, vfs_cfg_hdr_t *h)
|
||||
{
|
||||
(void)h;
|
||||
retro_retire_leave(&s->retired, vfs_cfg_free_node);
|
||||
}
|
||||
|
||||
/* @h (NULL: none) replaces what is published; its payload is written. */
|
||||
static INLINE void vfs_cfg_publish(vfs_cfg_slot_t *s, vfs_cfg_hdr_t *h)
|
||||
{
|
||||
vfs_cfg_hdr_t *old;
|
||||
do
|
||||
old = (vfs_cfg_hdr_t*)retro_atomic_load_acquire_ptr(&s->cur);
|
||||
while (!retro_atomic_cas_ptr(&s->cur, old, h));
|
||||
retro_retire(&s->retired, old ? &old->node : NULL, vfs_cfg_free_node);
|
||||
}
|
||||
#else
|
||||
static INLINE void vfs_cfg_lock(vfs_cfg_slot_t *s)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
while (!retro_atomic_cas_int(&s->busy, 0, 1))
|
||||
sthread_yield();
|
||||
#else
|
||||
(void)s;
|
||||
#endif
|
||||
}
|
||||
|
||||
static INLINE void vfs_cfg_unlock(vfs_cfg_slot_t *s)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
retro_atomic_store_release_int(&s->busy, 0);
|
||||
#else
|
||||
(void)s;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The published copy (NULL: none), valid until vfs_cfg_leave(). */
|
||||
static INLINE vfs_cfg_hdr_t *vfs_cfg_enter(vfs_cfg_slot_t *s)
|
||||
{
|
||||
vfs_cfg_hdr_t *h;
|
||||
vfs_cfg_lock(s);
|
||||
if ((h = s->cur))
|
||||
h->refs++;
|
||||
vfs_cfg_unlock(s);
|
||||
return h;
|
||||
}
|
||||
|
||||
static INLINE void vfs_cfg_leave(vfs_cfg_slot_t *s, vfs_cfg_hdr_t *h)
|
||||
{
|
||||
unsigned left;
|
||||
if (!h)
|
||||
return;
|
||||
vfs_cfg_lock(s);
|
||||
left = --h->refs;
|
||||
vfs_cfg_unlock(s);
|
||||
if (!left)
|
||||
free(h);
|
||||
}
|
||||
|
||||
/* @h (NULL: none) replaces what is published; its payload is written. */
|
||||
static INLINE void vfs_cfg_publish(vfs_cfg_slot_t *s, vfs_cfg_hdr_t *h)
|
||||
{
|
||||
vfs_cfg_hdr_t *old;
|
||||
unsigned left = 1;
|
||||
if (h)
|
||||
h->refs = 1;
|
||||
vfs_cfg_lock(s);
|
||||
old = s->cur;
|
||||
s->cur = h;
|
||||
if (old)
|
||||
left = --old->refs;
|
||||
vfs_cfg_unlock(s);
|
||||
if (old && !left)
|
||||
free(old);
|
||||
}
|
||||
#endif
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -3965,7 +3965,7 @@ libretro_vfs_implementation_dir *retro_vfs_opendir_impl(
|
||||
if (path_is_smb(name))
|
||||
{
|
||||
smb_dir_handle *dh = retro_vfs_opendir_smb(name, include_hidden);
|
||||
if (!dh || !dh->dir)
|
||||
if (!dh)
|
||||
{
|
||||
free(rdir->orig_path);
|
||||
free(rdir);
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -34,6 +33,7 @@
|
||||
#include <vfs/vfs_prefetch.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
#include "vfs_implementation_nfs.h"
|
||||
#include "vfs_cfg_snapshot.h"
|
||||
|
||||
#define NFS_PREFIX "nfs://"
|
||||
#define NFS_MAX_SLOTS 16
|
||||
@@ -45,24 +45,48 @@
|
||||
* no slot between calls - every read borrows one. When every slot is
|
||||
* busy the caller makes a private connection for that one call and
|
||||
* drops it afterwards: slower, never blocked, never waiting on a
|
||||
* stalled server through someone else's slot. The pool itself is
|
||||
* published once by nfs_init_cfg() and only replaced while nothing
|
||||
* holds a slot. */
|
||||
* stalled server through someone else's slot. The pool is published
|
||||
* once and kept until nfs_shutdown(): new settings close the idle
|
||||
* connections, and a slot serves only callers that ask for the
|
||||
* parameters it connected with. */
|
||||
|
||||
/* What a connection was made with. An open file or directory keeps
|
||||
* the ones it was opened with: its handle means nothing on another
|
||||
* version or port. */
|
||||
struct nfs_params
|
||||
{
|
||||
unsigned nfs_port;
|
||||
unsigned mount_port;
|
||||
unsigned version;
|
||||
};
|
||||
|
||||
struct nfs_slot
|
||||
{
|
||||
struct rnfs_ctx *ctx;
|
||||
retro_atomic_int_t busy;
|
||||
char export_path[512]; /* written by the holder only */
|
||||
/* written by the holder only */
|
||||
struct nfs_params params;
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
};
|
||||
|
||||
struct nfs_pool
|
||||
{
|
||||
struct nfs_slot slots[NFS_MAX_SLOTS];
|
||||
unsigned count;
|
||||
struct nfs_slot slots[NFS_MAX_SLOTS];
|
||||
retro_atomic_int_t count;
|
||||
};
|
||||
|
||||
static retro_atomic_ptr_t nfs_pool_ptr; /* struct nfs_pool * */
|
||||
static retro_atomic_ptr_t nfs_cfg_ptr; /* const struct nfs_settings * */
|
||||
/* The settings as last handed over: a copy that never changes, held
|
||||
* between nfs_cfg_enter() and nfs_cfg_leave() (vfs_cfg_snapshot.h) */
|
||||
struct nfs_cfg_snap
|
||||
{
|
||||
vfs_cfg_hdr_t hdr;
|
||||
struct nfs_settings s;
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
};
|
||||
static vfs_cfg_slot_t nfs_cfg_slot;
|
||||
/* Last error for nfs_get_last_error(), used only for diagnostics off
|
||||
* the hot path. Writers may run on several pool threads at once, so
|
||||
* each publishes into its own ring slot (claimed by an atomic counter)
|
||||
@@ -74,9 +98,38 @@ static char nfs_err_ring[NFS_ERR_SLOTS][128];
|
||||
static retro_atomic_int_t nfs_err_seq; /* next slot to hand out */
|
||||
static retro_atomic_int_t nfs_err_latest; /* slot last published, or -1 */
|
||||
|
||||
static const struct nfs_settings *nfs_cfg(void)
|
||||
static void nfs_note_error(const char *msg);
|
||||
|
||||
/* The settings (NULL: none), valid until nfs_cfg_leave(*@h). */
|
||||
static const struct nfs_settings *nfs_cfg_enter(vfs_cfg_hdr_t **h)
|
||||
{
|
||||
return (const struct nfs_settings*)retro_atomic_load_acquire_ptr(&nfs_cfg_ptr);
|
||||
*h = vfs_cfg_enter(&nfs_cfg_slot);
|
||||
return *h ? &((struct nfs_cfg_snap*)*h)->s : NULL;
|
||||
}
|
||||
|
||||
static void nfs_cfg_leave(vfs_cfg_hdr_t *h)
|
||||
{
|
||||
vfs_cfg_leave(&nfs_cfg_slot, h);
|
||||
}
|
||||
|
||||
/* What the client applies on every take, whatever a connection was
|
||||
* made with: nothing a handle depends on. */
|
||||
struct nfs_numbers
|
||||
{
|
||||
unsigned timeout;
|
||||
unsigned readahead; /* octets; 0: one request per read */
|
||||
unsigned num_contexts;
|
||||
};
|
||||
|
||||
static void nfs_cfg_numbers(struct nfs_numbers *n)
|
||||
{
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct nfs_settings *cfg = nfs_cfg_enter(&h);
|
||||
n->timeout = cfg && cfg->timeout ? cfg->timeout : RETRO_NFS_DEFAULT_TIMEOUT;
|
||||
n->readahead = cfg ? cfg->readahead * 1024 : 0;
|
||||
n->num_contexts = cfg && cfg->num_contexts ? cfg->num_contexts
|
||||
: RETRO_NFS_DEFAULT_NUM_CONTEXTS;
|
||||
nfs_cfg_leave(h);
|
||||
}
|
||||
|
||||
/* Splits nfs://server/... into the server and the part after it. */
|
||||
@@ -99,27 +152,30 @@ static bool nfs_url_split(const char *url, char *server, size_t server_len,
|
||||
}
|
||||
|
||||
/* With a configured export, the URL path is relative to it.
|
||||
* Otherwise mount prefixes of nfs://server/export/path. */
|
||||
* Otherwise mount prefixes of nfs://server/export/path. *@export_path
|
||||
* points into the settings held in *@h, which the caller leaves with
|
||||
* nfs_cfg_leave() once done with it; nothing is held on failure. */
|
||||
static bool nfs_resolve(const char *url, char *server, size_t server_len,
|
||||
char *export_path, size_t export_len, char *path, size_t path_len)
|
||||
const char **export_path, char *path, size_t path_len,
|
||||
struct nfs_params *params, vfs_cfg_hdr_t **h)
|
||||
{
|
||||
const char *rest;
|
||||
const struct nfs_settings *cfg = nfs_cfg();
|
||||
const struct nfs_settings *cfg = nfs_cfg_enter(h);
|
||||
if (!nfs_url_split(url, server, server_len, &rest))
|
||||
{
|
||||
nfs_note_error("bad URL: no server in it");
|
||||
nfs_cfg_leave(*h);
|
||||
*h = NULL;
|
||||
return false;
|
||||
if (cfg && cfg->export_path && *cfg->export_path)
|
||||
{
|
||||
strlcpy(export_path, cfg->export_path, export_len);
|
||||
strlcpy(path, rest, path_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* nfs://server/export/path: where the export ends is the
|
||||
* server's to say, so the split is found at connect time by
|
||||
* mounting prefixes; the whole remainder is handed over here. */
|
||||
export_path[0] = '\0';
|
||||
strlcpy(path, rest, path_len);
|
||||
}
|
||||
params->nfs_port = cfg ? cfg->nfs_port : 0;
|
||||
params->mount_port = cfg ? cfg->mount_port : 0;
|
||||
params->version = cfg && cfg->version == 4 ? 4 : 3;
|
||||
/* nfs://server/export/path with no export set: where the export
|
||||
* ends is the server's to say, so the split is found at connect time
|
||||
* by mounting prefixes; the whole remainder is handed over here. */
|
||||
*export_path = cfg && *cfg->export_path ? cfg->export_path : "";
|
||||
strlcpy(path, rest, path_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -128,7 +184,7 @@ static void nfs_pool_free(struct nfs_pool *p)
|
||||
unsigned i;
|
||||
if (!p)
|
||||
return;
|
||||
for (i = 0; i < p->count; i++)
|
||||
for (i = 0; i < NFS_MAX_SLOTS; i++)
|
||||
rnfs_free(p->slots[i].ctx);
|
||||
free(p);
|
||||
}
|
||||
@@ -136,36 +192,69 @@ static void nfs_pool_free(struct nfs_pool *p)
|
||||
static int nfs_pool_busy(const struct nfs_pool *p)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; p && i < p->count; i++)
|
||||
for (i = 0; p && i < NFS_MAX_SLOTS; i++)
|
||||
if (retro_atomic_load_acquire_int(&p->slots[i].busy))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The pool, published with @n slots when there is none yet. */
|
||||
static struct nfs_pool *nfs_pool_get(unsigned n)
|
||||
{
|
||||
struct nfs_pool *p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
if (p)
|
||||
return p;
|
||||
if (!(p = (struct nfs_pool*)calloc(1, sizeof(*p))))
|
||||
return NULL;
|
||||
retro_atomic_int_init(&p->count, (int)(n > NFS_MAX_SLOTS ? NFS_MAX_SLOTS : n));
|
||||
if (!retro_atomic_cas_ptr(&nfs_pool_ptr, NULL, p))
|
||||
free(p); /* someone else published one meanwhile */
|
||||
return (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
}
|
||||
|
||||
/* Settings are optional: an nfs://server/export/... URL carries all a
|
||||
* connection needs, so the pool comes up with defaults when there are
|
||||
* none. A new pool replaces the old one only when nothing holds a
|
||||
* slot; otherwise the settings are published and the old pool stays
|
||||
* until the next call here. */
|
||||
* none. @new_cfg is copied, strings included, so the caller may change
|
||||
* or free it at once. New settings wait for no slot: idle connections
|
||||
* close here. */
|
||||
bool nfs_init_cfg(const struct nfs_settings *new_cfg)
|
||||
{
|
||||
struct nfs_pool *old = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
struct nfs_cfg_snap *snap = NULL;
|
||||
struct nfs_pool *p;
|
||||
unsigned i;
|
||||
unsigned n = (new_cfg && new_cfg->num_contexts) ? new_cfg->num_contexts : RETRO_NFS_DEFAULT_NUM_CONTEXTS;
|
||||
|
||||
retro_atomic_store_release_ptr(&nfs_cfg_ptr, (void*)new_cfg);
|
||||
if (n > NFS_MAX_SLOTS)
|
||||
n = NFS_MAX_SLOTS;
|
||||
if (old && nfs_pool_busy(old))
|
||||
if (new_cfg)
|
||||
{
|
||||
if (!(snap = (struct nfs_cfg_snap*)calloc(1, sizeof(*snap))))
|
||||
return false;
|
||||
snap->s = *new_cfg;
|
||||
if (new_cfg->server_address)
|
||||
strlcpy(snap->server, new_cfg->server_address, sizeof(snap->server));
|
||||
if (new_cfg->export_path)
|
||||
strlcpy(snap->export_path, new_cfg->export_path, sizeof(snap->export_path));
|
||||
snap->s.server_address = snap->server;
|
||||
snap->s.export_path = snap->export_path;
|
||||
}
|
||||
vfs_cfg_publish(&nfs_cfg_slot, snap ? &snap->hdr : NULL);
|
||||
if ((p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr)))
|
||||
{
|
||||
retro_atomic_store_release_int(&p->count, (int)n);
|
||||
for (i = 0; i < NFS_MAX_SLOTS; i++)
|
||||
{
|
||||
struct nfs_slot *s = &p->slots[i];
|
||||
if (!retro_atomic_cas_int(&s->busy, 0, 1))
|
||||
continue;
|
||||
rnfs_free(s->ctx);
|
||||
s->ctx = NULL;
|
||||
s->export_path[0] = '\0';
|
||||
retro_atomic_store_release_int(&s->busy, 0);
|
||||
}
|
||||
return true;
|
||||
if (!(p = (struct nfs_pool*)calloc(1, sizeof(*p))))
|
||||
return false;
|
||||
p->count = n;
|
||||
if (retro_atomic_cas_ptr(&nfs_pool_ptr, old, p))
|
||||
nfs_pool_free(old);
|
||||
else
|
||||
free(p); /* someone else published one meanwhile */
|
||||
return true;
|
||||
}
|
||||
return nfs_pool_get(n) != NULL;
|
||||
}
|
||||
|
||||
void nfs_shutdown(void)
|
||||
@@ -198,32 +287,36 @@ const char *nfs_get_last_error(void)
|
||||
return nfs_err_ring[slot & (NFS_ERR_SLOTS - 1)];
|
||||
}
|
||||
|
||||
/* A connection to @server:@export for one caller: a free slot already
|
||||
* there, else a free slot reconnected, else a private connection.
|
||||
* *slot is NULL for a private one. */
|
||||
/* A connection to @server:@export made with @want for one caller: a
|
||||
* free slot already there, else a free slot reconnected, else a
|
||||
* private connection. *slot is NULL for a private one. */
|
||||
static struct rnfs_ctx *nfs_take(const char *server, const char *export_path,
|
||||
struct nfs_slot **slot)
|
||||
const struct nfs_params *want, struct nfs_slot **slot)
|
||||
{
|
||||
struct nfs_pool *p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
const struct nfs_settings *cfg = nfs_cfg();
|
||||
struct nfs_pool *p;
|
||||
struct nfs_numbers num;
|
||||
struct rnfs_ctx *c = NULL;
|
||||
unsigned i, pass;
|
||||
unsigned i, n, pass;
|
||||
|
||||
*slot = NULL;
|
||||
if (!p)
|
||||
{
|
||||
nfs_init_cfg(cfg);
|
||||
p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
}
|
||||
/* pass 0: a free slot on the right export; pass 1: any free slot */
|
||||
for (pass = 0; p && pass < 2 && !c; pass++)
|
||||
for (i = 0; i < p->count; i++)
|
||||
nfs_cfg_numbers(&num);
|
||||
p = nfs_pool_get(num.num_contexts);
|
||||
n = p ? (unsigned)retro_atomic_load_acquire_int(&p->count) : 0;
|
||||
/* pass 0: a free slot on the right server and export, connected
|
||||
* with @want; pass 1: a free slot with no connection, so an open
|
||||
* file's slot on other parameters is not taken from it at once;
|
||||
* pass 2: any free slot */
|
||||
for (pass = 0; pass < 3 && !*slot; pass++)
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
struct nfs_slot *s = &p->slots[i];
|
||||
if (!retro_atomic_cas_int(&s->busy, 0, 1))
|
||||
continue;
|
||||
if (pass == 0 && (!s->ctx || rnfs_get_fd(s->ctx) < 0
|
||||
if ( (pass == 0 && (!s->ctx || rnfs_get_fd(s->ctx) < 0
|
||||
|| memcmp(&s->params, want, sizeof(*want)) != 0
|
||||
|| strcmp(s->server, server) != 0
|
||||
|| strcmp(s->export_path, export_path) != 0))
|
||||
|| (pass == 1 && s->ctx && rnfs_get_fd(s->ctx) >= 0))
|
||||
{
|
||||
retro_atomic_store_release_int(&s->busy, 0);
|
||||
continue;
|
||||
@@ -250,17 +343,18 @@ static struct rnfs_ctx *nfs_take(const char *server, const char *export_path,
|
||||
*slot = NULL;
|
||||
}
|
||||
if (!c)
|
||||
return NULL;
|
||||
if (rnfs_get_fd(c) < 0 || !*slot || strcmp((*slot)->export_path, export_path) != 0)
|
||||
{
|
||||
rnfs_set_timeout(c, cfg && cfg->timeout ? cfg->timeout : RETRO_NFS_DEFAULT_TIMEOUT);
|
||||
if (cfg)
|
||||
{
|
||||
rnfs_set_ports(c, (uint16_t)cfg->nfs_port, (uint16_t)cfg->mount_port);
|
||||
rnfs_set_version(c, cfg->version ? cfg->version : 3);
|
||||
/* 0 is off: one request per read, as before read-ahead */
|
||||
rnfs_set_readahead(c, cfg->readahead * 1024);
|
||||
}
|
||||
nfs_note_error("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
if ( rnfs_get_fd(c) < 0 || !*slot
|
||||
|| memcmp(&(*slot)->params, want, sizeof(*want)) != 0
|
||||
|| strcmp((*slot)->server, server) != 0
|
||||
|| strcmp((*slot)->export_path, export_path) != 0)
|
||||
{
|
||||
rnfs_set_timeout(c, num.timeout);
|
||||
rnfs_set_ports(c, (uint16_t)want->nfs_port, (uint16_t)want->mount_port);
|
||||
rnfs_set_version(c, want->version);
|
||||
if (rnfs_connect(c, server, export_path) != 0)
|
||||
{
|
||||
nfs_note_error(rnfs_get_error(c));
|
||||
@@ -274,17 +368,39 @@ static struct rnfs_ctx *nfs_take(const char *server, const char *export_path,
|
||||
return NULL;
|
||||
}
|
||||
if (*slot)
|
||||
{
|
||||
(*slot)->params = *want;
|
||||
strlcpy((*slot)->server, server, sizeof((*slot)->server));
|
||||
strlcpy((*slot)->export_path, export_path, sizeof((*slot)->export_path));
|
||||
}
|
||||
}
|
||||
/* client side only, so they follow the settings on any connection */
|
||||
rnfs_set_timeout(c, num.timeout);
|
||||
rnfs_set_readahead(c, num.readahead);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void nfs_give(struct rnfs_ctx *c, struct nfs_slot *slot)
|
||||
{
|
||||
if (slot)
|
||||
retro_atomic_store_release_int(&slot->busy, 0);
|
||||
else
|
||||
struct nfs_pool *p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
unsigned i;
|
||||
if (!slot)
|
||||
{
|
||||
rnfs_free(c);
|
||||
return;
|
||||
}
|
||||
/* past a lowered count no take reaches the slot, and nfs_init_cfg()
|
||||
* skipped it while held: close it here */
|
||||
for (i = p ? (unsigned)retro_atomic_load_acquire_int(&p->count) : NFS_MAX_SLOTS;
|
||||
i < NFS_MAX_SLOTS; i++)
|
||||
if (slot == &p->slots[i])
|
||||
{
|
||||
rnfs_free(slot->ctx);
|
||||
slot->ctx = NULL;
|
||||
slot->export_path[0] = '\0';
|
||||
break;
|
||||
}
|
||||
retro_atomic_store_release_int(&slot->busy, 0);
|
||||
}
|
||||
|
||||
/* With no export in the settings the URL's path starts with it: a
|
||||
@@ -292,21 +408,26 @@ static void nfs_give(struct rnfs_ctx *c, struct nfs_slot *slot)
|
||||
* prefixes are tried shortest first until the server accepts one.
|
||||
* *rel points at the remainder of @path under the export. */
|
||||
static struct rnfs_ctx *nfs_take_url(const char *server, const char *path,
|
||||
const char **rel, struct nfs_slot **slot)
|
||||
const struct nfs_params *want, const char **rel, struct nfs_slot **slot)
|
||||
{
|
||||
struct nfs_pool *p = (struct nfs_pool*)retro_atomic_load_acquire_ptr(&nfs_pool_ptr);
|
||||
char export_buf[512];
|
||||
unsigned i;
|
||||
unsigned count = p ? (unsigned)retro_atomic_load_acquire_int(&p->count) : 0;
|
||||
const char *q;
|
||||
bool tried = false;
|
||||
|
||||
for (i = 0; p && i < p->count; i++)
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
struct nfs_slot *s = &p->slots[i];
|
||||
size_t el;
|
||||
if (!retro_atomic_cas_int(&s->busy, 0, 1))
|
||||
continue;
|
||||
el = strlen(s->export_path);
|
||||
if (s->ctx && el && rnfs_get_fd(s->ctx) >= 0 && strncmp(path, s->export_path, el) == 0
|
||||
if (s->ctx && el && rnfs_get_fd(s->ctx) >= 0
|
||||
&& memcmp(&s->params, want, sizeof(*want)) == 0
|
||||
&& strcmp(s->server, server) == 0
|
||||
&& strncmp(path, s->export_path, el) == 0
|
||||
&& (path[el] == '/' || path[el] == '\0'))
|
||||
{
|
||||
*rel = path + el;
|
||||
@@ -331,7 +452,8 @@ static struct rnfs_ctx *nfs_take_url(const char *server, const char *path,
|
||||
break;
|
||||
memcpy(export_buf, path, n);
|
||||
export_buf[n] = '\0';
|
||||
if ((c = nfs_take(server, export_buf, slot)))
|
||||
tried = true;
|
||||
if ((c = nfs_take(server, export_buf, want, slot)))
|
||||
{
|
||||
*rel = path + n;
|
||||
return c;
|
||||
@@ -340,18 +462,23 @@ static struct rnfs_ctx *nfs_take_url(const char *server, const char *path,
|
||||
break;
|
||||
q = e;
|
||||
}
|
||||
/* otherwise the last export tried has said why */
|
||||
if (!tried)
|
||||
nfs_note_error(*q ? "export path too long"
|
||||
: "no export: set NFS Export, or use nfs://server/export/path");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* nfs_take() for a resolved URL: the configured export, or the path's. */
|
||||
static struct rnfs_ctx *nfs_take_resolved(const char *server,
|
||||
const char *export_path, char *rel, struct nfs_slot **slot)
|
||||
const char *export_path, char *rel, const struct nfs_params *want,
|
||||
struct nfs_slot **slot)
|
||||
{
|
||||
if (*export_path)
|
||||
return nfs_take(server, export_path, slot);
|
||||
return nfs_take(server, export_path, want, slot);
|
||||
{
|
||||
const char *tail = NULL;
|
||||
struct rnfs_ctx *c = nfs_take_url(server, rel, &tail, slot);
|
||||
struct rnfs_ctx *c = nfs_take_url(server, rel, want, &tail, slot);
|
||||
if (c)
|
||||
memmove(rel, tail, strlen(tail) + 1);
|
||||
return c;
|
||||
@@ -360,40 +487,51 @@ static struct rnfs_ctx *nfs_take_resolved(const char *server,
|
||||
|
||||
bool nfs_probe_connection(void)
|
||||
{
|
||||
char server[256], export_path[512], path[8];
|
||||
char server[256], path[8];
|
||||
char url[800];
|
||||
const char *export_path;
|
||||
struct nfs_params want;
|
||||
struct nfs_slot *slot;
|
||||
struct rnfs_ctx *c;
|
||||
const struct nfs_settings *cfg = nfs_cfg();
|
||||
bool ok;
|
||||
if (!cfg || !cfg->server_address || !*cfg->server_address)
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct nfs_settings *cfg = nfs_cfg_enter(&h);
|
||||
bool ok = cfg && *cfg->server_address;
|
||||
if (ok)
|
||||
{
|
||||
strlcpy(url, NFS_PREFIX, sizeof(url));
|
||||
strlcat(url, cfg->server_address, sizeof(url));
|
||||
strlcat(url, "/", sizeof(url));
|
||||
}
|
||||
nfs_cfg_leave(h);
|
||||
if (!ok || !nfs_resolve(url, server, sizeof(server), &export_path, path, sizeof(path), &want, &h))
|
||||
return false;
|
||||
strlcpy(url, NFS_PREFIX, sizeof(url));
|
||||
strlcat(url, cfg->server_address, sizeof(url));
|
||||
strlcat(url, "/", sizeof(url));
|
||||
if (!nfs_resolve(url, server, sizeof(server), export_path, sizeof(export_path), path, sizeof(path)))
|
||||
return false;
|
||||
if (!(c = nfs_take_resolved(server, export_path, path, &slot)))
|
||||
return false;
|
||||
ok = rnfs_ping(c) == 0;
|
||||
nfs_give(c, slot);
|
||||
if ((c = nfs_take_resolved(server, export_path, path, &want, &slot)))
|
||||
{
|
||||
ok = rnfs_ping(c) == 0;
|
||||
nfs_give(c, slot);
|
||||
}
|
||||
else
|
||||
ok = false;
|
||||
nfs_cfg_leave(h);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* ---- files -------------------------------------------------------- */
|
||||
|
||||
/* A stream keeps the resolved server and export (its handle works on
|
||||
* any connection to that export) beside the rnfs_file. */
|
||||
/* A stream keeps the resolved server and export and the parameters it
|
||||
* was opened with (its handle works on any such connection to that
|
||||
* export) beside the rnfs_file. */
|
||||
struct nfs_stream
|
||||
{
|
||||
struct rnfs_file *f;
|
||||
struct nfs_params params;
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
};
|
||||
|
||||
static struct rnfs_ctx *nfs_stream_take(const struct nfs_stream *st, struct nfs_slot **slot)
|
||||
{
|
||||
return nfs_take(st->server, st->export_path, slot);
|
||||
return nfs_take(st->server, st->export_path, &st->params, slot);
|
||||
}
|
||||
|
||||
/* The background prefetcher of a read-only stream: a connection held
|
||||
@@ -443,9 +581,11 @@ static void nfs_prefetch_start(libretro_vfs_implementation_file *stream,
|
||||
const struct nfs_stream *ns, const char *rel)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
const struct nfs_settings *cfg = nfs_cfg();
|
||||
struct nfs_numbers num;
|
||||
struct nfs_prefetch *np;
|
||||
size_t window = cfg ? (size_t)cfg->readahead * 1024 : 0;
|
||||
size_t window;
|
||||
nfs_cfg_numbers(&num);
|
||||
window = num.readahead;
|
||||
/* read-ahead off: no second connection, no thread */
|
||||
if (!window)
|
||||
return;
|
||||
@@ -483,15 +623,15 @@ static void nfs_prefetch_start(libretro_vfs_implementation_file *stream,
|
||||
static void nfs_prefetch_stop(libretro_vfs_implementation_file *stream)
|
||||
{
|
||||
struct nfs_prefetch *np = (struct nfs_prefetch*)(void*)(uintptr_t)stream->nfs_prefetch;
|
||||
const struct nfs_settings *cfg;
|
||||
struct nfs_numbers num;
|
||||
if (!np)
|
||||
return;
|
||||
vfs_prefetch_free(np->p); /* joins the thread first */
|
||||
retro_atomic_fetch_add_int(&nfs_ra_window_kib, (int)((np->from_window + 512) >> 10));
|
||||
retro_atomic_fetch_add_int(&nfs_ra_direct_kib, (int)((np->direct + 512) >> 10));
|
||||
rnfs_close(np->c, np->f);
|
||||
cfg = nfs_cfg();
|
||||
rnfs_set_readahead(np->c, cfg ? cfg->readahead * 1024 : 0);
|
||||
nfs_cfg_numbers(&num);
|
||||
rnfs_set_readahead(np->c, num.readahead);
|
||||
nfs_give(np->c, np->slot);
|
||||
free(np);
|
||||
stream->nfs_prefetch = (intptr_t)0;
|
||||
@@ -500,17 +640,18 @@ static void nfs_prefetch_stop(libretro_vfs_implementation_file *stream)
|
||||
bool retro_vfs_file_open_nfs(libretro_vfs_implementation_file *stream,
|
||||
const char *path, unsigned mode, unsigned hints)
|
||||
{
|
||||
char server[256], export_path[512], rel[PATH_MAX_LENGTH];
|
||||
char server[256], rel[PATH_MAX_LENGTH];
|
||||
const char *export_path;
|
||||
struct nfs_params want;
|
||||
struct nfs_slot *slot;
|
||||
struct rnfs_ctx *c;
|
||||
struct rnfs_file *f;
|
||||
struct rnfs_stat st;
|
||||
struct nfs_stream *ns;
|
||||
vfs_cfg_hdr_t *h;
|
||||
int flags = 0;
|
||||
(void)hints;
|
||||
|
||||
if (!nfs_resolve(path, server, sizeof(server), export_path, sizeof(export_path), rel, sizeof(rel)))
|
||||
return false;
|
||||
switch (mode & 0xf)
|
||||
{
|
||||
case RETRO_VFS_FILE_ACCESS_READ:
|
||||
@@ -527,29 +668,35 @@ bool retro_vfs_file_open_nfs(libretro_vfs_implementation_file *stream,
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (!(c = nfs_take_resolved(server, export_path, rel, &slot)))
|
||||
if (!nfs_resolve(path, server, sizeof(server), &export_path, rel, sizeof(rel), &want, &h))
|
||||
return false;
|
||||
if (!(c = nfs_take_resolved(server, export_path, rel, &want, &slot)))
|
||||
{
|
||||
nfs_cfg_leave(h);
|
||||
return false;
|
||||
}
|
||||
if (!(ns = (struct nfs_stream*)calloc(1, sizeof(*ns))))
|
||||
{
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
return false;
|
||||
}
|
||||
if (!(f = rnfs_open(c, rel, flags)))
|
||||
{
|
||||
nfs_note_error(rnfs_get_error(c));
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
free(ns);
|
||||
return false;
|
||||
}
|
||||
stream->size = rnfs_stat(c, rel, &st) == 0 ? (int64_t)st.size : 0;
|
||||
ns->f = f;
|
||||
ns->f = f;
|
||||
ns->params = want;
|
||||
strlcpy(ns->server, server, sizeof(ns->server));
|
||||
/* the export a URL-only mount ended up on is the slot's; a private
|
||||
* connection reports it through the context */
|
||||
strlcpy(ns->export_path, slot ? slot->export_path : export_path, sizeof(ns->export_path));
|
||||
if (!*ns->export_path)
|
||||
strlcpy(ns->export_path, export_path, sizeof(ns->export_path));
|
||||
/* with no export set, the one the URL's path was mounted on */
|
||||
strlcpy(ns->export_path, rnfs_get_export(c), sizeof(ns->export_path));
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
stream->nfs_fh = (intptr_t)ns;
|
||||
stream->nfs_ctx = 0;
|
||||
stream->nfs_prefetch = 0;
|
||||
@@ -695,22 +842,27 @@ int retro_vfs_file_error_nfs(libretro_vfs_implementation_file *stream)
|
||||
|
||||
int retro_vfs_stat_nfs(const char *path, int64_t *size)
|
||||
{
|
||||
char server[256], export_path[512], rel[PATH_MAX_LENGTH];
|
||||
char server[256], rel[PATH_MAX_LENGTH];
|
||||
const char *export_path;
|
||||
struct nfs_params want;
|
||||
struct nfs_slot *slot;
|
||||
struct rnfs_ctx *c;
|
||||
struct rnfs_stat st;
|
||||
vfs_cfg_hdr_t *h;
|
||||
int ret = 0;
|
||||
if (!nfs_resolve(path, server, sizeof(server), export_path, sizeof(export_path), rel, sizeof(rel)))
|
||||
if (!nfs_resolve(path, server, sizeof(server), &export_path, rel, sizeof(rel), &want, &h))
|
||||
return 0;
|
||||
if (!(c = nfs_take_resolved(server, export_path, rel, &slot)))
|
||||
return 0;
|
||||
if (rnfs_stat(c, rel, &st) == 0)
|
||||
if ((c = nfs_take_resolved(server, export_path, rel, &want, &slot)))
|
||||
{
|
||||
if (size)
|
||||
*size = (int64_t)st.size;
|
||||
ret = RETRO_VFS_STAT_IS_VALID | (st.is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0);
|
||||
if (rnfs_stat(c, rel, &st) == 0)
|
||||
{
|
||||
if (size)
|
||||
*size = (int64_t)st.size;
|
||||
ret = RETRO_VFS_STAT_IS_VALID | (st.is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0);
|
||||
}
|
||||
nfs_give(c, slot);
|
||||
}
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -722,45 +874,55 @@ int retro_vfs_stat_nfs(const char *path, int64_t *size)
|
||||
struct nfs_dirstate
|
||||
{
|
||||
struct rnfs_dir *d;
|
||||
struct nfs_params params;
|
||||
char server[256];
|
||||
char export_path[512];
|
||||
};
|
||||
|
||||
nfs_dir_handle *retro_vfs_opendir_nfs(const char *path, bool include_hidden)
|
||||
{
|
||||
char server[256], export_path[512], rel[PATH_MAX_LENGTH];
|
||||
char server[256], rel[PATH_MAX_LENGTH];
|
||||
const char *export_path;
|
||||
struct nfs_params want;
|
||||
struct nfs_slot *slot;
|
||||
struct rnfs_ctx *c;
|
||||
struct rnfs_dir *d;
|
||||
nfs_dir_handle *dh;
|
||||
struct nfs_dirstate *ds;
|
||||
vfs_cfg_hdr_t *h;
|
||||
(void)include_hidden;
|
||||
if (!nfs_resolve(path, server, sizeof(server), export_path, sizeof(export_path), rel, sizeof(rel)))
|
||||
if (!nfs_resolve(path, server, sizeof(server), &export_path, rel, sizeof(rel), &want, &h))
|
||||
return NULL;
|
||||
if (!(c = nfs_take_resolved(server, export_path, rel, &slot)))
|
||||
if (!(c = nfs_take_resolved(server, export_path, rel, &want, &slot)))
|
||||
{
|
||||
nfs_cfg_leave(h);
|
||||
return NULL;
|
||||
}
|
||||
if (!(d = rnfs_opendir(c, rel)))
|
||||
{
|
||||
nfs_note_error(rnfs_get_error(c));
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
return NULL;
|
||||
}
|
||||
dh = (nfs_dir_handle*)calloc(1, sizeof(*dh));
|
||||
ds = (struct nfs_dirstate*)calloc(1, sizeof(*ds));
|
||||
if (!dh || !ds)
|
||||
{
|
||||
nfs_note_error("out of memory");
|
||||
rnfs_closedir(c, d);
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
free(dh);
|
||||
free(ds);
|
||||
return NULL;
|
||||
}
|
||||
ds->d = d;
|
||||
ds->d = d;
|
||||
ds->params = want;
|
||||
strlcpy(ds->server, server, sizeof(ds->server));
|
||||
strlcpy(ds->export_path, slot ? slot->export_path : export_path, sizeof(ds->export_path));
|
||||
if (!*ds->export_path)
|
||||
strlcpy(ds->export_path, export_path, sizeof(ds->export_path));
|
||||
strlcpy(ds->export_path, rnfs_get_export(c), sizeof(ds->export_path));
|
||||
nfs_give(c, slot);
|
||||
nfs_cfg_leave(h);
|
||||
dh->ctx = ds;
|
||||
dh->dir = d;
|
||||
return dh;
|
||||
@@ -772,7 +934,7 @@ struct nfs_dirent *retro_vfs_readdir_nfs(nfs_dir_handle *dh)
|
||||
struct nfs_slot *slot;
|
||||
struct rnfs_ctx *c;
|
||||
const struct rnfs_dirent *e;
|
||||
if (!ds || !(c = nfs_take(ds->server, ds->export_path, &slot)))
|
||||
if (!ds || !(c = nfs_take(ds->server, ds->export_path, &ds->params, &slot)))
|
||||
return NULL;
|
||||
e = rnfs_readdir(c, ds->d);
|
||||
nfs_give(c, slot);
|
||||
|
||||
@@ -68,6 +68,8 @@ typedef struct
|
||||
struct nfs_dirent ent;
|
||||
} nfs_dir_handle;
|
||||
|
||||
/* Hands the client new settings: they are copied, strings included,
|
||||
* so the caller may change or free @new_cfg once this returns. */
|
||||
bool nfs_init_cfg(const struct nfs_settings *new_cfg);
|
||||
|
||||
/* File operations */
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <vfs/vfs_implementation.h>
|
||||
#include <vfs/vfs_prefetch.h>
|
||||
#include "vfs_implementation_smb.h"
|
||||
#include "vfs_cfg_snapshot.h"
|
||||
|
||||
|
||||
#define SMB_PREFIX "smb://"
|
||||
@@ -170,15 +171,70 @@ struct smb_pool
|
||||
retro_atomic_int_t word; /* state and users */
|
||||
retro_atomic_int_t hash; /* of key, read before taking a use */
|
||||
retro_atomic_int_t auth_mode; /* resolved by the first connect */
|
||||
/* the settings copy last found to match key (0: none): a take on
|
||||
* that copy and the key's share skips building and comparing a key */
|
||||
retro_atomic_int_t gen;
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
static struct smb_pool smb_pools[SMB_MAX_POOLS];
|
||||
static retro_atomic_ptr_t smb_cfg_ptr; /* const struct smb_settings * */
|
||||
|
||||
static const struct smb_settings *smb_cfg_get(void)
|
||||
/* The settings as last handed over: a copy that never changes, held
|
||||
* between smb_cfg_enter() and smb_cfg_leave() (vfs_cfg_snapshot.h).
|
||||
* gen tells copies apart; 0 is never one. */
|
||||
struct smb_cfg_snap
|
||||
{
|
||||
return (const struct smb_settings*)retro_atomic_load_acquire_ptr(&smb_cfg_ptr);
|
||||
vfs_cfg_hdr_t hdr;
|
||||
struct smb_settings s;
|
||||
int gen;
|
||||
char server_address[256];
|
||||
char share[256];
|
||||
char username[256];
|
||||
char password[256];
|
||||
char workgroup[256];
|
||||
char subdir[PATH_MAX_LENGTH];
|
||||
char realm[128];
|
||||
char kdc[256];
|
||||
};
|
||||
static vfs_cfg_slot_t smb_cfg_slot;
|
||||
static retro_atomic_int_t smb_cfg_gen;
|
||||
/* Last error for smb_get_last_error(), kept as nfs_get_last_error()
|
||||
* keeps it: each writer fills a ring slot of its own, claimed by an
|
||||
* atomic counter, then publishes its index for the reader. */
|
||||
#define SMB_ERR_SLOTS 8
|
||||
static char smb_err_ring[SMB_ERR_SLOTS][256];
|
||||
static retro_atomic_int_t smb_err_seq;
|
||||
static retro_atomic_int_t smb_err_latest;
|
||||
|
||||
/* The settings copy (NULL: none), valid until smb_cfg_leave(*@h). */
|
||||
static const struct smb_cfg_snap *smb_cfg_enter(vfs_cfg_hdr_t **h)
|
||||
{
|
||||
*h = vfs_cfg_enter(&smb_cfg_slot);
|
||||
return (const struct smb_cfg_snap*)*h;
|
||||
}
|
||||
|
||||
static void smb_cfg_leave(vfs_cfg_hdr_t *h)
|
||||
{
|
||||
vfs_cfg_leave(&smb_cfg_slot, h);
|
||||
}
|
||||
|
||||
/* "@what: @why", or @what alone when the client gave no reason. */
|
||||
static void smb_note_error(const char *what, const char *why)
|
||||
{
|
||||
int slot = retro_atomic_fetch_add_int(&smb_err_seq, 1);
|
||||
slot &= (SMB_ERR_SLOTS - 1);
|
||||
strlcpy(smb_err_ring[slot], what, sizeof(smb_err_ring[slot]));
|
||||
if (why && *why)
|
||||
{
|
||||
strlcat(smb_err_ring[slot], ": ", sizeof(smb_err_ring[slot]));
|
||||
strlcat(smb_err_ring[slot], why, sizeof(smb_err_ring[slot]));
|
||||
}
|
||||
retro_atomic_store_release_int(&smb_err_latest, slot);
|
||||
}
|
||||
|
||||
const char *smb_get_last_error(void)
|
||||
{
|
||||
int slot = retro_atomic_load_acquire_int(&smb_err_latest);
|
||||
return smb_err_ring[slot & (SMB_ERR_SLOTS - 1)];
|
||||
}
|
||||
|
||||
/* Extracts the first path component after the server from an smb:// URL.
|
||||
@@ -222,20 +278,23 @@ static bool smb_url_share(const char *path, char *s, size_t len)
|
||||
* otherwise the share is the leading component of the URL itself. */
|
||||
static void smb_effective_share(const char *path, char *s, size_t len)
|
||||
{
|
||||
const struct smb_settings *smb_cfg = smb_cfg_get();
|
||||
if (smb_cfg && smb_cfg->share && *smb_cfg->share)
|
||||
{
|
||||
strlcpy(s, smb_cfg->share, len);
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
bool set = snap && *snap->share;
|
||||
if (set)
|
||||
strlcpy(s, snap->share, len);
|
||||
smb_cfg_leave(h);
|
||||
if (set)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!smb_url_share(path, s, len))
|
||||
s[0] = '\0';
|
||||
}
|
||||
|
||||
static void smb_conn_key_fill(struct smb_conn_key *key, const char *share)
|
||||
/* @key for @share from the settings copy @cfg (NULL: none). */
|
||||
static void smb_conn_key_fill_with(struct smb_conn_key *key,
|
||||
const struct smb_settings *cfg, const char *share)
|
||||
{
|
||||
const struct smb_settings *cfg = smb_cfg_get();
|
||||
memset(key, 0, sizeof(*key));
|
||||
if (!cfg)
|
||||
return;
|
||||
@@ -259,13 +318,24 @@ static void smb_conn_key_fill(struct smb_conn_key *key, const char *share)
|
||||
key->readahead = cfg->readahead;
|
||||
}
|
||||
|
||||
static void smb_conn_key_fill(struct smb_conn_key *key, const char *share)
|
||||
{
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
smb_conn_key_fill_with(key, snap ? &snap->s : NULL, share);
|
||||
smb_cfg_leave(h);
|
||||
}
|
||||
|
||||
/* A context set up from @key with @auth, connected to the key's share. */
|
||||
static struct smb2_context *smb_connect_with(const struct smb_conn_key *key, int auth)
|
||||
{
|
||||
struct smb2_context *ctx = smb_ctx_new();
|
||||
const char *username = *key->username ? key->username : NULL;
|
||||
if (!ctx)
|
||||
{
|
||||
smb_note_error("out of memory", NULL);
|
||||
return NULL;
|
||||
}
|
||||
if (username)
|
||||
smb2_set_user(ctx, username);
|
||||
if (*key->password)
|
||||
@@ -282,6 +352,11 @@ static struct smb2_context *smb_connect_with(const struct smb_conn_key *key, int
|
||||
#endif
|
||||
if (smb2_connect_share(ctx, key->server_address, key->share, username) < 0)
|
||||
{
|
||||
char what[192];
|
||||
snprintf(what, sizeof(what), "connect to %.64s, share %.64s (%s)",
|
||||
key->server_address, key->share,
|
||||
auth == RETRO_SMB2_SEC_KRB5 ? "Kerberos" : "NTLMSSP");
|
||||
smb_note_error(what, smb2_get_error(ctx));
|
||||
smb_ctx_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
@@ -422,6 +497,7 @@ static struct smb_pool *smb_pool_for(const struct smb_conn_key *key)
|
||||
SMB_POOL_WORD(SMB_POOL_FREE), SMB_POOL_WORD(SMB_POOL_BUILDING)))
|
||||
continue;
|
||||
p->key = *key;
|
||||
retro_atomic_store_release_int(&p->gen, 0);
|
||||
p->count = key->num_contexts > SMB_MAX_SLOTS
|
||||
? SMB_MAX_SLOTS : key->num_contexts;
|
||||
for (j = 0; j < SMB_MAX_SLOTS; j++)
|
||||
@@ -436,6 +512,28 @@ static struct smb_pool *smb_pool_for(const struct smb_conn_key *key)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* A use of the live pool last matched to settings copy @gen and built
|
||||
* for @share, or NULL: no key built, no key compared. */
|
||||
static struct smb_pool *smb_pool_find_gen(int gen, const char *share)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < SMB_MAX_POOLS; i++)
|
||||
{
|
||||
struct smb_pool *p = &smb_pools[i];
|
||||
if ( retro_atomic_load_acquire_int(&p->gen) == gen
|
||||
&& SMB_POOL_STATE(retro_atomic_load_acquire_int(&p->word)) == SMB_POOL_LIVE
|
||||
&& smb_pool_get(p))
|
||||
{
|
||||
/* the use holds the key still; the copy may have moved on */
|
||||
if ( retro_atomic_load_acquire_int(&p->gen) == gen
|
||||
&& string_is_equal(p->key.share, share ? share : ""))
|
||||
return p;
|
||||
smb_pool_put(p);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The auth mode a live pool on @server settled for @user, else @mode. */
|
||||
static int smb_settled_mode(const char *server, const char *user, int mode)
|
||||
{
|
||||
@@ -459,9 +557,34 @@ static int smb_settled_mode(const char *server, const char *user, int mode)
|
||||
return mode;
|
||||
}
|
||||
|
||||
#define SMB_SNAP_STR(field) \
|
||||
if (new_cfg->field) \
|
||||
strlcpy(snap->field, new_cfg->field, sizeof(snap->field)); \
|
||||
snap->s.field = snap->field
|
||||
|
||||
/* @new_cfg is copied, strings included, so the caller may change or
|
||||
* free it at once. */
|
||||
bool smb_init_cfg(const struct smb_settings *new_cfg)
|
||||
{
|
||||
retro_atomic_store_release_ptr(&smb_cfg_ptr, (void*)new_cfg);
|
||||
struct smb_cfg_snap *snap = NULL;
|
||||
if (new_cfg)
|
||||
{
|
||||
if (!(snap = (struct smb_cfg_snap*)calloc(1, sizeof(*snap))))
|
||||
return false;
|
||||
snap->s = *new_cfg;
|
||||
SMB_SNAP_STR(server_address);
|
||||
SMB_SNAP_STR(share);
|
||||
SMB_SNAP_STR(username);
|
||||
SMB_SNAP_STR(password);
|
||||
SMB_SNAP_STR(workgroup);
|
||||
SMB_SNAP_STR(subdir);
|
||||
SMB_SNAP_STR(realm);
|
||||
SMB_SNAP_STR(kdc);
|
||||
snap->gen = retro_atomic_fetch_add_int(&smb_cfg_gen, 1) + 1;
|
||||
if (!snap->gen) /* past a wrap: 0 means none */
|
||||
snap->gen = retro_atomic_fetch_add_int(&smb_cfg_gen, 1) + 1;
|
||||
}
|
||||
vfs_cfg_publish(&smb_cfg_slot, snap ? &snap->hdr : NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -470,7 +593,11 @@ static struct smb2_context *smb_connect_private(const struct smb_conn_key *key)
|
||||
{
|
||||
retro_atomic_int_t mode;
|
||||
if (!*key->server_address || !network_init())
|
||||
{
|
||||
smb_note_error(*key->server_address ? "network init failed"
|
||||
: "no SMB server set", NULL);
|
||||
return NULL;
|
||||
}
|
||||
retro_atomic_int_init(&mode, smb_settled_mode(key->server_address,
|
||||
key->username, (int)key->auth_mode));
|
||||
return smb_create_context(key, &mode);
|
||||
@@ -482,13 +609,27 @@ static struct smb2_context *smb_take(const char *want_share, struct smb_slot **s
|
||||
{
|
||||
struct smb_conn_key key;
|
||||
struct smb_pool *pool;
|
||||
struct smb2_context *ctx;
|
||||
unsigned i;
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
|
||||
*slot = NULL;
|
||||
smb_conn_key_fill(&key, want_share);
|
||||
if (!*key.server_address)
|
||||
if (!snap || !*snap->server_address)
|
||||
{
|
||||
smb_cfg_leave(h);
|
||||
smb_note_error("no SMB server set", NULL);
|
||||
return NULL;
|
||||
if ((pool = smb_pool_for(&key)))
|
||||
}
|
||||
/* the pool this copy of the settings already found: nothing to
|
||||
* build or compare; else the key, and the pool it names */
|
||||
if (!(pool = smb_pool_find_gen(snap->gen, want_share)))
|
||||
{
|
||||
smb_conn_key_fill_with(&key, &snap->s, want_share);
|
||||
if ((pool = smb_pool_for(&key)))
|
||||
retro_atomic_store_release_int(&pool->gen, snap->gen);
|
||||
}
|
||||
if (pool)
|
||||
{
|
||||
for (i = 0; i < pool->count; i++)
|
||||
{
|
||||
@@ -504,16 +645,21 @@ static struct smb2_context *smb_take(const char *want_share, struct smb_slot **s
|
||||
if (sl->ctx)
|
||||
{
|
||||
*slot = sl; /* the slot keeps the use */
|
||||
smb_cfg_leave(h);
|
||||
return sl->ctx;
|
||||
}
|
||||
retro_atomic_store_release_int(&sl->busy, 0);
|
||||
smb_pool_put(pool);
|
||||
smb_cfg_leave(h);
|
||||
return NULL; /* the server is not answering */
|
||||
}
|
||||
smb_pool_put(pool);
|
||||
}
|
||||
/* every slot busy, or no pool to be had: a private connection */
|
||||
return smb_connect_private(&key);
|
||||
smb_conn_key_fill_with(&key, &snap->s, want_share);
|
||||
ctx = smb_connect_private(&key);
|
||||
smb_cfg_leave(h);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void smb_give(struct smb2_context *ctx, struct smb_slot *slot)
|
||||
@@ -666,9 +812,9 @@ static void smb_free_share_list(char **shares, unsigned count)
|
||||
/* Collects the disk shares the server exports through the built-in
|
||||
* client's NetrShareEnum. Hidden and administrative shares are left
|
||||
* out, as are printer, device and IPC entries. */
|
||||
static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
static bool smb_enum_shares_with(const struct smb_settings *smb_cfg,
|
||||
char ***out, unsigned *out_count)
|
||||
{
|
||||
const struct smb_settings *smb_cfg = smb_cfg_get();
|
||||
struct rsmb_ctx *ctx;
|
||||
struct rsmb_share *list;
|
||||
char **shares;
|
||||
@@ -679,11 +825,15 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
*out_count = 0;
|
||||
|
||||
if (!smb_cfg || !smb_cfg->server_address || !*smb_cfg->server_address)
|
||||
{
|
||||
smb_note_error("no SMB server set", NULL);
|
||||
return false;
|
||||
if (!network_init())
|
||||
return false;
|
||||
if (!(ctx = rsmb_new()))
|
||||
}
|
||||
if (!network_init() || !(ctx = rsmb_new()))
|
||||
{
|
||||
smb_note_error("share list: setup failed", NULL);
|
||||
return false;
|
||||
}
|
||||
rsmb_set_credentials(ctx, smb_cfg->username, smb_cfg->password, smb_cfg->workgroup);
|
||||
rsmb_set_timeout(ctx, smb_cfg->timeout ? smb_cfg->timeout : RETRO_SMB2_DEFAULT_CLIENT_TIMEOUT);
|
||||
/* a ticket when a realm is configured and the mode allows it; the
|
||||
@@ -692,6 +842,7 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
rsmb_set_kerberos(ctx, smb_cfg->realm, smb_cfg->kdc, 0);
|
||||
if (rsmb_connect(ctx, smb_cfg->server_address, "IPC$") != 0)
|
||||
{
|
||||
smb_note_error("share list: connect to IPC$", rsmb_get_error(ctx));
|
||||
rsmb_free(ctx);
|
||||
return false;
|
||||
}
|
||||
@@ -701,6 +852,8 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
return false;
|
||||
}
|
||||
n = rsmb_enum_shares(ctx, list, 256);
|
||||
if (n <= 0)
|
||||
smb_note_error("share list", rsmb_get_error(ctx));
|
||||
rsmb_free(ctx);
|
||||
if (n <= 0 || !(shares = (char**)calloc((size_t)n, sizeof(char*))))
|
||||
{
|
||||
@@ -725,6 +878,7 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
free(list);
|
||||
if (count == 0)
|
||||
{
|
||||
smb_note_error("share list: no disk shares", NULL);
|
||||
smb_free_share_list(shares, count);
|
||||
return false;
|
||||
}
|
||||
@@ -735,9 +889,9 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
#else
|
||||
/* Collects the disk shares the server exports. Hidden and administrative
|
||||
* shares are left out, as are printer, device and IPC entries. */
|
||||
static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
static bool smb_enum_shares_with(const struct smb_settings *smb_cfg,
|
||||
char ***out, unsigned *out_count)
|
||||
{
|
||||
const struct smb_settings *smb_cfg = smb_cfg_get();
|
||||
struct smb_enum_state state;
|
||||
struct smb2_context *ctx;
|
||||
const smb_share_info1_t *info1;
|
||||
@@ -751,13 +905,16 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
*out_count = 0;
|
||||
|
||||
if (!smb_cfg || !smb_cfg->server_address || !*smb_cfg->server_address)
|
||||
{
|
||||
smb_note_error("no SMB server set", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!network_init())
|
||||
return false;
|
||||
|
||||
if (!(ctx = smb_ctx_new()))
|
||||
if (!network_init() || !(ctx = smb_ctx_new()))
|
||||
{
|
||||
smb_note_error("share list: setup failed", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(timeout = smb_cfg->timeout))
|
||||
timeout = RETRO_SMB2_DEFAULT_CLIENT_TIMEOUT;
|
||||
@@ -784,6 +941,7 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
if (smb2_connect_share(ctx, smb_cfg->server_address, "IPC$",
|
||||
smb_cfg->username) < 0)
|
||||
{
|
||||
smb_note_error("share list: connect to IPC$", smb2_get_error(ctx));
|
||||
smb_ctx_free(ctx);
|
||||
return false;
|
||||
}
|
||||
@@ -793,6 +951,7 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
if (smb2_share_enum_async(ctx, SMB_SHARE_LEVEL_1,
|
||||
smb_share_enum_cb, &state) != 0)
|
||||
{
|
||||
smb_note_error("share list", smb2_get_error(ctx));
|
||||
smb2_disconnect_share(ctx);
|
||||
smb_ctx_free(ctx);
|
||||
return false;
|
||||
@@ -802,6 +961,8 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
|| state.status != 0
|
||||
|| !state.rep)
|
||||
{
|
||||
smb_note_error(state.finished ? "share list" : "share list: no reply",
|
||||
smb2_get_error(ctx));
|
||||
if (state.rep)
|
||||
smb2_free_data(ctx, state.rep);
|
||||
smb2_disconnect_share(ctx);
|
||||
@@ -858,6 +1019,7 @@ static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
smb_note_error("share list: no disk shares", NULL);
|
||||
smb_free_share_list(shares, count);
|
||||
return false;
|
||||
}
|
||||
@@ -879,10 +1041,10 @@ void smb_shutdown(void)
|
||||
}
|
||||
|
||||
/* Build full SMB path from settings */
|
||||
static bool smb_build_path(char *dest, size_t dest_size, const char *relative_path)
|
||||
static bool smb_build_path_with(const struct smb_settings *smb_cfg,
|
||||
char *dest, size_t dest_size, const char *relative_path)
|
||||
{
|
||||
char temp_path[PATH_MAX_LENGTH];
|
||||
const struct smb_settings *smb_cfg = smb_cfg_get();
|
||||
const char *p;
|
||||
|
||||
/* If already has smb:// prefix, extract just the path component */
|
||||
@@ -910,7 +1072,7 @@ static bool smb_build_path(char *dest, size_t dest_size, const char *relative_pa
|
||||
size_t sz = sizeof(temp_path);
|
||||
|
||||
/* Add base folder if specified */
|
||||
if (smb_cfg->subdir && *smb_cfg->subdir)
|
||||
if (smb_cfg && smb_cfg->subdir && *smb_cfg->subdir)
|
||||
{
|
||||
_len = strlcpy(temp_path, smb_cfg->subdir, sz);
|
||||
if (_len > 0 && _len < sz && temp_path[_len - 1] != '/')
|
||||
@@ -1014,9 +1176,11 @@ static void smb_prefetch_start(libretro_vfs_implementation_file *stream,
|
||||
const char *share, const char *path)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
const struct smb_settings *cfg = smb_cfg_get();
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
struct smb_prefetch *sp;
|
||||
size_t window = cfg ? (size_t)cfg->readahead * 1024 : 0;
|
||||
size_t window = snap ? (size_t)snap->s.readahead * 1024 : 0;
|
||||
smb_cfg_leave(h);
|
||||
if (!window)
|
||||
return;
|
||||
if (!(sp = (struct smb_prefetch*)calloc(1, sizeof(*sp))))
|
||||
@@ -1144,6 +1308,26 @@ static bool smb_stream_mend(libretro_vfs_implementation_file *stream)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The disk shares, on the settings as they are now. */
|
||||
static bool smb_enum_shares(char ***out, unsigned *out_count)
|
||||
{
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
bool ok = smb_enum_shares_with(snap ? &snap->s : NULL, out, out_count);
|
||||
smb_cfg_leave(h);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* The path under the share, on the settings as they are now. */
|
||||
static bool smb_build_path(char *dest, size_t dest_size, const char *relative_path)
|
||||
{
|
||||
vfs_cfg_hdr_t *h;
|
||||
const struct smb_cfg_snap *snap = smb_cfg_enter(&h);
|
||||
bool ok = smb_build_path_with(snap ? &snap->s : NULL, dest, dest_size, relative_path);
|
||||
smb_cfg_leave(h);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool retro_vfs_file_open_smb(libretro_vfs_implementation_file *stream,
|
||||
const char *path, unsigned mode, unsigned hints)
|
||||
{
|
||||
@@ -1537,6 +1721,7 @@ smb_dir_handle* retro_vfs_opendir_smb(const char *path, bool include_hidden)
|
||||
|
||||
if (!(handle = (smb_dir_handle*)malloc(sizeof(smb_dir_handle))))
|
||||
{
|
||||
smb_note_error("out of memory", NULL);
|
||||
smb_free_share_list(shares, count);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1551,7 +1736,10 @@ smb_dir_handle* retro_vfs_opendir_smb(const char *path, bool include_hidden)
|
||||
}
|
||||
|
||||
if (!smb_build_path(full_path, sizeof(full_path), path))
|
||||
{
|
||||
smb_note_error("opendir: bad path", NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Root-of-share: bare "/" should become "" for libsmb2 opendir */
|
||||
if (full_path[0] == '/' && full_path[1] == '\0')
|
||||
@@ -1566,10 +1754,12 @@ smb_dir_handle* retro_vfs_opendir_smb(const char *path, bool include_hidden)
|
||||
dir = smb2_opendir(smb_context, full_path);
|
||||
if (!dir)
|
||||
{
|
||||
smb_note_error("opendir", smb2_get_error(smb_context));
|
||||
if ((fresh = smb_heal(smb_context, slot, share, &dead)))
|
||||
{
|
||||
smb_context = fresh;
|
||||
dir = smb2_opendir(smb_context, full_path);
|
||||
if (!(dir = smb2_opendir(smb_context, full_path)))
|
||||
smb_note_error("opendir", smb2_get_error(smb_context));
|
||||
}
|
||||
if (!dir)
|
||||
{
|
||||
@@ -1580,6 +1770,7 @@ smb_dir_handle* retro_vfs_opendir_smb(const char *path, bool include_hidden)
|
||||
handle = (smb_dir_handle*)malloc(sizeof(smb_dir_handle));
|
||||
if (!handle)
|
||||
{
|
||||
smb_note_error("out of memory", NULL);
|
||||
smb2_closedir(smb_context, dir);
|
||||
smb_give(smb_context, slot);
|
||||
return NULL;
|
||||
|
||||
@@ -58,6 +58,8 @@ typedef struct {
|
||||
* listings on different threads never share it */
|
||||
} smb_dir_handle;
|
||||
|
||||
/* Hands the client new settings: they are copied, strings included,
|
||||
* so the caller may change or free @new_cfg once this returns. */
|
||||
bool smb_init_cfg(const struct smb_settings *new_cfg);
|
||||
|
||||
/* File operations */
|
||||
@@ -84,6 +86,8 @@ int retro_vfs_stat_smb(const char *path, int64_t *size);
|
||||
|
||||
/* Errors */
|
||||
int retro_vfs_file_error_smb(libretro_vfs_implementation_file *stream);
|
||||
/* Why the last connect, share listing or opendir failed; "" if none has. */
|
||||
const char *smb_get_last_error(void);
|
||||
|
||||
/* Context management */
|
||||
void smb_shutdown(void);
|
||||
|
||||
@@ -1143,6 +1143,7 @@ struct libretro_vfs_implementation_dir
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
smb_dir_handle* smb_handle;
|
||||
char smb_path[PATH_MAX_LENGTH];
|
||||
bool smb_is_dir;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1171,7 +1172,7 @@ libretro_vfs_implementation_dir* retro_vfs_opendir_impl(
|
||||
name[3]==':' && name[4]=='/' && name[5]=='/' && name[6] != '\0')
|
||||
{
|
||||
smb_dir_handle *dh = retro_vfs_opendir_smb(name, include_hidden);
|
||||
if (!dh || !dh->dir)
|
||||
if (!dh)
|
||||
{
|
||||
free(rdir->orig_path);
|
||||
free(rdir);
|
||||
@@ -1179,6 +1180,7 @@ libretro_vfs_implementation_dir* retro_vfs_opendir_impl(
|
||||
}
|
||||
rdir->smb_handle = dh;
|
||||
rdir->smb_path[0] = '\0';
|
||||
rdir->smb_is_dir = false;
|
||||
return rdir;
|
||||
}
|
||||
#endif
|
||||
@@ -1213,13 +1215,14 @@ libretro_vfs_implementation_dir* retro_vfs_opendir_impl(
|
||||
bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir* rdir)
|
||||
{
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (rdir && rdir->smb_handle && rdir->smb_handle->dir != 0)
|
||||
if (rdir && rdir->smb_handle)
|
||||
{
|
||||
struct smbc_dirent *de = retro_vfs_readdir_smb(rdir->smb_handle);
|
||||
if (!de)
|
||||
return false;
|
||||
|
||||
strlcpy(rdir->smb_path, de->name, sizeof(rdir->smb_path));
|
||||
rdir->smb_is_dir = (de->type == RETRO_SMB_DIRENT_DIR);
|
||||
return true;
|
||||
}
|
||||
/* If we opened an SMB path but failed, do not fall through to native readdir */
|
||||
@@ -1239,7 +1242,7 @@ bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir* rdir)
|
||||
const char* retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir* rdir)
|
||||
{
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (rdir && rdir->smb_handle && rdir->smb_handle->dir != 0)
|
||||
if (rdir && rdir->smb_handle)
|
||||
return rdir->smb_path;
|
||||
#endif
|
||||
char* name = utf16_to_utf8_string_alloc(rdir->entry.cFileName);
|
||||
@@ -1253,20 +1256,8 @@ const char* retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir* rdir
|
||||
bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir* rdir)
|
||||
{
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (rdir && rdir->smb_handle && rdir->smb_handle->dir != 0)
|
||||
{
|
||||
char full[PATH_MAX_LENGTH];
|
||||
const char *name = retro_vfs_dirent_get_name_impl(rdir);
|
||||
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
fill_pathname_join_special(full, rdir->orig_path, name, sizeof(full));
|
||||
int64_t sz = 0;
|
||||
int st = retro_vfs_stat_smb(full, &sz);
|
||||
|
||||
return (st & RETRO_VFS_STAT_IS_DIRECTORY) != 0;
|
||||
}
|
||||
if (rdir && rdir->smb_handle)
|
||||
return rdir->smb_is_dir;
|
||||
#endif
|
||||
const WIN32_FIND_DATA* entry = (const WIN32_FIND_DATA*)&rdir->entry;
|
||||
return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
||||
@@ -1280,7 +1271,7 @@ int retro_vfs_dirent_stat_impl(libretro_vfs_implementation_dir* rdir,
|
||||
if (!rdir)
|
||||
return 0;
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (rdir->smb_handle && rdir->smb_handle->dir != 0)
|
||||
if (rdir->smb_handle)
|
||||
{
|
||||
char full[PATH_MAX_LENGTH];
|
||||
const char *name = retro_vfs_dirent_get_name_impl(rdir);
|
||||
@@ -1310,7 +1301,7 @@ int retro_vfs_closedir_impl(libretro_vfs_implementation_dir* rdir)
|
||||
return -1;
|
||||
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (rdir->smb_handle && rdir->smb_handle->dir != 0)
|
||||
if (rdir->smb_handle)
|
||||
{
|
||||
retro_vfs_closedir_smb(rdir->smb_handle);
|
||||
rdir->smb_handle = NULL;
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
#include <media/media_detect_cd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NFSCLIENT
|
||||
#include "../libretro-common/vfs/vfs_implementation_nfs.h"
|
||||
#endif
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
#include "../libretro-common/vfs/vfs_implementation_smb.h"
|
||||
#endif
|
||||
|
||||
#include "../audio/audio_driver.h"
|
||||
#include "../midi_driver.h"
|
||||
#include "../record/record_driver.h"
|
||||
@@ -369,6 +376,44 @@ bool menu_displaylist_nfs_title_path(char *s, size_t len, const char *path)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_NFSCLIENT) || defined(HAVE_SMBCLIENT)
|
||||
/* Why a network directory could not be listed, from its client,
|
||||
* beside the settings it ran with. */
|
||||
static void filebrowser_log_net_failure(const char *path)
|
||||
{
|
||||
settings_t *settings = config_get_ptr();
|
||||
const char *why = NULL;
|
||||
#ifdef HAVE_NFSCLIENT
|
||||
if (string_starts_with_size(path, "nfs://", STRLEN_CONST("nfs://")))
|
||||
{
|
||||
const char *export_p = settings->arrays.nfs_export;
|
||||
why = nfs_get_last_error();
|
||||
RARCH_ERR("[NFS] Cannot list \"%s\" (version %u, port %u, export %s%s%s): %s.\n",
|
||||
path,
|
||||
settings->uints.nfs_version ? settings->uints.nfs_version : 3,
|
||||
settings->uints.nfs_port,
|
||||
*export_p ? "\"" : "", *export_p ? export_p : "none",
|
||||
*export_p ? "\"" : "",
|
||||
*why ? why : "no reason given");
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
if (string_starts_with_size(path, "smb://", STRLEN_CONST("smb://")))
|
||||
{
|
||||
unsigned auth = settings->uints.smb_client_auth_mode;
|
||||
why = smb_get_last_error();
|
||||
RARCH_ERR("[SMB] Cannot list \"%s\" (server \"%s\", share \"%s\", auth %s): %s.\n",
|
||||
path,
|
||||
settings->arrays.smb_client_server_address,
|
||||
settings->arrays.smb_client_share,
|
||||
auth == RETRO_SMB2_SEC_KRB5 ? "Kerberos"
|
||||
: auth == RETRO_SMB2_SEC_NTLMSSP ? "NTLMSSP" : "auto",
|
||||
*why ? why : "no reason given");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void filebrowser_clear_type(void)
|
||||
{
|
||||
struct menu_displaylist_state *p_displist = &menu_displist_st;
|
||||
@@ -704,6 +749,11 @@ static int filebrowser_parse(
|
||||
? msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNABLE_TO_READ_COMPRESSED_FILE)
|
||||
: msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DIRECTORY_NOT_FOUND);
|
||||
|
||||
#if defined(HAVE_NFSCLIENT) || defined(HAVE_SMBCLIENT)
|
||||
if (!path_is_compressed && path && *path)
|
||||
filebrowser_log_net_failure(path);
|
||||
#endif
|
||||
|
||||
menu_entries_append(info_list, str, "",
|
||||
MENU_ENUM_LABEL_VALUE_DIRECTORY_NOT_FOUND, 0, 0, 0, NULL);
|
||||
goto end;
|
||||
|
||||
+38
-7
@@ -10742,6 +10742,37 @@ static void general_write_handler(rarch_setting_t *setting)
|
||||
ssl_socket_set_verify_mode(*setting->value.target.unsigned_integer);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_SERVER:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_SHARE:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_SUBDIR:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_USERNAME:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_PASSWORD:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_WORKGROUP:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_REALM:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_KDC:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_AUTH_MODE:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_NUM_CONTEXTS:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_TIMEOUT:
|
||||
case MENU_ENUM_LABEL_SMB_CLIENT_READAHEAD:
|
||||
/* the client keeps copies of the numbers */
|
||||
retroarch_smb_init();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_NFSCLIENT
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_SERVER:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_EXPORT:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_TIMEOUT:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_NUM_CONTEXTS:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_PORT:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_MOUNT_PORT:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_VERSION:
|
||||
case MENU_ENUM_LABEL_NFS_CLIENT_READAHEAD:
|
||||
/* the client keeps copies of the numbers, and connections
|
||||
* made with the old settings */
|
||||
retroarch_nfs_init();
|
||||
break;
|
||||
#endif
|
||||
#ifndef HAVE_LAKKA
|
||||
case MENU_ENUM_LABEL_GAMEMODE_ENABLE:
|
||||
if (frontend_driver_has_gamemode())
|
||||
@@ -19802,7 +19833,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
@@ -19816,7 +19847,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
@@ -19830,7 +19861,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
@@ -19844,7 +19875,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
@@ -19858,7 +19889,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
SETTINGS_ACTION_SET(repr, &(*list)[list_info->index - 1], setting_get_string_representation_smb_password)
|
||||
@@ -19873,7 +19904,7 @@ static void settings_build_smbclient(
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
NULL,
|
||||
general_write_handler,
|
||||
NULL);
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
@@ -19889,7 +19920,7 @@ static void settings_build_smbclient(
|
||||
CONFIG_STRING(list, list_info, settings->arrays.field, \
|
||||
sizeof(settings->arrays.field), MENU_ENUM_LABEL_##T, \
|
||||
MENU_ENUM_LABEL_VALUE_##T, "", &group_info, &subgroup_info, \
|
||||
parent_group, NULL, NULL); \
|
||||
parent_group, general_write_handler, NULL); \
|
||||
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT)
|
||||
#define NFS_UINT(field, T, def, mn, mx) \
|
||||
CONFIG_UINT(list, list_info, &settings->uints.field, \
|
||||
|
||||
@@ -215,6 +215,14 @@ void retroarch_favorites_init(void);
|
||||
|
||||
void retroarch_favorites_deinit(void);
|
||||
|
||||
/* Hand the network clients the current settings. */
|
||||
#ifdef HAVE_SMBCLIENT
|
||||
void retroarch_smb_init(void);
|
||||
#endif
|
||||
#ifdef HAVE_NFSCLIENT
|
||||
void retroarch_nfs_init(void);
|
||||
#endif
|
||||
|
||||
/* Audio */
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ else
|
||||
# file mapping as the shipping build has it (Win32 maps without it)
|
||||
MMAP_FLAGS = -DHAVE_MMAP
|
||||
endif
|
||||
all: nfs_test nfs_sparse_test nfs_idle_test nfs_reply_bounds_test vfs_test vfs_threads_test
|
||||
all: nfs_test nfs_sparse_test nfs_idle_test nfs_reply_bounds_test vfs_test vfs_threads_test nfs_error_test nfs_cfg_race_test
|
||||
|
||||
nfs_test: $(SRC)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(SRC) $(LDLIBS) -o $@
|
||||
@@ -32,9 +32,16 @@ VFS_SRC = vfs_test.c $(COMMON)/vfs/vfs_implementation_nfs.c $(COMMON)/vfs/vfs_im
|
||||
$(filter-out nfs_test.c,$(SRC))
|
||||
vfs_test: $(VFS_SRC)
|
||||
$(CC) $(CPPFLAGS) -DHAVE_NFSCLIENT -I$(ROOT) $(CFLAGS) $(VFS_SRC) $(LDLIBS) -o $@
|
||||
ERR_SRC = nfs_error_test.c $(filter-out vfs_test.c,$(VFS_SRC)) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c
|
||||
nfs_error_test: $(ERR_SRC)
|
||||
$(CC) $(CPPFLAGS) -DHAVE_NFSCLIENT -DHAVE_THREADS -I$(ROOT) $(CFLAGS) $(ERR_SRC) $(LDLIBS) -lpthread -o $@
|
||||
# settings handed over while threads resolve; no server
|
||||
RACE_SRC = nfs_cfg_race_test.c $(filter-out vfs_test.c,$(VFS_SRC)) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c
|
||||
nfs_cfg_race_test: $(RACE_SRC) $(COMMON)/vfs/vfs_cfg_snapshot.h
|
||||
$(CC) $(CPPFLAGS) -DHAVE_NFSCLIENT -DHAVE_THREADS -I$(ROOT) $(CFLAGS) $(RACE_SRC) $(LDLIBS) -lpthread -o $@
|
||||
THR_SRC = vfs_threads_test.c $(filter-out vfs_test.c,$(VFS_SRC)) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c
|
||||
vfs_threads_test: $(THR_SRC)
|
||||
$(CC) $(CPPFLAGS) -DHAVE_NFSCLIENT -DHAVE_THREADS $(MMAP_FLAGS) -I$(ROOT) $(CFLAGS) $(THR_SRC) $(LDLIBS) -lpthread -o $@
|
||||
clean:
|
||||
rm -f nfs_test nfs_test.exe nfs_sparse_test nfs_sparse_test.exe nfs_idle_test nfs_idle_test.exe nfs_reply_bounds_test nfs_reply_bounds_test.exe vfs_test vfs_test.exe vfs_threads_test vfs_threads_test.exe
|
||||
rm -f nfs_test nfs_test.exe nfs_sparse_test nfs_sparse_test.exe nfs_idle_test nfs_idle_test.exe nfs_reply_bounds_test nfs_reply_bounds_test.exe vfs_test vfs_test.exe vfs_threads_test vfs_threads_test.exe nfs_error_test nfs_error_test.exe nfs_cfg_race_test nfs_cfg_race_test.exe
|
||||
.PHONY: all clean
|
||||
@@ -8,12 +8,14 @@
|
||||
# on it), hence the fake. Its encoding is checked apart from the client
|
||||
# when tshark is installed: Wireshark's own NFS dissector decodes the
|
||||
# replies and their data and holes must add up to the file.
|
||||
# The fake also serves nfs_error_test: a failed listing must say why,
|
||||
# and an open file must read on after a settings change.
|
||||
# Runs as a Windows binary under Wine too (CC=..-mingw.. RUNNER=wine
|
||||
# EXE=.exe); needs python3.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
command -v python3 >/dev/null 2>&1 || { echo "skip: no python3"; exit 0; }
|
||||
make -s nfs_sparse_test
|
||||
make -s nfs_sparse_test nfs_error_test
|
||||
RUN=${RUNNER:-}
|
||||
EXE=${EXE:-}
|
||||
PORT=20460
|
||||
@@ -74,4 +76,10 @@ if [ -n "$CAP" ]; then
|
||||
else
|
||||
echo "skip: the independent decode needs tshark and root"
|
||||
fi
|
||||
# after the capture, whose sums its reads would add to
|
||||
if $RUN ./nfs_error_test$EXE $PORT "$local_path" > $D/err 2>&1; then
|
||||
tr -d "\r" < $D/err
|
||||
else
|
||||
echo "FAIL: nfs_error_test: $(cat $D/err)"; exit 1
|
||||
fi
|
||||
echo "[pass] nfs_retro local read_plus"
|
||||
@@ -8,8 +8,10 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
# reply parsing against short and hostile replies: needs no server
|
||||
make -s nfs_reply_bounds_test
|
||||
make -s nfs_reply_bounds_test nfs_cfg_race_test
|
||||
${RUNNER:-} ./nfs_reply_bounds_test${EXE:-}
|
||||
# settings handed over while threads resolve: needs no server
|
||||
${RUNNER:-} ./nfs_cfg_race_test${EXE:-}
|
||||
UNFSD=$(command -v unfsd || true)
|
||||
if [ -z "$UNFSD" ]; then
|
||||
if command -v git >/dev/null 2>&1 && command -v flex >/dev/null 2>&1 && command -v bison >/dev/null 2>&1; then
|
||||
@@ -36,7 +38,7 @@ $UNFSD -p -n 20049 -m 20048 -e $D/exports -d > $D/log 2>&1 &
|
||||
SRV=$!
|
||||
i=0; while [ $i -lt 50 ]; do
|
||||
$RUN ./nfs_test$EXE 127.0.0.1 $D/export 20049 20048 > $D/out 2>&1 && break
|
||||
grep -q "connect failed" $D/out || break
|
||||
grep -q "connect to " $D/out || break
|
||||
i=$((i + 1)); sleep 0.2
|
||||
done
|
||||
rm -rf $D/export/*
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/* nfs_init_cfg() copies the settings it is given: the caller's struct
|
||||
* may be freed or rewritten at once, while other threads resolve URLs
|
||||
* against whatever was published last. Needs no server - the ports
|
||||
* point where nothing listens, so every connect is refused at once.
|
||||
*
|
||||
* A heap settings struct is freed right after it is handed over and a
|
||||
* stat then resolves a URL (caught under ASan if the client kept the
|
||||
* pointer); then reader threads stat and list while the main thread
|
||||
* rewrites one struct in place and hands it over again and again
|
||||
* (caught under TSan, CFLAGS=-fsanitize=thread). Every call must fail
|
||||
* cleanly with a reason.
|
||||
*
|
||||
* Exit status is the failure count. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <compat/strl.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <retro_atomic.h>
|
||||
#include "../../../libretro-common/vfs/vfs_implementation_nfs.h"
|
||||
|
||||
#define READERS 4
|
||||
#define REPUBLISH 400
|
||||
|
||||
static struct nfs_settings cfg;
|
||||
static char cfg_server[256];
|
||||
static char cfg_export[512];
|
||||
static retro_atomic_int_t done;
|
||||
static retro_atomic_int_t answered; /* calls that wrongly succeeded */
|
||||
static retro_atomic_int_t calls;
|
||||
|
||||
static void reader(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!retro_atomic_load_acquire_int(&done))
|
||||
{
|
||||
int64_t size = 0;
|
||||
nfs_dir_handle *dh;
|
||||
if (retro_vfs_stat_nfs("nfs://127.0.0.1/roms/a.bin", &size))
|
||||
retro_atomic_fetch_add_int(&answered, 1);
|
||||
if ((dh = retro_vfs_opendir_nfs("nfs://127.0.0.1/roms", false)))
|
||||
{
|
||||
retro_atomic_fetch_add_int(&answered, 1);
|
||||
retro_vfs_closedir_nfs(dh);
|
||||
}
|
||||
retro_atomic_fetch_add_int(&calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void fill(struct nfs_settings *s, unsigned round)
|
||||
{
|
||||
strlcpy(cfg_server, "127.0.0.1", sizeof(cfg_server));
|
||||
strlcpy(cfg_export, (round & 1) ? "/srv/export/one" : "/e", sizeof(cfg_export));
|
||||
s->server_address = cfg_server;
|
||||
s->export_path = cfg_export;
|
||||
s->timeout = 1;
|
||||
s->num_contexts = 2;
|
||||
s->nfs_port = 1 + (round & 1);
|
||||
s->mount_port = 1 + (round & 1);
|
||||
s->version = (round & 2) ? 4 : 3;
|
||||
s->readahead = (round & 4) ? 64 : 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sthread_t *t[READERS];
|
||||
unsigned i;
|
||||
int failures = 0;
|
||||
int64_t size = 0;
|
||||
|
||||
/* a struct the caller frees at once */
|
||||
{
|
||||
struct nfs_settings *heap = (struct nfs_settings*)calloc(1, sizeof(*heap));
|
||||
char *exp = (char*)malloc(32);
|
||||
if (!heap || !exp)
|
||||
return 1;
|
||||
strlcpy(exp, "/srv/freed", 32);
|
||||
fill(heap, 0);
|
||||
heap->export_path = exp;
|
||||
if (!nfs_init_cfg(heap))
|
||||
{
|
||||
fprintf(stderr, "FAIL: nfs_init_cfg\n");
|
||||
return 1;
|
||||
}
|
||||
free(exp);
|
||||
free(heap);
|
||||
if (retro_vfs_stat_nfs("nfs://127.0.0.1/a.bin", &size))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat with nothing listening succeeded\n");
|
||||
failures++;
|
||||
}
|
||||
else if (!*nfs_get_last_error())
|
||||
{
|
||||
fprintf(stderr, "FAIL: a failed stat gave no reason\n");
|
||||
failures++;
|
||||
}
|
||||
else
|
||||
printf("ok: settings freed by the caller: %s\n", nfs_get_last_error());
|
||||
}
|
||||
|
||||
/* one struct rewritten in place while readers resolve */
|
||||
fill(&cfg, 0);
|
||||
nfs_init_cfg(&cfg);
|
||||
retro_atomic_int_init(&done, 0);
|
||||
retro_atomic_int_init(&answered, 0);
|
||||
retro_atomic_int_init(&calls, 0);
|
||||
for (i = 0; i < READERS; i++)
|
||||
if (!(t[i] = sthread_create(reader, NULL)))
|
||||
{
|
||||
fprintf(stderr, "FAIL: thread %u\n", i);
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < REPUBLISH; i++)
|
||||
{
|
||||
fill(&cfg, i);
|
||||
nfs_init_cfg(&cfg);
|
||||
}
|
||||
retro_atomic_store_release_int(&done, 1);
|
||||
for (i = 0; i < READERS; i++)
|
||||
sthread_join(t[i]);
|
||||
if (retro_atomic_load_acquire_int(&answered))
|
||||
{
|
||||
fprintf(stderr, "FAIL: %d calls succeeded with nothing listening\n",
|
||||
retro_atomic_load_acquire_int(&answered));
|
||||
failures++;
|
||||
}
|
||||
else
|
||||
printf("ok: %d reader rounds across %d republished settings\n",
|
||||
retro_atomic_load_acquire_int(&calls), REPUBLISH);
|
||||
nfs_shutdown();
|
||||
if (!failures)
|
||||
printf("[pass] nfs_cfg_race_test\n");
|
||||
return failures;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/* A failed nfs:// listing must say why: the reason nfs_get_last_error()
|
||||
* gives is never empty and names what failed. The first checks need no
|
||||
* server; with the port of fake_nfs42.py, a v4 export it does not have
|
||||
* must name the LOOKUP that failed and the status it got. Given the
|
||||
* local copy of the fake's sparse.img too, a file opened before the
|
||||
* settings change must read on with the settings it was opened with,
|
||||
* and with no export set and every slot busy, a file opened by URL must
|
||||
* keep the export its own connection found.
|
||||
*
|
||||
* Usage: nfs_error_test [fake_nfs42.py port [local sparse.img]]
|
||||
* Exit status is the failure count. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <net/net_compat.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <compat/strl.h>
|
||||
#include <file/file_path.h>
|
||||
#include <retro_dirent.h>
|
||||
#include "../../../libretro-common/vfs/vfs_implementation_nfs.h"
|
||||
|
||||
static struct nfs_settings cfg;
|
||||
|
||||
/* Listing @url must fail with a reason that holds @want. */
|
||||
static int expect_fail(const char *what, const char *url, const char *want)
|
||||
{
|
||||
struct RDIR *d = retro_opendir(url);
|
||||
const char *err = nfs_get_last_error();
|
||||
if (d)
|
||||
{
|
||||
retro_closedir(d);
|
||||
fprintf(stderr, "FAIL: %s: %s was listed\n", what, url);
|
||||
return 1;
|
||||
}
|
||||
if (!strstr(err, want))
|
||||
{
|
||||
fprintf(stderr, "FAIL: %s: \"%s\" does not say \"%s\"\n", what, err, want);
|
||||
return 1;
|
||||
}
|
||||
printf("ok: %s: %s\n", what, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Once the port and version point where nothing answers, a stat must
|
||||
* fail, and a file opened before must still read what @local holds:
|
||||
* its handle means nothing on the new settings' connections. */
|
||||
static int expect_open_file_kept(const char *local)
|
||||
{
|
||||
static char got[16384], want[16384];
|
||||
const char *url = "nfs://127.0.0.1/sparse.img";
|
||||
int fails = 0;
|
||||
int part;
|
||||
RFILE *f = filestream_open(url, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
RFILE *l = filestream_open(local, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!f || !l)
|
||||
{
|
||||
fprintf(stderr, "FAIL: open %s and %s (%s)\n", url, local,
|
||||
nfs_get_last_error());
|
||||
fails++;
|
||||
}
|
||||
for (part = 0; part < 3 && !fails; part++)
|
||||
{
|
||||
if (part == 1)
|
||||
{
|
||||
cfg.nfs_port = 1;
|
||||
cfg.mount_port = 1;
|
||||
cfg.version = 3;
|
||||
nfs_init_cfg(&cfg);
|
||||
}
|
||||
if (part > 0 && path_is_valid(url))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat after the change used the old settings\n");
|
||||
fails++;
|
||||
}
|
||||
if ( filestream_read(f, got, sizeof(got)) != (int64_t)sizeof(got)
|
||||
|| filestream_read(l, want, sizeof(want)) != (int64_t)sizeof(want)
|
||||
|| memcmp(got, want, sizeof(got)) != 0)
|
||||
{
|
||||
fprintf(stderr, "FAIL: read %d of the open file, %s the change (%s)\n",
|
||||
part, part ? "after" : "before", nfs_get_last_error());
|
||||
fails++;
|
||||
}
|
||||
}
|
||||
if (f)
|
||||
filestream_close(f);
|
||||
if (l)
|
||||
filestream_close(l);
|
||||
if (!fails && path_is_valid(url))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat used the closed file's connection\n");
|
||||
fails++;
|
||||
}
|
||||
if (!fails)
|
||||
printf("ok: a file opened before a port and version change reads on\n");
|
||||
return fails;
|
||||
}
|
||||
|
||||
/* The only slot held by a prefetcher, a second open of @url goes over
|
||||
* a connection of its own. Its prefetcher connects again, and finds
|
||||
* the file only on the export that connection settled on. */
|
||||
static int expect_private_open_keeps_export(const char *local)
|
||||
{
|
||||
static char got[16384], want[16384];
|
||||
const char *url = "nfs://127.0.0.1/export/sparse.img";
|
||||
unsigned window_kib = 0, direct_kib = 0;
|
||||
int fails = 0;
|
||||
RFILE *hold, *f, *l;
|
||||
cfg.export_path = "";
|
||||
cfg.num_contexts = 1;
|
||||
cfg.readahead = 64;
|
||||
nfs_init_cfg(&cfg);
|
||||
hold = filestream_open(url, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
f = filestream_open(url, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
l = filestream_open(local, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!hold || !f || !l)
|
||||
{
|
||||
fprintf(stderr, "FAIL: open %s twice and %s (%s)\n", url, local,
|
||||
nfs_get_last_error());
|
||||
fails++;
|
||||
}
|
||||
else if (filestream_read(f, got, sizeof(got)) != (int64_t)sizeof(got)
|
||||
|| filestream_read(l, want, sizeof(want)) != (int64_t)sizeof(want)
|
||||
|| memcmp(got, want, sizeof(got)) != 0)
|
||||
{
|
||||
fprintf(stderr, "FAIL: read the file opened with every slot busy (%s)\n",
|
||||
nfs_get_last_error());
|
||||
fails++;
|
||||
}
|
||||
if (f)
|
||||
filestream_close(f);
|
||||
nfs_take_readahead_stats(&window_kib, &direct_kib);
|
||||
if (!fails && !window_kib && !direct_kib)
|
||||
{
|
||||
fprintf(stderr, "FAIL: the file opened with every slot busy had no prefetcher\n");
|
||||
fails++;
|
||||
}
|
||||
if (hold)
|
||||
filestream_close(hold);
|
||||
if (l)
|
||||
filestream_close(l);
|
||||
if (!fails)
|
||||
printf("ok: a file opened by URL with every slot busy keeps its export\n");
|
||||
return fails;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fails = 0;
|
||||
|
||||
network_init();
|
||||
cfg.server_address = "127.0.0.1";
|
||||
cfg.export_path = "";
|
||||
cfg.timeout = 5;
|
||||
cfg.num_contexts = 2;
|
||||
cfg.version = 4;
|
||||
nfs_init_cfg(&cfg);
|
||||
fails += expect_fail("no export", "nfs://127.0.0.1", "NFS Export");
|
||||
fails += expect_fail("no server in the URL", "nfs:///export", "bad URL");
|
||||
|
||||
cfg.export_path = "/export";
|
||||
cfg.nfs_port = 1;
|
||||
nfs_init_cfg(&cfg);
|
||||
fails += expect_fail("nothing on the port", "nfs://127.0.0.1/",
|
||||
"127.0.0.1:1 refused");
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
cfg.export_path = "/export/missing";
|
||||
cfg.nfs_port = (unsigned)atoi(argv[1]);
|
||||
nfs_init_cfg(&cfg);
|
||||
fails += expect_fail("v4 export not there", "nfs://127.0.0.1/",
|
||||
"mount: LOOKUP missing: NFS4ERR_NOENT");
|
||||
|
||||
/* the same pooled connection fails again, this time before
|
||||
* sending anything: the reason must be its own */
|
||||
cfg.export_path = "/export";
|
||||
nfs_init_cfg(&cfg);
|
||||
fails += expect_fail("v4 path not there", "nfs://127.0.0.1/missing",
|
||||
"LOOKUP missing: NFS4ERR_NOENT");
|
||||
{
|
||||
char url[400];
|
||||
size_t _len = strlcpy(url, "nfs://127.0.0.1/", sizeof(url));
|
||||
memset(url + _len, 'a', 300);
|
||||
url[_len + 300] = '\0';
|
||||
fails += expect_fail("v4 name too long", url, "name too long");
|
||||
}
|
||||
}
|
||||
if (argc > 2)
|
||||
{
|
||||
cfg.export_path = "/export";
|
||||
nfs_init_cfg(&cfg);
|
||||
fails += expect_open_file_kept(argv[2]);
|
||||
cfg.nfs_port = (unsigned)atoi(argv[1]);
|
||||
cfg.mount_port = 0;
|
||||
cfg.version = 4;
|
||||
fails += expect_private_open_keeps_export(argv[2]);
|
||||
}
|
||||
nfs_shutdown();
|
||||
return fails;
|
||||
}
|
||||
@@ -272,6 +272,48 @@ int main(int argc, char **argv)
|
||||
}
|
||||
nfs_shutdown();
|
||||
}
|
||||
/* new settings reach the next call while a slot is held: a
|
||||
* prefetcher holds one, a stat connects the other, and once the
|
||||
* ports point where nothing listens no stat may ride an old
|
||||
* connection - the idle one, nor the prefetcher's once it closes */
|
||||
{
|
||||
unsigned nfs_port = cfg.nfs_port;
|
||||
unsigned mount_port = cfg.mount_port;
|
||||
RFILE *f;
|
||||
cfg.readahead = 1024;
|
||||
nfs_init_cfg(&cfg);
|
||||
f = filestream_open(url_file, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!f || !path_is_valid(url_file))
|
||||
{
|
||||
fprintf(stderr, "FAIL: open and stat before the port change\n");
|
||||
failures++;
|
||||
}
|
||||
cfg.nfs_port = 1;
|
||||
cfg.mount_port = 1;
|
||||
nfs_init_cfg(&cfg);
|
||||
if (path_is_valid(url_file))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat after a port change used the old connection\n");
|
||||
failures++;
|
||||
}
|
||||
if (f)
|
||||
filestream_close(f);
|
||||
if (path_is_valid(url_file))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat after a port change used the prefetcher's old connection\n");
|
||||
failures++;
|
||||
}
|
||||
cfg.nfs_port = nfs_port;
|
||||
cfg.mount_port = mount_port;
|
||||
nfs_init_cfg(&cfg);
|
||||
if (!path_is_valid(url_file))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat with the ports set back (%s)\n", nfs_get_last_error());
|
||||
failures++;
|
||||
}
|
||||
nfs_shutdown();
|
||||
}
|
||||
if (failures)
|
||||
{
|
||||
fprintf(stderr, "FAIL: %d bad operations across %d threads\n", failures, NTHREADS);
|
||||
|
||||
@@ -13,7 +13,7 @@ else
|
||||
# file mapping as the shipping build has it (Win32 maps without it)
|
||||
MMAP_FLAGS = -DHAVE_MMAP
|
||||
endif
|
||||
all: smb_test smb_dir_bounds_test vfs_test smb_idle_test vfs_threads_test
|
||||
all: smb_test smb_dir_bounds_test smb_cfg_race_test vfs_test smb_idle_test vfs_threads_test
|
||||
|
||||
smb_test: $(SRC)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(SRC) $(LDLIBS) -o $@
|
||||
@@ -30,6 +30,10 @@ vfs_test: $(VFS_SRC)
|
||||
IDLE_SRC = smb_idle_test.c $(filter-out vfs_test.c,$(VFS_SRC))
|
||||
smb_idle_test: $(IDLE_SRC)
|
||||
$(CC) $(CPPFLAGS) -DHAVE_RETROSMB -DHAVE_SMBCLIENT -DHAVE_NETWORKING -DRARCH_INTERNAL -I$(ROOT) $(CFLAGS) $(IDLE_SRC) $(LDLIBS) -o $@
|
||||
# settings handed over while threads resolve; no server
|
||||
RACE_SRC = smb_cfg_race_test.c $(filter-out vfs_test.c,$(VFS_SRC)) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c $(COMMON)/file/retro_dirent.c
|
||||
smb_cfg_race_test: $(RACE_SRC) $(COMMON)/vfs/vfs_cfg_snapshot.h
|
||||
$(CC) $(CPPFLAGS) -DHAVE_RETROSMB -DHAVE_SMBCLIENT -DHAVE_NETWORKING -DHAVE_THREADS -DRARCH_INTERNAL -I$(ROOT) $(CFLAGS) $(RACE_SRC) $(LDLIBS) -lpthread -o $@
|
||||
THR_SRC = vfs_threads_test.c $(filter-out vfs_test.c,$(VFS_SRC)) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c $(COMMON)/file/retro_dirent.c
|
||||
vfs_threads_test: $(THR_SRC)
|
||||
$(CC) $(CPPFLAGS) -DHAVE_RETROSMB -DHAVE_SMBCLIENT -DHAVE_NETWORKING -DHAVE_THREADS $(MMAP_FLAGS) -DRARCH_INTERNAL -I$(ROOT) $(CFLAGS) $(THR_SRC) $(LDLIBS) -lpthread -o $@
|
||||
@@ -50,5 +54,5 @@ cloud_sync_test_libsmb2: cloud_sync_test.c $(L2_SRC)
|
||||
$(CC) -I$(COMMON)/include -D_GNU_SOURCE -DHAVE_SMBCLIENT -DHAVE_STDINT_H -DHAVE_NETWORKING -DHAVE_CLOUDSYNC -DHAVE_THREADS -DRARCH_INTERNAL -I$(ROOT) $(LIBSMB2_CFLAGS) $(CFLAGS) cloud_sync_test.c $(L2_SRC) $(COMMON)/rthreads/rthreads.c $(COMMON)/rthreads/retro_eventcount.c $(LIBSMB2_LIBS) $(LDLIBS) -lpthread -o $@
|
||||
|
||||
clean:
|
||||
rm -f smb_dir_bounds_test smb_dir_bounds_test.exe smb_idle_test smb_idle_test.exe smb_idle_test_libsmb2 smb_test smb_test.exe vfs_test vfs_test.exe vfs_threads_test vfs_threads_test.exe vfs_test_libsmb2 vfs_threads_test_libsmb2 cloud_sync_test_libsmb2
|
||||
rm -f smb_dir_bounds_test smb_dir_bounds_test.exe smb_cfg_race_test smb_cfg_race_test.exe smb_idle_test smb_idle_test.exe smb_idle_test_libsmb2 smb_test smb_test.exe vfs_test vfs_test.exe vfs_threads_test vfs_threads_test.exe vfs_test_libsmb2 vfs_threads_test_libsmb2 cloud_sync_test_libsmb2
|
||||
.PHONY: all clean
|
||||
@@ -6,8 +6,10 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
# directory pages and names from hostile replies: needs no server
|
||||
make -s smb_dir_bounds_test
|
||||
make -s smb_dir_bounds_test smb_cfg_race_test
|
||||
${RUNNER:-} ./smb_dir_bounds_test${EXE:-}
|
||||
# settings handed over while threads resolve: needs no server
|
||||
${RUNNER:-} ./smb_cfg_race_test${EXE:-}
|
||||
command -v smbd >/dev/null 2>&1 || { echo "skip: no smbd"; exit 0; }
|
||||
[ "$(id -u)" = 0 ] || { echo "skip: needs root for port 445"; exit 0; }
|
||||
make -s smb_test vfs_test vfs_threads_test smb_idle_test
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/* smb_init_cfg() copies the settings it is given: the caller's struct
|
||||
* may be freed or rewritten at once, while other threads build paths
|
||||
* and look for connections against whatever was published last. Needs
|
||||
* no server - none is set, so every call stops before the network
|
||||
* after reading the share, subdir and credentials.
|
||||
*
|
||||
* A heap settings struct is freed right after it is handed over and a
|
||||
* stat then reads it (caught under ASan if the client kept the
|
||||
* pointer); then reader threads stat and list while the main thread
|
||||
* rewrites one struct in place and hands it over again and again
|
||||
* (caught under TSan, CFLAGS=-fsanitize=thread). Every call must fail
|
||||
* cleanly with a reason.
|
||||
*
|
||||
* Exit status is the failure count. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <compat/strl.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <retro_atomic.h>
|
||||
#include "../../../libretro-common/vfs/vfs_implementation_smb.h"
|
||||
|
||||
#define READERS 4
|
||||
#define REPUBLISH 400
|
||||
|
||||
static struct smb_settings cfg;
|
||||
static char cfg_share[256];
|
||||
static char cfg_subdir[256];
|
||||
static char cfg_user[128];
|
||||
static retro_atomic_int_t done;
|
||||
static retro_atomic_int_t answered; /* calls that wrongly succeeded */
|
||||
static retro_atomic_int_t calls;
|
||||
|
||||
static void reader(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!retro_atomic_load_acquire_int(&done))
|
||||
{
|
||||
int64_t size = 0;
|
||||
smb_dir_handle *dh;
|
||||
if (retro_vfs_stat_smb("smb://srv/share/roms/a.bin", &size))
|
||||
retro_atomic_fetch_add_int(&answered, 1);
|
||||
if ((dh = retro_vfs_opendir_smb("smb://srv/share/roms", false)))
|
||||
{
|
||||
retro_atomic_fetch_add_int(&answered, 1);
|
||||
retro_vfs_closedir_smb(dh);
|
||||
}
|
||||
retro_atomic_fetch_add_int(&calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void fill(struct smb_settings *s, unsigned round)
|
||||
{
|
||||
strlcpy(cfg_share, (round & 1) ? "roms" : "a-much-longer-share-name", sizeof(cfg_share));
|
||||
strlcpy(cfg_subdir, (round & 2) ? "/games/snes" : "", sizeof(cfg_subdir));
|
||||
strlcpy(cfg_user, (round & 4) ? "guest" : "", sizeof(cfg_user));
|
||||
s->server_address = "";
|
||||
s->share = cfg_share;
|
||||
s->username = cfg_user;
|
||||
s->password = "";
|
||||
s->workgroup = "";
|
||||
s->subdir = cfg_subdir;
|
||||
s->realm = "";
|
||||
s->kdc = "";
|
||||
s->timeout = 1;
|
||||
s->num_contexts = 2;
|
||||
s->auth_mode = 0;
|
||||
s->readahead = (round & 8) ? 64 : 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sthread_t *t[READERS];
|
||||
unsigned i;
|
||||
int failures = 0;
|
||||
int64_t size = 0;
|
||||
|
||||
/* a struct the caller frees at once */
|
||||
{
|
||||
struct smb_settings *heap = (struct smb_settings*)calloc(1, sizeof(*heap));
|
||||
char *sub = (char*)malloc(32);
|
||||
if (!heap || !sub)
|
||||
return 1;
|
||||
fill(heap, 0);
|
||||
strlcpy(sub, "/freed/subdir", 32);
|
||||
heap->subdir = sub;
|
||||
if (!smb_init_cfg(heap))
|
||||
{
|
||||
fprintf(stderr, "FAIL: smb_init_cfg\n");
|
||||
return 1;
|
||||
}
|
||||
free(sub);
|
||||
free(heap);
|
||||
if (retro_vfs_stat_smb("smb://srv/share/a.bin", &size))
|
||||
{
|
||||
fprintf(stderr, "FAIL: a stat with no server set succeeded\n");
|
||||
failures++;
|
||||
}
|
||||
else if (!*smb_get_last_error())
|
||||
{
|
||||
fprintf(stderr, "FAIL: a failed stat gave no reason\n");
|
||||
failures++;
|
||||
}
|
||||
else
|
||||
printf("ok: settings freed by the caller: %s\n", smb_get_last_error());
|
||||
}
|
||||
|
||||
/* one struct rewritten in place while readers resolve */
|
||||
fill(&cfg, 0);
|
||||
smb_init_cfg(&cfg);
|
||||
retro_atomic_int_init(&done, 0);
|
||||
retro_atomic_int_init(&answered, 0);
|
||||
retro_atomic_int_init(&calls, 0);
|
||||
for (i = 0; i < READERS; i++)
|
||||
if (!(t[i] = sthread_create(reader, NULL)))
|
||||
{
|
||||
fprintf(stderr, "FAIL: thread %u\n", i);
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < REPUBLISH; i++)
|
||||
{
|
||||
fill(&cfg, i);
|
||||
smb_init_cfg(&cfg);
|
||||
}
|
||||
retro_atomic_store_release_int(&done, 1);
|
||||
for (i = 0; i < READERS; i++)
|
||||
sthread_join(t[i]);
|
||||
if (retro_atomic_load_acquire_int(&answered))
|
||||
{
|
||||
fprintf(stderr, "FAIL: %d calls succeeded with no server set\n",
|
||||
retro_atomic_load_acquire_int(&answered));
|
||||
failures++;
|
||||
}
|
||||
else
|
||||
printf("ok: %d reader rounds across %d republished settings\n",
|
||||
retro_atomic_load_acquire_int(&calls), REPUBLISH);
|
||||
smb_shutdown();
|
||||
if (!failures)
|
||||
printf("[pass] smb_cfg_race_test\n");
|
||||
return failures;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ int main(int argc, char **argv)
|
||||
libretro_vfs_implementation_file stream;
|
||||
libretro_vfs_implementation_file held[2];
|
||||
smb_dir_handle *dh;
|
||||
libretro_vfs_implementation_dir *rdir;
|
||||
struct smbc_dirent *ent;
|
||||
char url[512];
|
||||
uint8_t buf[4096];
|
||||
@@ -127,6 +128,23 @@ int main(int argc, char **argv)
|
||||
fprintf(stderr, "FAIL: share browse (%d)\n", found);
|
||||
return 1;
|
||||
}
|
||||
/* the same listing through the dispatcher, as the menu browses it */
|
||||
found = 0;
|
||||
if (!(rdir = retro_vfs_opendir_impl(url, false)))
|
||||
{
|
||||
fprintf(stderr, "FAIL: dispatcher opendir server\n");
|
||||
return 1;
|
||||
}
|
||||
while (retro_vfs_readdir_impl(rdir))
|
||||
if ( strcmp(retro_vfs_dirent_get_name_impl(rdir), argv[2]) == 0
|
||||
&& retro_vfs_dirent_is_dir_impl(rdir))
|
||||
found = 1;
|
||||
retro_vfs_closedir_impl(rdir);
|
||||
if (found != 1)
|
||||
{
|
||||
fprintf(stderr, "FAIL: dispatcher share browse\n");
|
||||
return 1;
|
||||
}
|
||||
smb_shutdown();
|
||||
printf("ok: VFS over the SMB backend\n");
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user