libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
OgcSmbDriver.cpp
1/****************************************************************************
2 * Platform Abstraction Layer (OGC driver)
3 * Daryl Borth 2026
4 * OgcSmbDriver.cpp
5 *
6 * Registers a "smb:/" newlib devoptab backed by libsmb2's synchronous
7 * client API (smb2_open/smb2_pread/smb2_opendir/...)
8 *
9 * All paths handed in by newlib arrive with leading 'smb:/'
10 * so every callback strips it via RelativePath() below.
11 ***************************************************************************/
12#include <cerrno>
13#include <cstring>
14#include <ctime>
15#include <fcntl.h>
16#include <limits.h>
17#include <unistd.h>
18#include <sys/stat.h>
19#include <sys/types.h>
20#include <sys/iosupport.h>
21#include <smb2/smb2.h>
22#include <smb2/libsmb2.h>
23#include <cstdio>
24#include "OgcSmbDriver.h"
25#include "../Logger.h"
26
27#ifdef HW_DOL
28#include "gamecube/GameCubeNetwork.h"
29#else
30#include "wii/WiiNetwork.h"
31#endif
32
33smb2_context * OgcSmbDriver::ctx = nullptr;
34
35// Fixed device name (smb:/)
36static const char * const smbDeviceName = "smb";
37
38// Last failure's detail, in libsmb2's own words where we have a context to
39// ask - a real protocol/auth error ("NT_STATUS_ACCESS_DENIED", "Failed to
40// resolve hostname") tells you far more than the generic POSIX errno
41static char lastError[256] = "";
42
43static void CaptureSmb2Error(const char * context)
44{
45 const char * detail = OgcSmbDriver::getContext() ? smb2_get_error(OgcSmbDriver::getContext()) : nullptr;
46
47 if(detail && detail[0])
48 snprintf(lastError, sizeof(lastError), "%s: %s", context, detail);
49 else
50 snprintf(lastError, sizeof(lastError), "%s", context);
51}
52
53/****************************************************************************
54 * Path / error helpers
55 ***************************************************************************/
56static const char * RelativePath(const char * path)
57{
58 // Devoptab callbacks get the ORIGINAL path exactly as the app passed
59 // it to opendir()/open()/etc - including our own "smb:" device prefix
60 // Strip the device prefix before passing to libsmb2
61 const char * colon = strchr(path, ':');
62 if(colon)
63 path = colon + 1;
64
65 if(path[0] == '/')
66 path++;
67 return path;
68}
69
70static int SetErrnoFromSmb2(int smb2Result)
71{
72 // libsmb2's sync calls return -errno directly on failure.
73 errno = (smb2Result < 0) ? -smb2Result : EIO;
74 return -1;
75}
76
77static void FillStatFromSmb2(struct stat * st, const smb2_stat_64 & src)
78{
79 memset(st, 0, sizeof(struct stat));
80 st->st_size = (off_t)src.smb2_size;
81 st->st_mode = (src.smb2_type == SMB2_TYPE_DIRECTORY) ? (S_IFDIR | 0777) : (S_IFREG | 0666);
82 st->st_nlink = src.smb2_nlink ? src.smb2_nlink : 1;
83 st->st_mtime = (time_t)src.smb2_mtime;
84 st->st_atime = (time_t)src.smb2_atime;
85 st->st_ctime = (time_t)src.smb2_ctime;
86}
87
88/****************************************************************************
89 * File state - devoptab hands us a caller-sized blob per open fd; we use it
90 * to track the smb2fh plus our own read/write cursor, since we drive the
91 * connection with pread/pwrite (explicit offsets) rather than depending on
92 * an internal libsmb2 seek position.
93 ***************************************************************************/
96{
97 smb2fh * fh;
98 uint64_t offset;
99};
100
101static int smb_open_r(struct _reent *, void * fileStruct, const char * path, int flags, int)
102{
103 SmbFileState * state = (SmbFileState *)fileStruct;
104
105 // libsmb2's smb2_open() only documents O_RDONLY/O_WRONLY/O_RDWR/O_SYNC/
106 // O_CREAT/O_EXCL as supported - O_APPEND is handled ourselves below via
107 // the tracked write cursor, so it's stripped before handing flags off
108 // rather than relying on the server/library to honor it.
109 bool append = (flags & O_APPEND) != 0;
110 smb2fh * fh = smb2_open(OgcSmbDriver::getContext(), RelativePath(path), flags & ~O_APPEND);
111 if(!fh)
112 {
113 CaptureSmb2Error("open");
114 return SetErrnoFromSmb2(-EIO);
115 }
116
117 state->fh = fh;
118 state->offset = 0;
119
120 if(append)
121 {
122 smb2_stat_64 st;
123 if(smb2_fstat(OgcSmbDriver::getContext(), fh, &st) == 0)
124 state->offset = st.smb2_size;
125 }
126
127 return 0;
128}
129
130static int smb_close_r(struct _reent *, void * fileStruct)
131{
132 SmbFileState * state = (SmbFileState *)fileStruct;
133 smb2_close(OgcSmbDriver::getContext(), state->fh);
134 return 0;
135}
136
137static ssize_t smb_write_r(struct _reent *, void * fileStruct, const char * ptr, size_t len)
138{
139 SmbFileState * state = (SmbFileState *)fileStruct;
140 int written = smb2_pwrite(OgcSmbDriver::getContext(), state->fh, (const uint8_t *)ptr, (uint32_t)len, state->offset);
141 if(written < 0)
142 return SetErrnoFromSmb2(written);
143
144 state->offset += written;
145 return written;
146}
147
148static ssize_t smb_read_r(struct _reent *, void * fileStruct, char * ptr, size_t len)
149{
150 SmbFileState * state = (SmbFileState *)fileStruct;
151 int bytesRead = smb2_pread(OgcSmbDriver::getContext(), state->fh, (uint8_t *)ptr, (uint32_t)len, state->offset);
152 if(bytesRead < 0)
153 return SetErrnoFromSmb2(bytesRead);
154
155 state->offset += bytesRead;
156 return bytesRead;
157}
158
159static off_t smb_seek_r(struct _reent *, void * fileStruct, off_t pos, int dir)
160{
161 SmbFileState * state = (SmbFileState *)fileStruct;
162
163 uint64_t base = 0;
164 if(dir == SEEK_CUR)
165 base = state->offset;
166 else if(dir == SEEK_END)
167 {
168 smb2_stat_64 st;
169 if(smb2_fstat(OgcSmbDriver::getContext(), state->fh, &st) < 0)
170 return SetErrnoFromSmb2(-EIO);
171 base = st.smb2_size;
172 }
173 // SEEK_SET: base stays 0
174
175 state->offset = base + pos;
176 return (off_t)state->offset;
177}
178
179static int smb_fstat_r(struct _reent *, void * fileStruct, struct stat * st)
180{
181 SmbFileState * state = (SmbFileState *)fileStruct;
182 smb2_stat_64 s;
183 int result = smb2_fstat(OgcSmbDriver::getContext(), state->fh, &s);
184 if(result < 0)
185 {
186 CaptureSmb2Error("fstat");
187 return SetErrnoFromSmb2(result);
188 }
189
190 FillStatFromSmb2(st, s);
191 return 0;
192}
193
194static int smb_stat_r(struct _reent *, const char * path, struct stat * st)
195{
196 smb2_stat_64 s;
197 int result = smb2_stat(OgcSmbDriver::getContext(), RelativePath(path), &s);
198 if(result < 0)
199 {
200 CaptureSmb2Error("stat");
201 return SetErrnoFromSmb2(result);
202 }
203
204 FillStatFromSmb2(st, s);
205 return 0;
206}
207
208static int smb_unlink_r(struct _reent *, const char * path)
209{
210 int result = smb2_unlink(OgcSmbDriver::getContext(), RelativePath(path));
211 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
212}
213
214static int smb_mkdir_r(struct _reent *, const char * path, int)
215{
216 int result = smb2_mkdir(OgcSmbDriver::getContext(), RelativePath(path));
217 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
218}
219
220static int smb_rmdir_r(struct _reent *, const char * path)
221{
222 int result = smb2_rmdir(OgcSmbDriver::getContext(), RelativePath(path));
223 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
224}
225
226/****************************************************************************
227 * Directory iteration
228 ***************************************************************************/
231{
232 smb2dir * dir;
233};
234
235static DIR_ITER * smb_diropen_r(struct _reent *, DIR_ITER * dirState, const char * path)
236{
237 const char * relPath = RelativePath(path);
238 smb2dir * dir = smb2_opendir(OgcSmbDriver::getContext(), relPath);
239 if(!dir)
240 {
241 char context[64];
242 snprintf(context, sizeof(context), "opendir '%s'", relPath[0] ? relPath : "(root)");
243 CaptureSmb2Error(context);
244 SetErrnoFromSmb2(-ENOENT);
245 return nullptr;
246 }
247
248 ((SmbDirState *)dirState->dirStruct)->dir = dir;
249 return dirState;
250}
251
252static int smb_dirreset_r(struct _reent *, DIR_ITER * dirState)
253{
254 smb2_rewinddir(OgcSmbDriver::getContext(), ((SmbDirState *)dirState->dirStruct)->dir);
255 return 0;
256}
257
258static int smb_dirnext_r(struct _reent *, DIR_ITER * dirState, char * filename, struct stat * filestat)
259{
260 smb2dir * dir = ((SmbDirState *)dirState->dirStruct)->dir;
261 smb2dirent * entry = smb2_readdir(OgcSmbDriver::getContext(), dir);
262 if(!entry)
263 {
264 errno = ENOENT; // no more entries - the standard devoptab end-of-listing signal
265 return -1;
266 }
267
268 strncpy(filename, entry->name, NAME_MAX);
269 filename[NAME_MAX] = '\0';
270 FillStatFromSmb2(filestat, entry->st);
271 return 0;
272}
273
274static int smb_dirclose_r(struct _reent *, DIR_ITER * dirState)
275{
276 smb2_closedir(OgcSmbDriver::getContext(), ((SmbDirState *)dirState->dirStruct)->dir);
277 return 0;
278}
279
280/****************************************************************************
281 * Devoptab registration
282 ***************************************************************************/
283static devoptab_t BuildSmbDevoptab()
284{
285 devoptab_t dotab = {};
286
287 dotab.name = smbDeviceName;
288 dotab.structSize = sizeof(SmbFileState);
289 dotab.open_r = smb_open_r;
290 dotab.close_r = smb_close_r;
291 dotab.write_r = smb_write_r;
292 dotab.read_r = smb_read_r;
293 dotab.seek_r = smb_seek_r;
294 dotab.fstat_r = smb_fstat_r;
295 dotab.stat_r = smb_stat_r;
296 dotab.unlink_r = smb_unlink_r;
297 dotab.mkdir_r = smb_mkdir_r;
298 dotab.rmdir_r = smb_rmdir_r;
299 dotab.dirStateSize = sizeof(SmbDirState);
300 dotab.diropen_r = smb_diropen_r;
301 dotab.dirreset_r = smb_dirreset_r;
302 dotab.dirnext_r = smb_dirnext_r;
303 dotab.dirclose_r = smb_dirclose_r;
304
305 return dotab;
306}
307
308/****************************************************************************
309 * OgcSmbDriver
310 ***************************************************************************/
312{
313 // Bring-up happens lazily in ensureNetworkUp() on the first connect()
314}
315
317{
318 disconnect();
319
320#ifdef HW_DOL
322#else
324#endif
325}
326
328{
329#ifdef HW_DOL
330 return GameCubeNetwork::isUp();
331#else
332 return WiiNetwork::isUp();
333#endif
334}
335
337{
338#ifdef HW_DOL
339 if(GameCubeNetwork::ensureUp()) return true;
340#else
341 if(WiiNetwork::ensureUp()) return true;
342#endif
343 snprintf(lastError, sizeof(lastError), "Network unavailable");
344 return false;
345}
346
348{
349 lastError[0] = '\0';
350
351 if(info.host[0] == '\0' || info.share[0] == '\0')
353
354 if(ctx && strcmp(current.host, info.host) == 0 && strcmp(current.share, info.share) == 0 && strcmp(current.user, info.user) == 0)
355 return SmbConnectResult::Success; // already connected to this exact target
356
357 disconnect(); // drop any existing connection to a *different* target first
358 if(!ensureNetworkUp())
360
361 ctx = smb2_init_context();
362 if(!ctx)
363 {
364 snprintf(lastError, sizeof(lastError), "Connection failed");
366 }
367
368 smb2_set_security_mode(ctx, SMB2_NEGOTIATE_SIGNING_ENABLED);
369 smb2_set_version(ctx, SMB2_VERSION_ANY2);
370 smb2_set_timeout(ctx, 30);
371 if(info.user[0] != '\0')
372 smb2_set_user(ctx, info.user);
373 smb2_set_password(ctx, info.password);
374
375 // This is the single call most likely to be the hang: it does DNS
376 // resolution, TCP connect, and the full SMB2 negotiate/session-setup/
377 // tree-connect exchange synchronously, with no timeout of its own.
378 // If a lockup reliably lands here, try connecting by bare IP (rules
379 // out getaddrinfo()/DNS) and check whether libsmb2 was built assuming
380 // standard poll()/select() semantics that libogc's socket layer
381 // doesn't fully provide.
382 int connectResult = smb2_connect_share(ctx, info.host, info.share, info.user[0] ? info.user : nullptr);
383 if(connectResult != 0)
384 {
385 CaptureSmb2Error("connect_share");
386 smb2_destroy_context(ctx);
387 ctx = nullptr;
389 }
390
391 static devoptab_t smbDevoptab = BuildSmbDevoptab();
392 int devnum = AddDevice(&smbDevoptab);
393 if(devnum < 0)
394 {
395 // A real connection succeeded but the devoptab itself couldn't be registered
396 snprintf(lastError, sizeof(lastError), "Add device failed");
397 smb2_disconnect_share(ctx);
398 smb2_destroy_context(ctx);
399 ctx = nullptr;
401 }
402 devoptabAdded = true;
403
404 current = info;
405 return SmbConnectResult::Success;
406}
407
409{
410 if(devoptabAdded)
411 {
412 RemoveDevice("smb:");
413 devoptabAdded = false;
414 }
415
416 if(ctx)
417 {
418 smb2_disconnect_share(ctx);
419 smb2_destroy_context(ctx);
420 ctx = nullptr;
421 }
422
423 memset(&current, 0, sizeof(current));
424}
425
427{
428 switch(result)
429 {
430 case SmbConnectResult::Success: return "Connected.";
431 case SmbConnectResult::InvalidSettings: return "Network share host/name is blank.";
432 case SmbConnectResult::NetworkUnavailable: return "Unable to initialize network!";
433 case SmbConnectResult::ConnectFailed: return "Failed to connect to network share.";
434 default: return "Unknown network share error.";
435 }
436}
437
438const char * OgcSmbDriver::getLastError() const
439{
440 return lastError;
441}
static void shutdown()
No-op on GameCube.
static bool isUp()
Non-blocking: true if the console currently has a usable IP.
static bool ensureUp()
Brings the network interface up if it isn't already, or confirms it's still alive if it is.
void shutdown() override
Disconnects if connected and releases whatever init() set up.
bool ensureNetworkUp() override
Ensures the network is up via the platform's network helper (WiiNetwork or GameCubeNetwork).
SmbConnectResult connect(const SmbShareInfo &info) override
Attempts to connect and mount in one call.
const char * connectResultMessage(SmbConnectResult result) const override
Short, user-displayable reason for a non-Success SmbConnectResult.
void disconnect() override
Unmounts and drops the connection.
static smb2_context * getContext()
The active context, used by the free-function devoptab callbacks in OgcSmbDriver.cpp.
void init() override
One-time setup.
bool isNetworkUp() const override
True if the network is already up, i.e.
const char * getLastError() const override
Raw, implementation-specific detail behind the last failure.
static void shutdown()
Stops and joins the background bring-up thread.
static bool isUp()
Non-blocking: true if the console currently has a usable IP.
static bool ensureUp(bool(*cancel)(void)=nullptr)
Brings the network interface up if it isn't already, or confirms it's still alive if it is.
SmbConnectResult
Result of a single connect() attempt.
Definition SmbDriver.h:33
@ ConnectFailed
network's up but the server/share/credentials didn't work
Definition SmbDriver.h:37
@ InvalidSettings
host or share is empty - nothing to try
Definition SmbDriver.h:35
@ NetworkUnavailable
no usable network connection at all
Definition SmbDriver.h:36
Per-open-directory state for the smb: devoptab directory callbacks.
smb2dir * dir
libsmb2 handle of the directory being iterated
Per-open-file state for the smb: devoptab file callbacks.
smb2fh * fh
libsmb2 handle of the open file
uint64_t offset
Current read/write position, passed explicitly to smb2_pread/smb2_pwrite.
Credentials/target for a single SMB share.
Definition SmbDriver.h:20
char share[64]
share name, eg.
Definition SmbDriver.h:22
char password[32]
"" for guest/anonymous
Definition SmbDriver.h:24
char user[32]
"" for guest/anonymous
Definition SmbDriver.h:23
char host[64]
server IP or hostname
Definition SmbDriver.h:21