// Copyright (c) 2026 Pyxis contributors // SPDX-License-Identifier: MIT #include "NomadNetStorageSD.h" #ifdef ARDUINO #include #include #include #include #include #include #include #include namespace Hardware { namespace TDeck { using UI::LXMF::NomadNet::StorageResult; namespace { StorageResult ioResult(){return errno==ENOSPC?StorageResult::FULL:StorageResult::IO_ERROR;} } NomadNetStorageSD::NomadNetStorageSD()=default; NomadNetStorageSD::~NomadNetStorageSD(){abortWrite();endRead();endList();abortWrite();} bool NomadNetStorageSD::cardPresentLocked(){return SD.cardType()!=CARD_NONE;} bool NomadNetStorageSD::mountedPath(const char*p,char*out,std::size_t cap){if(!p||p[0]!='/'||std::strstr(p,".."))return false;const int n=std::snprintf(out,cap,"/sd%s",p);return n>0&&static_cast(n)=sizeof(part))return false;std::memcpy(part,p,n+1);for(std::size_t i=1;i=0)abort_pending_=true;writing_=false;} StorageResult NomadNetStorageSD::serviceAbortLocked(){if(!abort_pending_)return StorageResult::OK;const bool ok=write_fd_<0||::close(write_fd_)==0;write_fd_=-1;abort_pending_=false;writing_=false;if(!ok)healthy_=false;return ok?StorageResult::OK:StorageResult::IO_ERROR;} bool NomadNetStorageSD::isAvailable()const{if(!healthy_||!SDAccess::is_ready()||!SDAccess::acquire_bus(100))return false;auto*self=const_cast(this);const bool ok=self->serviceAbortLocked()==StorageResult::OK&&cardPresentLocked();SDAccess::release_bus();return ok;} StorageResult NomadNetStorageSD::beginRead(const char*p,std::uint32_t&s){if(!healthy_||!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;auto cleanup=serviceAbortLocked();if(cleanup!=StorageResult::OK){SDAccess::release_bus();return cleanup;}if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}char mp[196];if(!mountedPath(p,mp,sizeof(mp))){SDAccess::release_bus();return StorageResult::INVALID_ARGUMENT;}struct stat st{};errno=0;if(::stat(mp,&st)!=0){auto r=errno==ENOENT?StorageResult::MISS:ioResult();SDAccess::release_bus();return r;}if(st.st_size<0||static_cast(st.st_size)>UINT32_MAX){SDAccess::release_bus();return StorageResult::TOO_LARGE;}read_=SD.open(p,FILE_READ);if(!read_){SDAccess::release_bus();return StorageResult::IO_ERROR;}s=st.st_size;SDAccess::release_bus();return StorageResult::OK;} StorageResult NomadNetStorageSD::readChunk(std::uint8_t*out,std::size_t cap,std::size_t&n){n=0;if(!healthy_)return StorageResult::IO_ERROR;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;if(!cardPresentLocked()){read_.close();SDAccess::release_bus();return StorageResult::UNAVAILABLE;}if(!read_){SDAccess::release_bus();return StorageResult::INVALID_STATE;}n=read_.read(out,cap);const bool failed=n==0&&read_.available();SDAccess::release_bus();return failed?StorageResult::NO_PROGRESS:StorageResult::OK;} StorageResult NomadNetStorageSD::endRead(){if(!read_)return StorageResult::OK;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;read_.close();SDAccess::release_bus();return StorageResult::OK;} StorageResult NomadNetStorageSD::beginWrite(const char*p){if(!healthy_||!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;auto cleanup=serviceAbortLocked();if(cleanup!=StorageResult::OK){SDAccess::release_bus();return cleanup;}if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}if(!parentsLocked(p)){SDAccess::release_bus();return StorageResult::IO_ERROR;}char mp[196];if(!mountedPath(p,mp,sizeof(mp))){SDAccess::release_bus();return StorageResult::INVALID_ARGUMENT;}errno=0;write_fd_=::open(mp,O_WRONLY|O_CREAT|O_TRUNC,0666);writing_=write_fd_>=0;auto r=writing_?StorageResult::OK:ioResult();SDAccess::release_bus();return r;} StorageResult NomadNetStorageSD::writeChunk(const std::uint8_t*d,std::size_t z,std::size_t&n){n=0;if(!healthy_)return StorageResult::IO_ERROR;if(!SDAccess::acquire_bus(100)){poisonWrite();return StorageResult::BUSY;}if(!cardPresentLocked()){poisonWrite();SDAccess::release_bus();return StorageResult::UNAVAILABLE;}if(!writing_||write_fd_<0){SDAccess::release_bus();return StorageResult::INVALID_STATE;}errno=0;const auto w=::write(write_fd_,d,z);n=w<0?0:static_cast(w);auto r=w<0?ioResult():n==0?StorageResult::NO_PROGRESS:n(st.st_size)>UINT32_MAX){SDAccess::release_bus();return StorageResult::TOO_LARGE;}s=st.st_size;SDAccess::release_bus();return StorageResult::OK;} StorageResult NomadNetStorageSD::beginList(const char*p){auto prior=endList();if(prior!=StorageResult::OK)return prior;if(!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}char mp[196];if(!mountedPath(p,mp,sizeof(mp))||std::strlen(p)>=sizeof(list_base_)){SDAccess::release_bus();return StorageResult::INVALID_ARGUMENT;}struct stat st{};errno=0;if(::stat(mp,&st)!=0){const auto r=errno==ENOENT?StorageResult::OK:ioResult();SDAccess::release_bus();return r;}if(!S_ISDIR(st.st_mode)){SDAccess::release_bus();return StorageResult::INVALID_STATE;}errno=0;list_dir_=::opendir(mp);if(!list_dir_){const auto r=ioResult();SDAccess::release_bus();return r;}std::strcpy(list_base_,p);SDAccess::release_bus();return StorageResult::OK;} StorageResult NomadNetStorageSD::nextList(char*out,std::size_t cap,bool&done){done=false;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}if(!list_dir_){done=true;SDAccess::release_bus();return StorageResult::OK;}struct dirent*entry=nullptr;do{errno=0;entry=::readdir(list_dir_);if(!entry){const auto saved=errno;done=saved==0;SDAccess::release_bus();errno=saved;return done?StorageResult::OK:ioResult();}}while(std::strcmp(entry->d_name,".")==0||std::strcmp(entry->d_name,"..")==0);const int n=std::snprintf(out,cap,"%s/%s",list_base_,entry->d_name);SDAccess::release_bus();return n>0&&static_cast(n)