/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #include "env_esp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "esp_heap_caps.h" #include "esp_log.h" #include "ff.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/projdefs.h" #include "freertos/queue.h" #include "freertos/task.h" #include "leveldb/env.h" #include "leveldb/slice.h" #include "leveldb/status.h" #include "tasks.hpp" namespace leveldb { std::shared_ptr sBackgroundThread; std::string ErrToStr(FRESULT err) { switch (err) { case FR_OK: return "FR_OK"; case FR_DISK_ERR: return "FR_DISK_ERR"; case FR_INT_ERR: return "FR_INT_ERR"; case FR_NOT_READY: return "FR_NOT_READY"; case FR_NO_FILE: return "FR_NO_FILE"; case FR_NO_PATH: return "FR_NO_PATH"; case FR_INVALID_NAME: return "FR_INVALID_NAME"; case FR_DENIED: return "FR_DENIED"; case FR_EXIST: return "FR_EXIST"; case FR_INVALID_OBJECT: return "FR_INVALID_OBJECT"; case FR_WRITE_PROTECTED: return "FR_WRITE_PROTECTED"; case FR_INVALID_DRIVE: return "FR_INVALID_DRIVE"; case FR_NOT_ENABLED: return "FR_NOT_ENABLED"; case FR_NO_FILESYSTEM: return "FR_NO_FILESYSTEM"; case FR_MKFS_ABORTED: return "FR_MKFS_ABORTED"; case FR_TIMEOUT: return "FR_TIMEOUT"; case FR_LOCKED: return "FR_LOCKED"; case FR_NOT_ENOUGH_CORE: return "FR_NOT_ENOUGH_CORE"; case FR_TOO_MANY_OPEN_FILES: return "FR_TOO_MANY_OPEN_FILES"; case FR_INVALID_PARAMETER: return "FR_INVALID_PARAMETER"; default: return "UNKNOWN"; } } Status EspError(const std::string& context, FRESULT err) { if (err == FR_NO_FILE) { return Status::NotFound(context, ErrToStr(err)); } else { return Status::IOError(context, ErrToStr(err)); } } class EspSequentialFile final : public SequentialFile { public: EspSequentialFile(const std::string& filename, FIL file) : file_(file), filename_(filename) {} ~EspSequentialFile() override { f_close(&file_); } Status Read(size_t n, Slice* result, char* scratch) override { UINT read_size = 0; FRESULT res = f_read(&file_, scratch, n, &read_size); if (res != FR_OK) { // Read error. return EspError(filename_, res); } *result = Slice(scratch, read_size); return Status::OK(); } Status Skip(uint64_t n) override { DWORD current_pos = f_tell(&file_); FRESULT res = f_lseek(&file_, current_pos + n); if (res != FR_OK) { return EspError(filename_, res); } return Status::OK(); } private: FIL file_; const std::string filename_; }; // Implements random read access in a file using pread(). // // Instances of this class are thread-safe, as required by the RandomAccessFile // API. Instances are immutable and Read() only calls thread-safe library // functions. class EspRandomAccessFile final : public RandomAccessFile { public: // The new instance takes ownership of |fd|. |fd_limiter| must outlive this // instance, and will be used to determine if . explicit EspRandomAccessFile(const std::string& filename) : filename_(std::move(filename)) {} ~EspRandomAccessFile() override {} Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { FIL file; FRESULT res = f_open(&file, filename_.c_str(), FA_READ); if (res != FR_OK) { return EspError(filename_, res); } res = f_lseek(&file, offset); if (res != FR_OK) { return EspError(filename_, res); } Status status; UINT read_size = 0; res = f_read(&file, scratch, n, &read_size); if (res != FR_OK || read_size == 0) { return EspError(filename_, res); } *result = Slice(scratch, read_size); f_close(&file); return status; } private: const std::string filename_; }; // TODO(jacqueline): LevelDB expects writes to this class to be buffered in // memory. FatFs already does in-memory buffering, but we should think about // whether to layer more on top. class EspWritableFile final : public WritableFile { public: EspWritableFile(std::string filename, FIL file) : filename_(std::move(filename)), file_(file), is_open_(true) {} ~EspWritableFile() override { if (is_open_) { // Ignoring any potential errors Close(); } } Status Append(const Slice& data) override { if (!is_open_) { return EspError(filename_, FR_NOT_ENABLED); } size_t write_size = data.size(); const char* write_data = data.data(); UINT bytes_written = 0; FRESULT res = f_write(&file_, write_data, write_size, &bytes_written); if (res != FR_OK) { return EspError(filename_, res); } return Status::OK(); } Status Close() override { is_open_ = false; FRESULT res = f_close(&file_); if (res != FR_OK) { return EspError(filename_, res); } return Status::OK(); } Status Flush() override { return Sync(); } Status Sync() override { if (!is_open_) { return EspError(filename_, FR_NOT_ENABLED); } FRESULT res = f_sync(&file_); if (res != FR_OK) { return EspError(filename_, res); } return Status::OK(); } private: const std::string filename_; FIL file_; bool is_open_; }; class EspFileLock : public FileLock { public: explicit EspFileLock(const std::string& filename) : filename_(filename) {} const std::string& filename() { return filename_; } private: const std::string filename_; }; class EspLogger final : public Logger { public: explicit EspLogger(FIL file) : file_(file) {} ~EspLogger() override { f_close(&file_); } void Logv(const char* format, std::va_list ap) override { /* std::va_list args_copy; va_copy(args_copy, ap); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" std::size_t bytes_needed = snprintf(NULL, 0, format, args_copy); char* output = reinterpret_cast( heap_caps_calloc(bytes_needed, 1, MALLOC_CAP_SPIRAM)); snprintf(output, bytes_needed, format, args_copy); #pragma GCC diagnostic pop va_end(args_copy); ESP_LOGI("LEVELDB", "%s", output); // f_puts(output, &file_); free(reinterpret_cast(output)); */ } private: FIL file_; }; EspEnv::~EspEnv() { ESP_LOGE("LEVELDB", "EspEnv singleton destroyed. Unsupported behavior!"); } Status EspEnv::NewSequentialFile(const std::string& filename, SequentialFile** result) { FIL file; FRESULT res = f_open(&file, filename.c_str(), FA_READ); if (res != FR_OK) { *result = nullptr; return EspError(filename, res); } *result = new EspSequentialFile(filename, file); return Status::OK(); } Status EspEnv::NewRandomAccessFile(const std::string& filename, RandomAccessFile** result) { // EspRandomAccessFile doesn't try to open the file until it's needed, so // we need to first ensure the file exists to handle the NotFound case // correctly. FILINFO info; FRESULT res = f_stat(filename.c_str(), &info); if (res != FR_OK) { *result = nullptr; return EspError(filename, res); } *result = new EspRandomAccessFile(filename); return Status::OK(); } Status EspEnv::NewWritableFile(const std::string& filename, WritableFile** result) { FIL file; FRESULT res = f_open(&file, filename.c_str(), FA_WRITE | FA_CREATE_ALWAYS); if (res != FR_OK) { *result = nullptr; return EspError(filename, res); } *result = new EspWritableFile(filename, file); return Status::OK(); } Status EspEnv::NewAppendableFile(const std::string& filename, WritableFile** result) { FIL file; FRESULT res = f_open(&file, filename.c_str(), FA_WRITE | FA_OPEN_APPEND); if (res != FR_OK) { *result = nullptr; return EspError(filename, res); } *result = new EspWritableFile(filename, file); return Status::OK(); } bool EspEnv::FileExists(const std::string& filename) { FILINFO info; return f_stat(filename.c_str(), &info) == FR_OK; } Status EspEnv::GetChildren(const std::string& directory_path, std::vector* result) { result->clear(); FF_DIR dir; FRESULT res = f_opendir(&dir, directory_path.c_str()); if (res != FR_OK) { return EspError(directory_path, res); } FILINFO info; for (;;) { res = f_readdir(&dir, &info); if (res != FR_OK) { return EspError(directory_path, res); } if (info.fname[0] == 0) { break; } result->emplace_back(info.fname); } res = f_closedir(&dir); if (res != FR_OK) { return EspError(directory_path, res); } return Status::OK(); } Status EspEnv::RemoveFile(const std::string& filename) { FRESULT res = f_unlink(filename.c_str()); if (res != FR_OK) { return EspError(filename, res); } return Status::OK(); } Status EspEnv::CreateDir(const std::string& dirname) { FRESULT res = f_mkdir(dirname.c_str()); if (res != FR_OK) { return EspError(dirname, res); } return Status::OK(); } Status EspEnv::RemoveDir(const std::string& dirname) { return RemoveFile(dirname); } Status EspEnv::GetFileSize(const std::string& filename, uint64_t* size) { FILINFO info; FRESULT res = f_stat(filename.c_str(), &info); if (res != FR_OK) { *size = 0; return EspError(filename, res); } *size = info.fsize; return Status::OK(); } Status EspEnv::RenameFile(const std::string& from, const std::string& to) { // Match the POSIX behaviour of replacing any existing file. if (FileExists(to)) { Status s = RemoveFile(to); if (!s.ok()) { return s; } } FRESULT res = f_rename(from.c_str(), to.c_str()); if (res != FR_OK) { return EspError(from, res); } return Status::OK(); } Status EspEnv::LockFile(const std::string& filename, FileLock** lock) { *lock = nullptr; if (!locks_.Insert(filename)) { return Status::IOError("lock " + filename, "already held by process"); } *lock = new EspFileLock(filename); return Status::OK(); } Status EspEnv::UnlockFile(FileLock* lock) { EspFileLock* posix_file_lock = static_cast(lock); locks_.Remove(posix_file_lock->filename()); delete posix_file_lock; return Status::OK(); } void EspEnv::StartThread(void (*thread_main)(void* thread_main_arg), void* thread_main_arg) { std::thread new_thread(thread_main, thread_main_arg); new_thread.detach(); } Status EspEnv::GetTestDirectory(std::string* result) { CreateDir("/tmp"); *result = "/tmp"; return Status::OK(); } Status EspEnv::NewLogger(const std::string& filename, Logger** result) { FIL file; FRESULT res = f_open(&file, filename.c_str(), FA_WRITE | FA_OPEN_APPEND); if (res != FR_OK) { *result = nullptr; return EspError(filename, res); } *result = new EspLogger(file); return Status::OK(); } uint64_t EspEnv::NowMicros() { struct timeval tv_now; gettimeofday(&tv_now, NULL); return (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec; } void EspEnv::SleepForMicroseconds(int micros) { vTaskDelay(pdMS_TO_TICKS(micros / 1000)); } EspEnv::EspEnv() {} void EspEnv::Schedule( void (*background_work_function)(void* background_work_arg), void* background_work_arg) { auto worker = sBackgroundThread; if (worker) { worker->Dispatch( [=]() { std::invoke(background_work_function, background_work_arg); }); } } } // namespace leveldb