mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-27 09:58:28 +00:00
39 lines
9.5 KiB
C++
39 lines
9.5 KiB
C++
// Copyright (c) 2026 Pyxis contributors
|
|
// SPDX-License-Identifier: MIT
|
|
#include "NomadNetStorageSD.h"
|
|
#ifdef ARDUINO
|
|
#include <SD.h>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
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<std::size_t>(n)<cap;}
|
|
bool NomadNetStorageSD::parentsLocked(const char*p){char part[192]={};const auto n=std::strlen(p);if(n>=sizeof(part))return false;std::memcpy(part,p,n+1);for(std::size_t i=1;i<n;++i)if(part[i]=='/'){part[i]=0;if(!SD.exists(part)&&!SD.mkdir(part))return false;part[i]='/';}return true;}
|
|
void NomadNetStorageSD::poisonWrite(){if(write_fd_>=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<NomadNetStorageSD*>(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<std::uint64_t>(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<std::size_t>(w);auto r=w<0?ioResult():n==0?StorageResult::NO_PROGRESS:n<z?StorageResult::PARTIAL_WRITE:StorageResult::OK;if(r!=StorageResult::OK)poisonWrite();SDAccess::release_bus();return r;}
|
|
StorageResult NomadNetStorageSD::commitWrite(){if(!healthy_)return StorageResult::IO_ERROR;if(!SDAccess::acquire_bus(100)){poisonWrite();return StorageResult::BUSY;}if(abort_pending_||!writing_||write_fd_<0){serviceAbortLocked();SDAccess::release_bus();return StorageResult::INVALID_STATE;}if(!cardPresentLocked()){poisonWrite();serviceAbortLocked();SDAccess::release_bus();return StorageResult::UNAVAILABLE;}errno=0;const bool synced=::fsync(write_fd_)==0;const int sync_error=errno;const bool closed=::close(write_fd_)==0;write_fd_=-1;writing_=false;errno=sync_error;auto r=synced&&closed?StorageResult::OK:ioResult();SDAccess::release_bus();return r;}
|
|
StorageResult NomadNetStorageSD::abortWrite(){if(write_fd_<0&&!abort_pending_)return StorageResult::OK;if(!SDAccess::acquire_bus(100)){poisonWrite();return StorageResult::BUSY;}abort_pending_=true;auto r=serviceAbortLocked();SDAccess::release_bus();return r;}
|
|
StorageResult NomadNetStorageSD::remove(const char*p){if(!healthy_)return StorageResult::IO_ERROR;if(!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;auto c=serviceAbortLocked();if(c!=StorageResult::OK){SDAccess::release_bus();return c;}if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}char mp[196];if(!mountedPath(p,mp,sizeof(mp))){SDAccess::release_bus();return StorageResult::INVALID_ARGUMENT;}errno=0;const bool ok=::unlink(mp)==0;const int saved_errno=errno;SDAccess::release_bus();if(ok)return StorageResult::OK;errno=saved_errno;return errno==ENOENT?StorageResult::MISS:ioResult();}
|
|
StorageResult NomadNetStorageSD::rename(const char*a,const char*b){if(!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;auto c=serviceAbortLocked();if(c!=StorageResult::OK){SDAccess::release_bus();return c;}if(!cardPresentLocked()){SDAccess::release_bus();return StorageResult::UNAVAILABLE;}if(!SD.exists(a)){SDAccess::release_bus();return StorageResult::MISS;}/* Replacement policy belongs to the generation-aware cache. A generic rename must never destroy its destination before publication succeeds. */if(SD.exists(b)){SDAccess::release_bus();return StorageResult::INVALID_STATE;}const bool ok=SD.rename(a,b);SDAccess::release_bus();return ok?StorageResult::OK:StorageResult::IO_ERROR;}
|
|
StorageResult NomadNetStorageSD::stat(const char*p,std::uint32_t&s){if(!SDAccess::is_ready())return StorageResult::UNAVAILABLE;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;char mp[196];if(!mountedPath(p,mp,sizeof(mp))){SDAccess::release_bus();return StorageResult::INVALID_ARGUMENT;}struct stat st{};errno=0;const bool ok=::stat(mp,&st)==0;if(!ok){auto r=errno==ENOENT?StorageResult::MISS:ioResult();SDAccess::release_bus();return r;}if(st.st_size<0||static_cast<std::uint64_t>(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<std::size_t>(n)<cap?StorageResult::OK:StorageResult::TOO_LARGE;}
|
|
StorageResult NomadNetStorageSD::endList(){if(!list_dir_)return StorageResult::OK;if(!SDAccess::acquire_bus(100))return StorageResult::BUSY;errno=0;const bool ok=::closedir(list_dir_)==0;list_dir_=nullptr;list_base_[0]=0;SDAccess::release_bus();return ok?StorageResult::OK:ioResult();}
|
|
}}
|
|
#endif
|