From 8b2e541b1d73d02f0ab5c648e9688cf60479d410 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Sat, 13 Sep 2025 13:16:18 +0100 Subject: [PATCH] lots of changes, see description. - enable sftp by default. - add more descriptive stdio errors. - disable devoptab timeout by default. - handle errors for devoptab seek. - add r/d/w progress events for threaded_file_transfer - remove system album from file browser, only show sd card. - do not clear game selection in games menu. useful for selecting games to backup, then delete after. - change smb2 r/w to only send max amount, matches nfs behaviour. not sure if its needed as smb probably handles it for us. --- sphaira/CMakeLists.txt | 2 +- sphaira/include/defines.hpp | 28 +++++++++ sphaira/include/utils/devoptab_common.hpp | 6 +- sphaira/source/fs.cpp | 41 +++++++------- sphaira/source/threaded_file_transfer.cpp | 55 +++++++++++------- sphaira/source/ui/error_box.cpp | 13 +++++ sphaira/source/ui/menus/filebrowser.cpp | 3 +- sphaira/source/ui/menus/game_menu.cpp | 1 - sphaira/source/utils/devoptab_bfsar.cpp | 4 +- sphaira/source/utils/devoptab_common.cpp | 69 +++++++++++++++++++---- sphaira/source/utils/devoptab_fatfs.cpp | 4 +- sphaira/source/utils/devoptab_ftp.cpp | 4 +- sphaira/source/utils/devoptab_http.cpp | 4 +- sphaira/source/utils/devoptab_nca.cpp | 10 ++-- sphaira/source/utils/devoptab_nfs.cpp | 28 +++------ sphaira/source/utils/devoptab_nro.cpp | 4 +- sphaira/source/utils/devoptab_nsp.cpp | 6 +- sphaira/source/utils/devoptab_save.cpp | 4 +- sphaira/source/utils/devoptab_sftp.cpp | 17 +++--- sphaira/source/utils/devoptab_smb2.cpp | 56 +++++++++++++----- sphaira/source/utils/devoptab_vfs.cpp | 4 +- sphaira/source/utils/devoptab_webdav.cpp | 5 +- sphaira/source/utils/devoptab_xci.cpp | 4 +- sphaira/source/utils/devoptab_zip.cpp | 4 +- 24 files changed, 254 insertions(+), 122 deletions(-) diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index b232977..0b3ea25 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -26,7 +26,7 @@ option(ENABLE_DEVOPTAB_WEBDAV "" ON) # max speed is 8MiB/s, which is fine for wifi, but awful for ethernet. # other clients get 36-40MiB/s. # it also adds 230k to binary size, and i don't think anyone will use it. -option(ENABLE_DEVOPTAB_SFTP "" OFF) +option(ENABLE_DEVOPTAB_SFTP "" ON) set(sphaira_VERSION 0.13.3) diff --git a/sphaira/include/defines.hpp b/sphaira/include/defines.hpp index 3b06e5b..a8ebe5f 100644 --- a/sphaira/include/defines.hpp +++ b/sphaira/include/defines.hpp @@ -511,7 +511,22 @@ enum class SphairaResult : Result { FsNewPathEmpty, FsLoadingCancelled, FsBrokenRoot, + FsUnknownStdioError, + FsStdioFailedToSeek, + FsStdioFailedToRead, + FsStdioFailedToWrite, + FsStdioFailedToOpenFile, + FsStdioFailedToCreate, + FsStdioFailedToTruncate, + FsStdioFailedToFlush, + FsStdioFailedToCreateDirectory, + FsStdioFailedToDeleteFile, + FsStdioFailedToDeleteDirectory, + FsStdioFailedToOpenDirectory, + FsStdioFailedToRename, + FsStdioFailedToStat, + FsReadOnly, FsNotActive, FsFailedStdioStat, @@ -680,6 +695,19 @@ enum : Result { MAKE_SPHAIRA_RESULT_ENUM(FsLoadingCancelled), MAKE_SPHAIRA_RESULT_ENUM(FsBrokenRoot), MAKE_SPHAIRA_RESULT_ENUM(FsUnknownStdioError), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToSeek), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRead), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToWrite), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenFile), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreate), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToTruncate), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToFlush), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreateDirectory), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteFile), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteDirectory), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenDirectory), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRename), + MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToStat), MAKE_SPHAIRA_RESULT_ENUM(FsReadOnly), MAKE_SPHAIRA_RESULT_ENUM(FsNotActive), MAKE_SPHAIRA_RESULT_ENUM(FsFailedStdioStat), diff --git a/sphaira/include/utils/devoptab_common.hpp b/sphaira/include/utils/devoptab_common.hpp index 71ab81f..c2f5730 100644 --- a/sphaira/include/utils/devoptab_common.hpp +++ b/sphaira/include/utils/devoptab_common.hpp @@ -91,6 +91,8 @@ bool fix_path(const char* str, char* out, bool strip_leading_slash = false); void update_devoptab_for_read_only(devoptab_t* devoptab, bool read_only); struct PushPullThreadData { + static constexpr size_t MAX_BUFFER_SIZE = 1024 * 64; // 64KB max buffer + PushPullThreadData(CURL* _curl); virtual ~PushPullThreadData(); Result CreateAndStart(); @@ -130,7 +132,7 @@ struct MountConfig { std::string pass{}; std::string dump_path{}; std::optional port{}; - int timeout{5000}; // 5 seconds. + long timeout{}; bool read_only{}; bool no_stat_file{true}; bool no_stat_dir{true}; @@ -164,7 +166,7 @@ struct MountDevice { virtual int devoptab_close(void *fd) { return -EIO; } virtual ssize_t devoptab_read(void *fd, char *ptr, size_t len) { return -EIO; } virtual ssize_t devoptab_write(void *fd, const char *ptr, size_t len) { return -EIO; } - virtual off_t devoptab_seek(void *fd, off_t pos, int dir) { return 0; } + virtual ssize_t devoptab_seek(void *fd, off_t pos, int dir) { return 0; } virtual int devoptab_fstat(void *fd, struct stat *st) { return -EIO; } virtual int devoptab_unlink(const char *path) { return -EIO; } virtual int devoptab_rename(const char *oldName, const char *newName) { return -EIO; } diff --git a/sphaira/source/fs.cpp b/sphaira/source/fs.cpp index f7182cb..41ef168 100644 --- a/sphaira/source/fs.cpp +++ b/sphaira/source/fs.cpp @@ -321,12 +321,12 @@ Result CreateFile(const FsPathReal& path, u64 size, u32 option, bool ignore_read } R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToCreate; } ON_SCOPE_EXIT(close(fd)); if (size) { - R_UNLESS(!ftruncate(fd, size), Result_FsUnknownStdioError); + R_UNLESS(!ftruncate(fd, size), Result_FsStdioFailedToTruncate); } R_SUCCEED(); @@ -341,7 +341,7 @@ Result CreateDirectory(const FsPathReal& path, bool ignore_read_only) { } R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToCreateDirectory; } R_SUCCEED(); } @@ -363,7 +363,7 @@ Result DeleteFile(const FsPathReal& path, bool ignore_read_only) { if (unlink(path)) { R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToDeleteFile; } R_SUCCEED(); } @@ -373,7 +373,7 @@ Result DeleteDirectory(const FsPathReal& path, bool ignore_read_only) { if (rmdir(path)) { R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToDeleteDirectory; } R_SUCCEED(); } @@ -405,7 +405,7 @@ Result RenameFile(const FsPathReal& src, const FsPathReal& dst, bool ignore_read if (rename(src, dst)) { R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToRename; } R_SUCCEED(); } @@ -421,7 +421,7 @@ Result GetEntryType(const FsPathReal& path, FsDirEntryType* out) { struct stat st; if (stat(path, &st)) { R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToStat; } *out = S_ISREG(st.st_mode) ? FsDirEntryType_File : FsDirEntryType_Dir; R_SUCCEED(); @@ -431,7 +431,7 @@ Result GetFileTimeStampRaw(const FsPathReal& path, FsTimeStampRaw *out) { struct stat st; if (stat(path, &st)) { R_TRY(fsdevGetLastResult()); - return Result_FsUnknownStdioError; + return Result_FsStdioFailedToStat; } out->is_valid = true; @@ -489,7 +489,7 @@ Result OpenFile(fs::Fs* fs, const FsPathReal& path, u32 mode, File* f) { f->m_stdio = std::fopen(path, "rb+"); } - R_UNLESS(f->m_stdio, Result_FsUnknownStdioError); + R_UNLESS(f->m_stdio, Result_FsStdioFailedToOpenFile); // disable buffering to match native fs behavior. // this also causes problems with network io as it will do double reads. @@ -515,9 +515,11 @@ Result File::Read( s64 off, void* buf, u64 read_size, u32 option, u64* bytes_rea } else { R_UNLESS(m_stdio, Result_FsUnknownStdioError); - if (std::ftell(m_stdio) != off) { + if (off != std::ftell(m_stdio)) { const auto ret = std::fseek(m_stdio, off, SEEK_SET); - R_UNLESS(ret == 0, Result_FsUnknownStdioError); + log_write("[FS] fseek to %ld ret: %d new_off: %zd\n", off, ret, std::ftell(m_stdio)); + R_UNLESS(ret == 0, Result_FsStdioFailedToSeek); + R_UNLESS(off == std::ftell(m_stdio), Result_FsStdioFailedToSeek); } *bytes_read = std::fread(buf, 1, read_size, m_stdio); @@ -526,7 +528,7 @@ Result File::Read( s64 off, void* buf, u64 read_size, u32 option, u64* bytes_rea if (*bytes_read < read_size) { if (!std::feof(m_stdio) && std::ferror(m_stdio)) { log_write("[FS] fread error: %d\n", std::ferror(m_stdio)); - R_THROW(Result_FsUnknownStdioError); + R_THROW(Result_FsStdioFailedToRead); } } } @@ -542,14 +544,15 @@ Result File::Write(s64 off, const void* buf, u64 write_size, u32 option) { } else { R_UNLESS(m_stdio, Result_FsUnknownStdioError); - if (std::ftell(m_stdio) != off) { + if (off != std::ftell(m_stdio)) { const auto ret = std::fseek(m_stdio, off, SEEK_SET); - R_UNLESS(ret == 0, Result_FsUnknownStdioError); + R_UNLESS(ret == 0, Result_FsStdioFailedToSeek); + R_UNLESS(off == std::ftell(m_stdio), Result_FsStdioFailedToSeek); } const auto result = std::fwrite(buf, 1, write_size, m_stdio); // log_write("[FS] fwrite res: %zu vs %zu\n", result, write_size); - R_UNLESS(result == write_size, Result_FsUnknownStdioError); + R_UNLESS(result == write_size, Result_FsStdioFailedToWrite); } R_SUCCEED(); @@ -563,8 +566,8 @@ Result File::SetSize(s64 sz) { } else { R_UNLESS(m_stdio, Result_FsUnknownStdioError); const auto fd = fileno(m_stdio); - R_UNLESS(fd > 0, Result_FsUnknownStdioError); - R_UNLESS(!ftruncate(fd, sz), Result_FsUnknownStdioError); + R_UNLESS(fd > 0, Result_FsStdioFailedToTruncate); + R_UNLESS(!ftruncate(fd, sz), Result_FsStdioFailedToTruncate); } R_SUCCEED(); @@ -579,7 +582,7 @@ Result File::GetSize(s64* out) { R_UNLESS(m_stdio, Result_FsUnknownStdioError); struct stat st; - R_UNLESS(!fstat(fileno(m_stdio), &st), Result_FsUnknownStdioError); + R_UNLESS(!fstat(fileno(m_stdio), &st), Result_FsStdioFailedToStat); *out = st.st_size; } @@ -617,7 +620,7 @@ Result OpenDirectory(fs::Fs* fs, const FsPathReal& path, u32 mode, Dir* d) { R_TRY(fsFsOpenDirectory(&fs->m_fs, path, mode, &d->m_native)); } else { d->m_stdio = opendir(path); - R_UNLESS(d->m_stdio, Result_FsUnknownStdioError); + R_UNLESS(d->m_stdio, Result_FsStdioFailedToOpenDirectory); } R_SUCCEED(); diff --git a/sphaira/source/threaded_file_transfer.cpp b/sphaira/source/threaded_file_transfer.cpp index f2967c0..b865aab 100644 --- a/sphaira/source/threaded_file_transfer.cpp +++ b/sphaira/source/threaded_file_transfer.cpp @@ -82,6 +82,14 @@ struct ThreadData { return read_running || decompress_running || write_running; } + auto GetReadOffset() volatile const -> s64 { + return read_offset; + } + + auto GetDecompressOffset() volatile const -> s64 { + return decompress_offset; + } + auto GetWriteOffset() volatile const -> s64 { return write_offset; } @@ -94,8 +102,16 @@ struct ThreadData { return &m_uevent_done; } - auto GetProgressEvent() { - return &m_uevent_progres; + auto GetReadProgressEvent() { + return &m_uevent_read_progress; + } + + auto GetDecompressProgressEvent() { + return &m_uevent_decompress_progress; + } + + auto GetWriteProgressEvent() { + return &m_uevent_write_progress; } void SetReadResult(Result result) { @@ -174,7 +190,9 @@ private: CondVar can_pull_write{}; UEvent m_uevent_done{}; - UEvent m_uevent_progres{}; + UEvent m_uevent_read_progress{}; + UEvent m_uevent_decompress_progress{}; + UEvent m_uevent_write_progress{}; RingBuf<2> read_buffers{}; RingBuf<2> write_buffers{}; @@ -219,8 +237,10 @@ ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, const ReadCallback& _rf condvarInit(std::addressof(can_pull)); condvarInit(std::addressof(can_pull_write)); - ueventCreate(&m_uevent_done, false); - ueventCreate(&m_uevent_progres, true); + ueventCreate(GetDoneEvent(), false); + ueventCreate(GetReadProgressEvent(), true); + ueventCreate(GetDecompressProgressEvent(), true); + ueventCreate(GetWriteProgressEvent(), true); } auto ThreadData::GetResults() volatile -> Result { @@ -379,6 +399,7 @@ Result ThreadData::readFuncInternal() { break; } + ueventSignal(GetReadProgressEvent()); auto buf_size = bytes_read; R_TRY(this->SetDecompressBuf(buf, buffer_offset, buf_size)); } @@ -423,25 +444,17 @@ Result ThreadData::decompressFuncInternal() { } size -= rsize; - this->decompress_offset += rsize; data += rsize; - - // const auto buf_off = temp_buf.size(); - // temp_buf.resize(buf_off + size); - // std::memcpy(temp_buf.data() + buf_off, data, size); - // this->decompress_offset += size; - - // if (temp_buf.size() >= temp_buf_flush_max) { - // // log_write("flushing data: %zu %.2f MiB\n", temp_buf.size(), temp_buf.size() / 1024.0 / 1024.0); - // R_TRY(this->SetWriteBuf(temp_buf, temp_buf.size())); - // temp_buf.resize(0); - // } + this->decompress_offset += rsize; + ueventSignal(GetDecompressProgressEvent()); } R_SUCCEED(); })); } else { this->decompress_offset += buf.size(); + ueventSignal(GetDecompressProgressEvent()); + R_TRY(this->SetWriteBuf(buf, buf.size())); } } @@ -479,7 +492,7 @@ Result ThreadData::writeFuncInternal() { } this->write_offset += size; - ueventSignal(GetProgressEvent()); + ueventSignal(GetWriteProgressEvent()); } log_write("finished write thread success!\n"); @@ -586,7 +599,11 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfu R_TRY(start_threads()); log_write("[THREAD] started threads\n"); - const auto waiter_progress = waiterForUEvent(t_data.GetProgressEvent()); + // use the read progress as the write output may be smaller due to compressing + // so read will show a more accurate progress. + // TODO: show progress bar for all 3 threads. + // NOTE: went back to using write progress for now. + const auto waiter_progress = waiterForUEvent(t_data.GetWriteProgressEvent()); const auto waiter_cancel = waiterForUEvent(pbox->GetCancelEvent()); const auto waiter_done = waiterForUEvent(t_data.GetDoneEvent()); diff --git a/sphaira/source/ui/error_box.cpp b/sphaira/source/ui/error_box.cpp index 5fed2fa..8d85a79 100644 --- a/sphaira/source/ui/error_box.cpp +++ b/sphaira/source/ui/error_box.cpp @@ -51,6 +51,19 @@ auto GetCodeMessage(Result rc) -> const char* { case Result_FsLoadingCancelled: return "SphairaError_FsLoadingCancelled"; case Result_FsBrokenRoot: return "SphairaError_FsBrokenRoot"; case Result_FsUnknownStdioError: return "SphairaError_FsUnknownStdioError"; + case Result_FsStdioFailedToSeek: return "SphairaError_FsStdioFailedToSeek"; + case Result_FsStdioFailedToRead: return "SphairaError_FsStdioFailedToRead"; + case Result_FsStdioFailedToWrite: return "SphairaError_FsStdioFailedToWrite"; + case Result_FsStdioFailedToOpenFile: return "SphairaError_FsStdioFailedToOpenFile"; + case Result_FsStdioFailedToCreate: return "SphairaError_FsStdioFailedToCreate"; + case Result_FsStdioFailedToTruncate: return "SphairaError_FsStdioFailedToTruncate"; + case Result_FsStdioFailedToFlush: return "SphairaError_FsStdioFailedToFlush"; + case Result_FsStdioFailedToCreateDirectory: return "SphairaError_FsStdioFailedToCreateDirectory"; + case Result_FsStdioFailedToDeleteFile: return "SphairaError_FsStdioFailedToDeleteFile"; + case Result_FsStdioFailedToDeleteDirectory: return "SphairaError_FsStdioFailedToDeleteDirectory"; + case Result_FsStdioFailedToOpenDirectory: return "SphairaError_FsStdioFailedToOpenDirectory"; + case Result_FsStdioFailedToRename: return "SphairaError_FsStdioFailedToRename"; + case Result_FsStdioFailedToStat: return "SphairaError_FsStdioFailedToStat"; case Result_FsReadOnly: return "SphairaError_FsReadOnly"; case Result_FsNotActive: return "SphairaError_FsNotActive"; case Result_FsFailedStdioStat: return "SphairaError_FsFailedStdioStat"; diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index bda65cd..4846eb4 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -81,8 +81,7 @@ constexpr FsEntry FS_ENTRY_DEFAULT{ constexpr FsEntry FS_ENTRIES[]{ FS_ENTRY_DEFAULT, - { "Image System memory", "/", FsType::ImageNand }, - { "Image microSD card", "/", FsType::ImageSd}, + { "Album", "/", FsType::ImageSd}, }; constexpr std::string_view AUDIO_EXTENSIONS[] = { diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index 5113196..5d78875 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -706,7 +706,6 @@ void Menu::ExportOptions(bool to_nsz) { void Menu::DumpGames(u32 flags, bool to_nsz) { auto targets = GetSelectedEntries(); - ClearSelection(); std::vector nsp_entries; for (auto& e : targets) { diff --git a/sphaira/source/utils/devoptab_bfsar.cpp b/sphaira/source/utils/devoptab_bfsar.cpp index bec740e..b2b191d 100644 --- a/sphaira/source/utils/devoptab_bfsar.cpp +++ b/sphaira/source/utils/devoptab_bfsar.cpp @@ -88,7 +88,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -134,7 +134,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto& info = file->info; diff --git a/sphaira/source/utils/devoptab_common.cpp b/sphaira/source/utils/devoptab_common.cpp index b42a760..919af73 100644 --- a/sphaira/source/utils/devoptab_common.cpp +++ b/sphaira/source/utils/devoptab_common.cpp @@ -171,9 +171,14 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { SCOPED_RWLOCK(&g_rwlock, false); SCOPED_MUTEX(&file->device->mutex); - const auto off = file->device->mount_device->devoptab_seek(file->fd, pos, dir); + const auto ret = file->device->mount_device->devoptab_seek(file->fd, pos, dir); + if (ret < 0) { + set_errno(r, -ret); + return 0; + } + r->_errno = 0; - return off; + return ret; } int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { @@ -960,11 +965,17 @@ PushPullThreadData::PushPullThreadData(CURL* _curl) : curl{_curl} { PushPullThreadData::~PushPullThreadData() { Cancel(); - threadWaitForExit(&thread); + + if (started) { + threadWaitForExit(&thread); + } + threadClose(&thread); } Result PushPullThreadData::CreateAndStart() { + SCOPED_MUTEX(&mutex); + if (started) { R_SUCCEED(); } @@ -989,6 +1000,10 @@ bool PushPullThreadData::IsRunning() { } size_t PushPullThreadData::PullData(char* data, size_t total_size) { + if (!data || !total_size) { + return 0; + } + SCOPED_MUTEX(&mutex); ON_SCOPE_EXIT(condvarWakeOne(&can_push)); @@ -1014,12 +1029,16 @@ size_t PushPullThreadData::PullData(char* data, size_t total_size) { } size_t PushPullThreadData::PushData(const char* data, size_t total_size) { + if (!data || !total_size) { + return 0; + } + SCOPED_MUTEX(&mutex); ON_SCOPE_EXIT(condvarWakeOne(&can_pull)); size_t bytes_written = 0; while (bytes_written < total_size && !error && !finished) { - const size_t space_left = (1024 * 64) - buffer.size(); // 64K max buffer + const size_t space_left = MAX_BUFFER_SIZE - buffer.size(); if (space_left == 0) { condvarWakeOne(&can_pull); condvarWait(&can_push, &mutex); @@ -1035,11 +1054,19 @@ size_t PushPullThreadData::PushData(const char* data, size_t total_size) { } size_t PushPullThreadData::push_thread_callback(const char *ptr, size_t size, size_t nmemb, void *userdata) { + if (!ptr || !userdata || !size || !nmemb) { + return 0; + } + auto* data = static_cast(userdata); return data->PushData(ptr, size * nmemb); } size_t PushPullThreadData::pull_thread_callback(char *ptr, size_t size, size_t nmemb, void *userdata) { + if (!ptr || !userdata || !size || !nmemb) { + return 0; + } + auto* data = static_cast(userdata); return data->PullData(ptr, size * nmemb); } @@ -1067,10 +1094,29 @@ PullThreadData::~PullThreadData() { if (started) { SCOPED_MUTEX(&mutex); + // for now, always wait until the dat is flushed. + // may enable a timeout later on, however i don't want to risk + // data loss for users that have slow hdd / connections. + #if 1 while (!finished && !error && !buffer.empty()) { condvarWakeOne(&can_pull); - condvarWaitTimeout(&can_push, &mutex, 5e+9); + condvarWait(&can_push, &mutex); } + #else + u64 timeout = 5e+9; + const auto deadline = armGetSystemTick() + armNsToTicks(timeout); + + while (!finished && !error && !buffer.empty()) { + const s64 remaining = deadline - armGetSystemTick(); + timeout = remaining > 0 ? armTicksToNs(remaining) : 0; + + condvarWakeOne(&can_pull); + if (R_FAILED(condvarWaitTimeout(&can_push, &mutex, timeout))) { + log_write("[PullThreadData] condvarWaitTimeout() timed out flushing data: %zu\n", buffer.size()); + break; + } + } + #endif } } @@ -1276,11 +1322,6 @@ void MountCurlDevice::curl_set_common_options(CURL* curl, const std::string& url // NOTE: port, user and pass are set in the curl_url. curl_easy_reset(curl); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - // cancel if speed is less than 1 bytes/sec for timeout seconds. - curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L); - // todo: change config to accept seconds rather than ms. - curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, config.timeout / 1000L); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config.timeout); curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 15L); @@ -1291,6 +1332,14 @@ void MountCurlDevice::curl_set_common_options(CURL* curl, const std::string& url curl_easy_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, 1024L * 64L); curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); + if (config.timeout > 0) { + // cancel if speed is less than 1 bytes/sec for timeout seconds. + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L); + // todo: change config to accept seconds rather than ms. + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, config.timeout / 1000L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config.timeout); + } + if (m_curl_share) { curl_easy_setopt(curl, CURLOPT_SHARE, m_curl_share); } diff --git a/sphaira/source/utils/devoptab_fatfs.cpp b/sphaira/source/utils/devoptab_fatfs.cpp index 24e0450..4c05d34 100644 --- a/sphaira/source/utils/devoptab_fatfs.cpp +++ b/sphaira/source/utils/devoptab_fatfs.cpp @@ -97,7 +97,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -331,7 +331,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return total_bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto size = get_size_from_files(file); diff --git a/sphaira/source/utils/devoptab_ftp.cpp b/sphaira/source/utils/devoptab_ftp.cpp index 5a68c41..0ae8c7e 100644 --- a/sphaira/source/utils/devoptab_ftp.cpp +++ b/sphaira/source/utils/devoptab_ftp.cpp @@ -38,7 +38,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -546,7 +546,7 @@ ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { return ret; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); if (dir == SEEK_CUR) { diff --git a/sphaira/source/utils/devoptab_http.cpp b/sphaira/source/utils/devoptab_http.cpp index bb86095..f1b13f1 100644 --- a/sphaira/source/utils/devoptab_http.cpp +++ b/sphaira/source/utils/devoptab_http.cpp @@ -51,7 +51,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -318,7 +318,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return ret; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); if (dir == SEEK_CUR) { diff --git a/sphaira/source/utils/devoptab_nca.cpp b/sphaira/source/utils/devoptab_nca.cpp index 5d1f87d..8761fd3 100644 --- a/sphaira/source/utils/devoptab_nca.cpp +++ b/sphaira/source/utils/devoptab_nca.cpp @@ -159,7 +159,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -207,7 +207,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto& entry = file->entry; @@ -343,9 +343,11 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr& s // check if this is a ncz. ncz::Header ncz_header{}; - R_TRY(source->Read2(&ncz_header, NCZ_NORMAL_SIZE, sizeof(ncz_header))); + if (size >= NCZ_NORMAL_SIZE) { + R_TRY(source->Read2(&ncz_header, NCZ_NORMAL_SIZE, sizeof(ncz_header))); + } - if (ncz_header.magic == NCZ_SECTION_MAGIC) { + if (size >= NCZ_NORMAL_SIZE && ncz_header.magic == NCZ_SECTION_MAGIC) { // read all the sections. s64 ncz_offset = NCZ_SECTION_OFFSET; ncz::Sections ncz_sections(ncz_header.total_sections); diff --git a/sphaira/source/utils/devoptab_nfs.cpp b/sphaira/source/utils/devoptab_nfs.cpp index 6164430..f58e4b2 100644 --- a/sphaira/source/utils/devoptab_nfs.cpp +++ b/sphaira/source/utils/devoptab_nfs.cpp @@ -1,5 +1,3 @@ -#ifdef ENABLE_DEVOPTAB_NFS - #include "utils/devoptab_common.hpp" #include "defines.hpp" #include "log.hpp" @@ -25,7 +23,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -111,8 +109,10 @@ bool Device::Mount() { } } - nfs_set_timeout(nfs, this->config.timeout); - nfs_set_readonly(nfs, this->config.read_only); + if (this->config.timeout > 0) { + nfs_set_timeout(nfs, this->config.timeout); + nfs_set_readonly(nfs, this->config.read_only); + } // nfs_set_mountport(nfs, url->port); } @@ -123,7 +123,7 @@ bool Device::Mount() { url = "nfs://" + url; } - auto nfs_url = nfs_parse_url_dir(nfs, url.c_str()); + auto nfs_url = nfs_parse_url_full(nfs, url.c_str()); if (!nfs_url) { log_write("[NFS] nfs_parse_url() failed for url: %s\n", url.c_str()); return false; @@ -225,7 +225,7 @@ ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { return written; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); u64 current_offset = 0; @@ -414,17 +414,3 @@ Result MountNfsAll() { } } // namespace sphaira::devoptab - -#else // ENABLE_DEVOPTAB_NFS - -#include "defines.hpp" - -namespace sphaira::devoptab { - -Result MountNfsAll() { - R_SUCCEED(); -} - -} // namespace sphaira::devoptab - -#endif // ENABLE_DEVOPTAB_NFS diff --git a/sphaira/source/utils/devoptab_nro.cpp b/sphaira/source/utils/devoptab_nro.cpp index 0676a31..3853682 100644 --- a/sphaira/source/utils/devoptab_nro.cpp +++ b/sphaira/source/utils/devoptab_nro.cpp @@ -118,7 +118,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -166,7 +166,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto& entry = file->entry; diff --git a/sphaira/source/utils/devoptab_nsp.cpp b/sphaira/source/utils/devoptab_nsp.cpp index 21f4708..88edb94 100644 --- a/sphaira/source/utils/devoptab_nsp.cpp +++ b/sphaira/source/utils/devoptab_nsp.cpp @@ -41,7 +41,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -90,7 +90,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto& collection = file->collection; @@ -171,7 +171,7 @@ int Device::devoptab_lstat(const char *path, struct stat *st) { st->st_size = it->size; } - return -ENOENT; + return 0; } } // namespace diff --git a/sphaira/source/utils/devoptab_save.cpp b/sphaira/source/utils/devoptab_save.cpp index e84181b..62b42d7 100644 --- a/sphaira/source/utils/devoptab_save.cpp +++ b/sphaira/source/utils/devoptab_save.cpp @@ -49,7 +49,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -101,7 +101,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); if (dir == SEEK_CUR) { diff --git a/sphaira/source/utils/devoptab_sftp.cpp b/sphaira/source/utils/devoptab_sftp.cpp index b701c05..56ca385 100644 --- a/sphaira/source/utils/devoptab_sftp.cpp +++ b/sphaira/source/utils/devoptab_sftp.cpp @@ -40,7 +40,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -247,11 +247,14 @@ bool Device::Mount() { libssh2_session_set_blocking(m_session, 1); libssh2_session_flag(m_session, LIBSSH2_FLAG_COMPRESS, 1); - libssh2_session_set_timeout(m_session, this->config.timeout); - // dkp libssh2 is too old for this. -#if LIBSSH2_VERSION_NUM >= 0x010B00 - libssh2_session_set_read_timeout(m_session, this->config.timeout); -#endif + + if (this->config.timeout > 0) { + libssh2_session_set_timeout(m_session, this->config.timeout); + // dkp libssh2 is too old for this. + #if LIBSSH2_VERSION_NUM >= 0x010B00 + libssh2_session_set_read_timeout(m_session, this->config.timeout); + #endif + } } if (this->config.user.empty() || this->config.pass.empty()) { @@ -353,7 +356,7 @@ ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { return ret; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto current_pos = libssh2_sftp_tell64(file->fd); diff --git a/sphaira/source/utils/devoptab_smb2.cpp b/sphaira/source/utils/devoptab_smb2.cpp index 6490582..7a06b0b 100644 --- a/sphaira/source/utils/devoptab_smb2.cpp +++ b/sphaira/source/utils/devoptab_smb2.cpp @@ -28,7 +28,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -118,7 +118,9 @@ bool Device::Mount() { smb2_set_workstation(this->smb2, workstation->second.c_str()); } - smb2_set_timeout(this->smb2, this->config.timeout); + if (config.timeout > 0) { + smb2_set_timeout(this->smb2, this->config.timeout); + } } auto smb2_url = smb2_parse_url(this->smb2, this->config.url.c_str()); @@ -160,28 +162,56 @@ int Device::devoptab_close(void *fd) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { auto file = static_cast(fd); - const auto ret = smb2_read(this->smb2, file->fd, reinterpret_cast(ptr), len); - if (ret < 0) { - log_write("[SMB2] smb2_read() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret)); - return ret; + const auto max_read = smb2_get_max_read_size(this->smb2); + size_t bytes_read = 0; + + while (bytes_read < len) { + const auto to_read = std::min(len - bytes_read, max_read); + const auto ret = smb2_read(this->smb2, file->fd, (u8*)ptr, to_read); + + if (ret < 0) { + log_write("[SMB2] smb2_read() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret)); + return ret; + } + + ptr += ret; + bytes_read += ret; + + if (ret < to_read) { + break; + } } - return ret; + return bytes_read; } ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { auto file = static_cast(fd); - const auto ret = smb2_write(this->smb2, file->fd, reinterpret_cast(ptr), len); - if (ret < 0) { - log_write("[SMB2] smb2_write() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret)); - return ret; + const auto max_write = smb2_get_max_write_size(this->smb2); + size_t written = 0; + + while (written < len) { + const auto to_write = std::min(len - written, max_write); + const auto ret = smb2_write(this->smb2, file->fd, (const u8*)ptr, to_write); + + if (ret < 0) { + log_write("[SMB2] smb2_write() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret)); + return ret; + } + + ptr += ret; + written += ret; + + if (ret < to_write) { + break; + } } - return ret; + return written; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); u64 current_offset = 0; diff --git a/sphaira/source/utils/devoptab_vfs.cpp b/sphaira/source/utils/devoptab_vfs.cpp index 8beee0a..8be588e 100644 --- a/sphaira/source/utils/devoptab_vfs.cpp +++ b/sphaira/source/utils/devoptab_vfs.cpp @@ -34,7 +34,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -124,7 +124,7 @@ ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { return ret; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); return lseek(file->fd, pos, dir); diff --git a/sphaira/source/utils/devoptab_webdav.cpp b/sphaira/source/utils/devoptab_webdav.cpp index b6f9931..382ceea 100644 --- a/sphaira/source/utils/devoptab_webdav.cpp +++ b/sphaira/source/utils/devoptab_webdav.cpp @@ -58,7 +58,7 @@ private: int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_unlink(const char *path) override; int devoptab_rename(const char *oldName, const char *newName) override; @@ -188,6 +188,7 @@ int Device::webdav_dirlist(const std::string& path, DirEntries& out) { continue; } + // todo: fix requested path still being displayed. const auto href = url_decode(href_x.node().text().as_string()); if (href.empty() || href == requested_path || href == requested_path + '/') { continue; @@ -483,7 +484,7 @@ ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) { return ret; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); if (dir == SEEK_CUR) { diff --git a/sphaira/source/utils/devoptab_xci.cpp b/sphaira/source/utils/devoptab_xci.cpp index a05ba00..b65cb94 100644 --- a/sphaira/source/utils/devoptab_xci.cpp +++ b/sphaira/source/utils/devoptab_xci.cpp @@ -39,7 +39,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -90,7 +90,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return bytes_read; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); const auto& collection = file->collection; diff --git a/sphaira/source/utils/devoptab_zip.cpp b/sphaira/source/utils/devoptab_zip.cpp index 2c6cf95..8e18b0b 100644 --- a/sphaira/source/utils/devoptab_zip.cpp +++ b/sphaira/source/utils/devoptab_zip.cpp @@ -199,7 +199,7 @@ private: int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override; int devoptab_close(void *fd) override; ssize_t devoptab_read(void *fd, char *ptr, size_t len) override; - off_t devoptab_seek(void *fd, off_t pos, int dir) override; + ssize_t devoptab_seek(void *fd, off_t pos, int dir) override; int devoptab_fstat(void *fd, struct stat *st) override; int devoptab_diropen(void* fd, const char *path) override; int devoptab_dirreset(void* fd) override; @@ -338,7 +338,7 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) { return len; } -off_t Device::devoptab_seek(void *fd, off_t pos, int dir) { +ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) { auto file = static_cast(fd); // seek like normal.