libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
WutSmbDriver.cpp
1/****************************************************************************
2 * Platform Abstraction Layer (WUT driver)
3 * Daryl Borth 2026
4 * WutSmbDriver.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
22#include <smb2/smb2.h>
23#include <smb2/libsmb2.h>
24#include <cstdio>
25#include <nn/ac.h>
26#include "WutSmbDriver.h"
27#include "../Logger.h"
28
29smb2_context * WutSmbDriver::ctx = nullptr;
30
31// Fixed device name (smb:/)
32static const char * const kSmbDeviceName = "smb";
33
34// Last failure's detail, in libsmb2's own words where we have a context to
35// ask - a real protocol/auth error ("NT_STATUS_ACCESS_DENIED", "Failed to
36// resolve hostname") tells you far more than the generic POSIX errno
37static char g_lastError[256] = "";
38
39static void CaptureSmb2Error(const char * context)
40{
41 const char * detail = WutSmbDriver::getContext() ? smb2_get_error(WutSmbDriver::getContext()) : nullptr;
42
43 if(detail && detail[0])
44 snprintf(g_lastError, sizeof(g_lastError), "%s: %s", context, detail);
45 else
46 snprintf(g_lastError, sizeof(g_lastError), "%s", context);
47}
48
49/****************************************************************************
50 * Path / error helpers
51 ***************************************************************************/
52static const char * RelativePath(const char * path)
53{
54 // Devoptab callbacks get the ORIGINAL path exactly as the app passed
55 // it to opendir()/open()/etc - including our own "smb:" device prefix
56 // Strip the device prefix before passing to libsmb2
57 const char * colon = strchr(path, ':');
58 if(colon)
59 path = colon + 1;
60
61 if(path[0] == '/')
62 path++;
63 return path;
64}
65
66static int SetErrnoFromSmb2(int smb2Result)
67{
68 // libsmb2's sync calls return -errno directly on failure.
69 errno = (smb2Result < 0) ? -smb2Result : EIO;
70 return -1;
71}
72
73static void FillStatFromSmb2(struct stat * st, const smb2_stat_64 & src)
74{
75 memset(st, 0, sizeof(struct stat));
76 st->st_size = (off_t)src.smb2_size;
77 st->st_mode = (src.smb2_type == SMB2_TYPE_DIRECTORY) ? (S_IFDIR | 0777) : (S_IFREG | 0666);
78 st->st_nlink = src.smb2_nlink ? src.smb2_nlink : 1;
79 st->st_mtime = (time_t)src.smb2_mtime;
80 st->st_atime = (time_t)src.smb2_atime;
81 st->st_ctime = (time_t)src.smb2_ctime;
82}
83
84/****************************************************************************
85 * File state - devoptab hands us a caller-sized blob per open fd; we use it
86 * to track the smb2fh plus our own read/write cursor, since we drive the
87 * connection with pread/pwrite (explicit offsets) rather than depending on
88 * an internal libsmb2 seek position.
89 ***************************************************************************/
91struct SmbFileState
92{
93 smb2fh * fh;
94 uint64_t offset;
95};
96
97static int smb_open_r(struct _reent *, void * fileStruct, const char * path, int flags, int)
98{
99 SmbFileState * state = (SmbFileState *)fileStruct;
100
101 // libsmb2's smb2_open() only documents O_RDONLY/O_WRONLY/O_RDWR/O_SYNC/
102 // O_CREAT/O_EXCL as supported - O_APPEND is handled ourselves below via
103 // the tracked write cursor, so it's stripped before handing flags off
104 // rather than relying on the server/library to honor it.
105 bool append = (flags & O_APPEND) != 0;
106 smb2fh * fh = smb2_open(WutSmbDriver::getContext(), RelativePath(path), flags & ~O_APPEND);
107 if(!fh)
108 {
109 CaptureSmb2Error("open");
110 return SetErrnoFromSmb2(-EIO);
111 }
112
113 state->fh = fh;
114 state->offset = 0;
115
116 if(append)
117 {
118 smb2_stat_64 st;
119 if(smb2_fstat(WutSmbDriver::getContext(), fh, &st) == 0)
120 state->offset = st.smb2_size;
121 }
122
123 return 0;
124}
125
126static int smb_close_r(struct _reent *, void * fileStruct)
127{
128 SmbFileState * state = (SmbFileState *)fileStruct;
129 smb2_close(WutSmbDriver::getContext(), state->fh);
130 return 0;
131}
132
133static ssize_t smb_write_r(struct _reent *, void * fileStruct, const char * ptr, size_t len)
134{
135 SmbFileState * state = (SmbFileState *)fileStruct;
136 int written = smb2_pwrite(WutSmbDriver::getContext(), state->fh, (const uint8_t *)ptr, (uint32_t)len, state->offset);
137 if(written < 0)
138 return SetErrnoFromSmb2(written);
139
140 state->offset += written;
141 return written;
142}
143
144static ssize_t smb_read_r(struct _reent *, void * fileStruct, char * ptr, size_t len)
145{
146 SmbFileState * state = (SmbFileState *)fileStruct;
147 int bytesRead = smb2_pread(WutSmbDriver::getContext(), state->fh, (uint8_t *)ptr, (uint32_t)len, state->offset);
148 if(bytesRead < 0)
149 return SetErrnoFromSmb2(bytesRead);
150
151 state->offset += bytesRead;
152 return bytesRead;
153}
154
155static off_t smb_seek_r(struct _reent *, void * fileStruct, off_t pos, int dir)
156{
157 SmbFileState * state = (SmbFileState *)fileStruct;
158
159 uint64_t base = 0;
160 if(dir == SEEK_CUR)
161 base = state->offset;
162 else if(dir == SEEK_END)
163 {
164 smb2_stat_64 st;
165 if(smb2_fstat(WutSmbDriver::getContext(), state->fh, &st) < 0)
166 return SetErrnoFromSmb2(-EIO);
167 base = st.smb2_size;
168 }
169 // SEEK_SET: base stays 0
170
171 state->offset = base + pos;
172 return (off_t)state->offset;
173}
174
175static int smb_fstat_r(struct _reent *, void * fileStruct, struct stat * st)
176{
177 SmbFileState * state = (SmbFileState *)fileStruct;
178 smb2_stat_64 s;
179 int result = smb2_fstat(WutSmbDriver::getContext(), state->fh, &s);
180 if(result < 0)
181 {
182 CaptureSmb2Error("fstat");
183 return SetErrnoFromSmb2(result);
184 }
185
186 FillStatFromSmb2(st, s);
187 return 0;
188}
189
190static int smb_stat_r(struct _reent *, const char * path, struct stat * st)
191{
192 smb2_stat_64 s;
193 int result = smb2_stat(WutSmbDriver::getContext(), RelativePath(path), &s);
194 if(result < 0)
195 {
196 CaptureSmb2Error("stat");
197 return SetErrnoFromSmb2(result);
198 }
199
200 FillStatFromSmb2(st, s);
201 return 0;
202}
203
204static int smb_unlink_r(struct _reent *, const char * path)
205{
206 int result = smb2_unlink(WutSmbDriver::getContext(), RelativePath(path));
207 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
208}
209
210static int smb_mkdir_r(struct _reent *, const char * path, int)
211{
212 int result = smb2_mkdir(WutSmbDriver::getContext(), RelativePath(path));
213 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
214}
215
216static int smb_rmdir_r(struct _reent *, const char * path)
217{
218 int result = smb2_rmdir(WutSmbDriver::getContext(), RelativePath(path));
219 return (result < 0) ? SetErrnoFromSmb2(result) : 0;
220}
221
222/****************************************************************************
223 * Directory iteration
224 ***************************************************************************/
226struct SmbDirState
227{
228 smb2dir * dir;
229};
230
231static DIR_ITER * smb_diropen_r(struct _reent *, DIR_ITER * dirState, const char * path)
232{
233 const char * relPath = RelativePath(path);
234 smb2dir * dir = smb2_opendir(WutSmbDriver::getContext(), relPath);
235 if(!dir)
236 {
237 char context[64];
238 snprintf(context, sizeof(context), "opendir '%s'", relPath[0] ? relPath : "(root)");
239 CaptureSmb2Error(context);
240 SetErrnoFromSmb2(-ENOENT);
241 return nullptr;
242 }
243
244 ((SmbDirState *)dirState->dirStruct)->dir = dir;
245 return dirState;
246}
247
248static int smb_dirreset_r(struct _reent *, DIR_ITER * dirState)
249{
250 smb2_rewinddir(WutSmbDriver::getContext(), ((SmbDirState *)dirState->dirStruct)->dir);
251 return 0;
252}
253
254static int smb_dirnext_r(struct _reent *, DIR_ITER * dirState, char * filename, struct stat * filestat)
255{
256 smb2dir * dir = ((SmbDirState *)dirState->dirStruct)->dir;
257 smb2dirent * entry = smb2_readdir(WutSmbDriver::getContext(), dir);
258 if(!entry)
259 {
260 errno = ENOENT; // no more entries - the standard devoptab end-of-listing signal
261 return -1;
262 }
263
264 strncpy(filename, entry->name, NAME_MAX);
265 filename[NAME_MAX] = '\0';
266 FillStatFromSmb2(filestat, entry->st);
267 return 0;
268}
269
270static int smb_dirclose_r(struct _reent *, DIR_ITER * dirState)
271{
272 smb2_closedir(WutSmbDriver::getContext(), ((SmbDirState *)dirState->dirStruct)->dir);
273 return 0;
274}
275
276/****************************************************************************
277 * Devoptab registration
278 ***************************************************************************/
279static devoptab_t BuildSmbDevoptab()
280{
281 devoptab_t dotab = {};
282
283 dotab.name = kSmbDeviceName;
284 dotab.structSize = sizeof(SmbFileState);
285 dotab.open_r = smb_open_r;
286 dotab.close_r = smb_close_r;
287 dotab.write_r = smb_write_r;
288 dotab.read_r = smb_read_r;
289 dotab.seek_r = smb_seek_r;
290 dotab.fstat_r = smb_fstat_r;
291 dotab.stat_r = smb_stat_r;
292 dotab.unlink_r = smb_unlink_r;
293 dotab.mkdir_r = smb_mkdir_r;
294 dotab.rmdir_r = smb_rmdir_r;
295 dotab.dirStateSize = sizeof(SmbDirState);
296 dotab.diropen_r = smb_diropen_r;
297 dotab.dirreset_r = smb_dirreset_r;
298 dotab.dirnext_r = smb_dirnext_r;
299 dotab.dirclose_r = smb_dirclose_r;
300
301 return dotab;
302}
303
304/****************************************************************************
305 * WutSmbDriver
306 ***************************************************************************/
308{
309 // One-time nn::ac bring-up. Cheap and does not itself touch the network -
310 // it just readies the library so ACConnect() can be called later.
311 NNResult result = ACInitialize();
312 acInitialized = NNResult_IsSuccess(result);
313}
314
316{
317 disconnect();
318
319 // Mirror init(): only close/finalize what we actually brought up.
320 if(acConnected)
321 {
322 ACClose();
323 acConnected = false;
324 }
325
326 if(acInitialized)
327 {
328 ACFinalize();
329 acInitialized = false;
330}
331}
332
334{
335 if(!acInitialized)
336 return false;
337
338 BOOL isConnected = FALSE;
339 NNResult result = ACIsApplicationConnected(&isConnected);
340 return NNResult_IsSuccess(result) && isConnected;
341}
342
344{
345 if(!acInitialized)
346 {
347 snprintf(g_lastError, sizeof(g_lastError), "AC not initialized");
348 return false;
349 }
350
351 // Don't assume our own acConnected flag is the only way the network got
352 // up (or down) - ask AC directly every time, since eg. Wi-Fi can drop
353 // between connect() calls without this driver being told.
354 BOOL isConnected = FALSE;
355 NNResult result = ACIsApplicationConnected(&isConnected);
356 if(NNResult_IsSuccess(result) && isConnected)
357 return true;
358
359 result = ACConnect(); // blocking - may take a while on cold Wi-Fi association
360 if(NNResult_IsFailure(result))
361 {
362 snprintf(g_lastError, sizeof(g_lastError), "ACConnect failed (0x%08X)", (unsigned)result.value);
363 return false;
364 }
365
366 acConnected = true;
367 return true;
368}
369
371{
372 g_lastError[0] = '\0';
373
374 if(info.host[0] == '\0' || info.share[0] == '\0')
376
377 if(ctx && strcmp(current.host, info.host) == 0 && strcmp(current.share, info.share) == 0 && strcmp(current.user, info.user) == 0)
378 return SmbConnectResult::Success; // already connected to this exact target
379
380 disconnect(); // drop any existing connection to a *different* target first
381
382 if(!ensureNetworkUp())
384
385 ctx = smb2_init_context();
386 if(!ctx)
387 {
388 snprintf(g_lastError, sizeof(g_lastError), "smb2_init_context() failed");
390 }
391
392 smb2_set_security_mode(ctx, SMB2_NEGOTIATE_SIGNING_ENABLED);
393 if(info.user[0] != '\0')
394 smb2_set_user(ctx, info.user);
395 smb2_set_password(ctx, info.password);
396
397 if(smb2_connect_share(ctx, info.host, info.share, info.user[0] ? info.user : nullptr) != 0)
398 {
399 CaptureSmb2Error("connect_share");
400 smb2_destroy_context(ctx);
401 ctx = nullptr;
403 }
404
405 static devoptab_t smbDevoptab = BuildSmbDevoptab();
406 int devnum = AddDevice(&smbDevoptab);
407 if(devnum < 0)
408 {
409 // A real connection succeeded but the devoptab itself couldn't be registered
410 snprintf(g_lastError, sizeof(g_lastError), "AddDevice(\"%s\") failed", smbDevoptab.name);
411 smb2_disconnect_share(ctx);
412 smb2_destroy_context(ctx);
413 ctx = nullptr;
415 }
416 devoptabAdded = true;
417
418 current = info;
419 return SmbConnectResult::Success;
420}
421
423{
424 if(devoptabAdded)
425 {
426 RemoveDevice("smb:");
427 devoptabAdded = false;
428 }
429
430 if(ctx)
431 {
432 smb2_disconnect_share(ctx);
433 smb2_destroy_context(ctx);
434 ctx = nullptr;
435 }
436
437 memset(&current, 0, sizeof(current));
438}
439
441{
442 switch(result)
443 {
444 case SmbConnectResult::Success: return "Connected.";
445 case SmbConnectResult::InvalidSettings: return "Network share host/name is blank.";
446 case SmbConnectResult::NetworkUnavailable: return "Network is not available.";
447 case SmbConnectResult::ConnectFailed: return "Failed to connect to network share.";
448 default: return "Unknown network share error.";
449 }
450}
451
452const char * WutSmbDriver::getLastError() const
453{
454 return g_lastError;
455}
void disconnect() override
Unmounts and drops the connection.
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 init() override
One-time setup.
const char * getLastError() const override
Raw, implementation-specific detail behind the last failure.
bool isNetworkUp() const override
True if the network is already up, i.e.
bool ensureNetworkUp() override
Brings the Wii U network connection up if it isn't already, via nn::ac (ACConnect()).
void shutdown() override
Disconnects if connected and releases whatever init() set up.
bool isConnected() const override
static smb2_context * getContext()
The active context, used by the free-function devoptab callbacks in WutSmbDriver.cpp.
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