Files
MihawkandClaude aa7f0b2fbe SMB: RetroArch's file browser opens a shared folder from the WebUI
The WebUI's new SMB page signs in to one server (guest or a user and
password), lists its shares, checks a share and folder and saves them in
config/smb.cfg on the console; the password is never answered back.
RetroArch's file browser shows the share as SMB, after INTERNAL and
EXTERNAL. smb:// paths go through RetroArch's VFS to src/ps5_smb_vfs.c
(patches/series 0136), read-only and only on the saved server: a core that
reads through the VFS streams the file, one that opens paths itself gets a
copy in /app0/smb-cache, removed when the content closes.

libsmb2 is pinned at the master revision itsPLK/ps5-pkg-manager runs on the
PS5 (LGPL-2.1-or-later), built by tools/build-libsmb2.sh for the PS5 and the
host and listed in tooling/notices/components.json: RetroArch 1.22.2 has no
SMB client. In a title, getaddrinfo is bound to the platform layer's, which
resolves nothing, so libsmb2's resolver calls go to RetroArch's own
(src/platform_wraps.c); readv, writev, getlogin_r and getprotobyname are
wrapped, so libsmb2 adds no import. SMB sockets set SO_NOSIGPIPE.

Evidence: evidence/smb-load-content (python3 tools/evidence.py compare
evidence/): on the Base PS5 the console's WebUI listed the PC's guest share
and saved share/smb-test; Load Content > SMB listed it and a homebrew NES
ROM on it ran under FCEUmm, read through the VFS (build a2c7800a,
retroarch.log read back over FTP).

Gates: tools/verify.sh (format, unit 214, build, integration, evidence 140)
with PS5_REUSE_STAGED_CORES=ps5sx2: in this worktree the SDK's prospero-lld
wrapper adds -pie to ps5sx2's link (clang passes its long object list in a
response file the wrapper cannot read), so ps5sx2 is the main checkout's
build of the same pinned revision. Host tests run a Samba server of their
own (tests/smb_server.py, no root).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-18 17:42:06 -04:00

62 lines
3.3 KiB
Python

"""Builds the WebUI's server for the host (the tests' fixtures): the given main, the
server's sources and its libraries, with defines. One list of sources for every test."""
from pathlib import Path
import http.client
import json
import socket
import subprocess
import time
ROOT = Path(__file__).resolve().parent.parent
SOURCES = ['src/webui_ps5.cpp', 'src/webui_transfer.cpp', 'src/webui_update.cpp', 'src/scraper.cpp',
'src/scraper_http.cpp', 'src/ps5_forwarders.cpp']
C_SOURCES = ['src/ps5_library.c', 'src/ps5_smb.c']
def build(output, main, defines=(), flags=('-O1', '-g')):
output = Path(output)
http = subprocess.check_output(['bash', 'tools/build-webui-http.sh', 'host'], cwd=ROOT, text=True).strip()
update = subprocess.check_output(['bash', 'tools/build-webui-update.sh', 'host'], cwd=ROOT, text=True).strip()
smb2 = subprocess.check_output(['bash', 'tools/build-libsmb2.sh', 'host'], cwd=ROOT, text=True).strip()
objects = []
for source in C_SOURCES:
obj = output.with_name(output.name + '-' + Path(source).stem + '.o')
subprocess.run(['cc', '-std=c11', '-D_DEFAULT_SOURCE', *flags, '-I' + smb2 + '/include', '-c', source, '-o', str(obj)], cwd=ROOT, check=True)
objects.append(str(obj))
subprocess.run(['c++', '-std=c++17', *flags, '-pthread', '-Isrc', '-I' + smb2 + '/include',
'-I' + str(ROOT / '.deps/webui/libmicrohttpd-1.0.10/src/include'),
'-I' + str(ROOT / '.deps/native/zlib/zlib-1.3.2/contrib/minizip'),
'-I' + str(ROOT / 'vendor/retroarch/deps/mbedtls'),
'-DPS5_SCRAPER_CURL', # the daemon's HTTP client, the host's libcurl
*[f'-D{d}' for d in defines], main, *SOURCES, *objects, http, update, smb2 + '/lib/libsmb2.a', '-lz', '-lcurl',
'-o', str(output)], cwd=ROOT, check=True)
return output
def start(binary, root, env=None, seconds=8.0):
"""Starts a server built above (tests/webui_server_main.cpp) for the title folder root on a
free port, once it answers: its process, port and session token. A port found free can be
taken before the server binds it (another connection gets it as its own port): the server
then exits at once, "Address already in use", and starts again on another."""
for _ in range(5):
with socket.socket() as probe:
probe.bind(('127.0.0.1', 0))
port = probe.getsockname()[1]
process = subprocess.Popen([str(binary), str(root), str(port)], env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
deadline = time.monotonic() + seconds
while process.poll() is None and time.monotonic() < deadline:
try:
connection = http.client.HTTPConnection('127.0.0.1', port, timeout=5)
connection.request('GET', '/api/status')
token = json.loads(connection.getresponse().read())['token']
connection.close()
return process, port, token
except (OSError, http.client.HTTPException):
time.sleep(0.02)
if process.poll() is None: # running, and no answer: no port problem
process.kill()
process.wait()
break
raise RuntimeError('the server did not start')