mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-23 00:52:29 -05:00
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>
103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""A Samba server of the tests' own, run as this user on a free port of 127.0.0.1 (no root):
|
|
a guest share, "games", and one only a user with a password opens, "locked", both over the
|
|
same folder. Its folders are short paths under /tmp: smbd's sockets live there, and a socket's
|
|
path has a length limit the tests' long temporary paths can pass."""
|
|
import getpass
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import signal
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
|
|
AVAILABLE = bool(shutil.which('smbd') and shutil.which('pdbedit'))
|
|
USER = getpass.getuser()
|
|
PASSWORD = 'Pa55 word!'
|
|
|
|
|
|
class SambaServer:
|
|
def __init__(self):
|
|
self.home = Path(tempfile.mkdtemp(prefix='smb', dir='/tmp'))
|
|
self.share = self.home / 'share'
|
|
for name in ('lock', 'state', 'cache', 'pid', 'private', 'ncalrpc', 'log', 'share'):
|
|
(self.home / name).mkdir()
|
|
with socket.socket() as probe:
|
|
probe.bind(('127.0.0.1', 0))
|
|
self.port = probe.getsockname()[1]
|
|
home = self.home
|
|
(home / 'smb.conf').write_text(f'''[global]
|
|
smb ports = {self.port}
|
|
bind interfaces only = yes
|
|
interfaces = 127.0.0.1
|
|
lock directory = {home}/lock
|
|
state directory = {home}/state
|
|
cache directory = {home}/cache
|
|
pid directory = {home}/pid
|
|
private dir = {home}/private
|
|
ncalrpc dir = {home}/ncalrpc
|
|
log file = {home}/log/%m.log
|
|
passdb backend = tdbsam:{home}/private/passdb.tdb
|
|
disable netbios = yes
|
|
server min protocol = SMB2
|
|
map to guest = Bad User
|
|
server role = standalone server
|
|
load printers = no
|
|
printcap name = /dev/null
|
|
[games]
|
|
path = {self.share}
|
|
guest ok = yes
|
|
read only = yes
|
|
[locked]
|
|
path = {self.share}
|
|
valid users = {USER}
|
|
read only = yes
|
|
''')
|
|
subprocess.run(['pdbedit', '-s', str(home / 'smb.conf'), '-a', '-u', USER, '-t'],
|
|
input=f'{PASSWORD}\n{PASSWORD}\n', text=True, check=True, capture_output=True)
|
|
self.start()
|
|
|
|
def start(self):
|
|
self.log = (self.home / 'log/smbd.out').open('a')
|
|
self.process = subprocess.Popen(['smbd', '-F', '--no-process-group', '-s', str(self.home / 'smb.conf'),
|
|
'--debug-stdout', '-d1'],
|
|
stdout=self.log, stderr=subprocess.STDOUT,
|
|
# smbd -F stops at the end of its input: this pipe stays open
|
|
stdin=subprocess.PIPE, start_new_session=True)
|
|
deadline = time.monotonic() + 15
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
socket.create_connection(('127.0.0.1', self.port), timeout=1).close()
|
|
return
|
|
except OSError:
|
|
if self.process.poll() is not None:
|
|
break
|
|
time.sleep(0.05)
|
|
output = (self.home / 'log/smbd.out').read_text()[-2000:]
|
|
self.stop()
|
|
raise RuntimeError('smbd did not start:\n' + output)
|
|
|
|
def restart(self):
|
|
"""The server goes away and comes back: every connection to it is dropped."""
|
|
self.halt()
|
|
self.start()
|
|
|
|
@property
|
|
def address(self):
|
|
return f'127.0.0.1:{self.port}'
|
|
|
|
def halt(self):
|
|
"""smbd serves each connection from a child of its own: the whole group goes."""
|
|
try:
|
|
os.killpg(self.process.pid, signal.SIGKILL)
|
|
except ProcessLookupError:
|
|
pass
|
|
self.process.wait()
|
|
self.process.stdin.close()
|
|
self.log.close()
|
|
|
|
def stop(self):
|
|
self.halt()
|
|
shutil.rmtree(self.home, ignore_errors=True)
|