diff --git a/scripts/esp32_file_buffer_fix.py b/scripts/esp32_file_buffer_fix.py index 28047156a..08ab8aeba 100644 --- a/scripts/esp32_file_buffer_fix.py +++ b/scripts/esp32_file_buffer_fix.py @@ -44,17 +44,55 @@ STRICT_METHOD = ORIGINAL_METHOD.replace( && static_cast(_f->_bf._size) == size));''') -def patched_vfs_source(source): +LAYOUT_GUARD_ARDUINO3 = LAYOUT_GUARD.replace( + "Arduino 2.0.17/newlib 3.3.0", "Arduino 3.1.3/3.3.11/newlib 4.3.0" +).replace("__NEWLIB__ != 3", "__NEWLIB__ != 4") +ORIGINAL_METHOD_ARDUINO3 = '''bool VFSFileImpl::setBufferSize(size_t size) { + if (_isDirectory || !_f) { + return 0; + } + int res = setvbuf(_f, NULL, _IOFBF, size); + return res == 0; +}''' +STRICT_METHOD_ARDUINO3 = ORIGINAL_METHOD_ARDUINO3.replace( + " return res == 0;", + ''' // Reject a successful newlib fallback to a different allocation size. + // size==0 retains the public choose-default behavior. + return res == 0 && (size == 0 || (_f->_bf._size > 0 + && static_cast(_f->_bf._size) == size));''') +FRAMEWORK_PINS = { + (2, 0, 17): (PINNED_VFS_SHA256, PATCHED_VFS_SHA256), + (3, 1, 3): ( + 'b879998d67c5c6fc36845831af60b03338365031b4ca15d638d5ff6389a5c155', + '5facd9c1543aad0105008ce6c93db0b771396834bf18cec6c62cceadee08d109'), + (3, 3, 11): ( + '3d345bcb3b764f08c6e923dbacc96f27da10edb12beb774de439228057d14a54', + '0cfa80cc29a38e854fa17401605dbe9adbd6157195ec5444f0a90d68c344cd0c'), +} + + +def patched_vfs_source(source, framework_version=(2, 0, 17)): + try: + pinned_digest, patched_digest = FRAMEWORK_PINS[framework_version] + except KeyError as error: + raise RuntimeError("ESP32 FS buffer fix: unreviewed framework version") from error source = source.replace("\r\n", "\n") digest = hashlib.sha256(source.encode("utf-8")).hexdigest() - if digest == PATCHED_VFS_SHA256: + if digest == patched_digest: return source - if (digest != PINNED_VFS_SHA256 or source.count(INCLUDE) != 1 - or source.count(ORIGINAL_METHOD) != 1): - raise RuntimeError("ESP32 FS buffer fix: changed pinned2.0.17 source; review SDK update") - patched = source.replace(INCLUDE, INCLUDE + LAYOUT_GUARD, 1).replace( - ORIGINAL_METHOD, STRICT_METHOD, 1) - if hashlib.sha256(patched.encode("utf-8")).hexdigest() != PATCHED_VFS_SHA256: + original, strict, layout = ((ORIGINAL_METHOD, STRICT_METHOD, LAYOUT_GUARD) + if framework_version == (2, 0, 17) else + (ORIGINAL_METHOD_ARDUINO3, STRICT_METHOD_ARDUINO3, + LAYOUT_GUARD_ARDUINO3)) + if (digest != pinned_digest or source.count(INCLUDE) != 1 + or source.count(original) != 1): + raise RuntimeError("ESP32 FS buffer fix: changed pinned source; review SDK update") + patched = source.replace(INCLUDE, INCLUDE + layout, 1).replace(original, strict, 1) + # Only the reviewed 3.3.11 comments contain these characters. Keep generated + # project copies ASCII without changing the shared SDK or executable text. + for character, replacement in (("\u2192", "->"), ("\u2013", "-"), ("\u2014", "-")): + patched = patched.replace(character, replacement) + if hashlib.sha256(patched.encode("utf-8")).hexdigest() != patched_digest: raise RuntimeError("ESP32 FS buffer fix: unexpected transform result") return patched @@ -79,8 +117,13 @@ def require_pinned_framework(source): raise RuntimeError("ESP32 FS buffer fix: missing framework version header") from error entries = re.findall(r"^#define ESP_ARDUINO_VERSION_(MAJOR|MINOR|PATCH)\s+(\d+)\s*$", header, re.MULTILINE) - if (len(entries) != 3 or dict(entries) != {"MAJOR": "2", "MINOR": "0", "PATCH": "17"}): + values = dict(entries) + if len(entries) != 3 or set(values) != {"MAJOR", "MINOR", "PATCH"}: raise RuntimeError("ESP32 FS buffer fix: changed/malformed pinned framework version") + version = tuple(int(values[key]) for key in ("MAJOR", "MINOR", "PATCH")) + if version not in FRAMEWORK_PINS: + raise RuntimeError("ESP32 FS buffer fix: changed/malformed pinned framework version") + return version def replace_vfs_source(build_env, node): @@ -89,8 +132,8 @@ def replace_vfs_source(build_env, node): source = Path(node.srcnode().get_abspath()) if source.parts[-4:] != SOURCE_SUFFIX: return node - require_pinned_framework(source) - patched = patched_vfs_source(source.read_text(encoding="utf-8")) + version = require_pinned_framework(source) + patched = patched_vfs_source(source.read_text(encoding="utf-8"), version) destination = Path(build_env.subst("$BUILD_DIR")) / "patched-esp32-fs" / source.name destination.parent.mkdir(parents=True, exist_ok=True) if not destination.exists() or destination.read_text(encoding="utf-8") != patched: diff --git a/test/fixtures/esp32_file_buffer/3.1.3/esp_arduino_version.h b/test/fixtures/esp32_file_buffer/3.1.3/esp_arduino_version.h new file mode 100644 index 000000000..f8b00d3a1 --- /dev/null +++ b/test/fixtures/esp32_file_buffer/3.1.3/esp_arduino_version.h @@ -0,0 +1,51 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Major version number (X.x.x) */ +#define ESP_ARDUINO_VERSION_MAJOR 3 +/** Minor version number (x.X.x) */ +#define ESP_ARDUINO_VERSION_MINOR 1 +/** Patch version number (x.x.X) */ +#define ESP_ARDUINO_VERSION_PATCH 3 + +/** + * Macro to convert ARDUINO version number into an integer + * + * To be used in comparisons, such as ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) + */ +#define ESP_ARDUINO_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) + +/** + * Current ARDUINO version, as an integer + * + * To be used in comparisons, such as ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) + */ +#define ESP_ARDUINO_VERSION ESP_ARDUINO_VERSION_VAL(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH) + +/** + * Current ARDUINO version, as string + */ +#define df2xstr(s) #s +#define df2str(s) df2xstr(s) +#define ESP_ARDUINO_VERSION_STR df2str(ESP_ARDUINO_VERSION_MAJOR) "." df2str(ESP_ARDUINO_VERSION_MINOR) "." df2str(ESP_ARDUINO_VERSION_PATCH) + +#ifdef __cplusplus +} +#endif diff --git a/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.cpp b/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.cpp new file mode 100644 index 000000000..616f37ac6 --- /dev/null +++ b/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.cpp @@ -0,0 +1,532 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "vfs_api.h" + +using namespace fs; + +#define DEFAULT_FILE_BUFFER_SIZE 4096 + +FileImplPtr VFSImpl::open(const char *fpath, const char *mode, const bool create) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return FileImplPtr(); + } + + if (!fpath || fpath[0] != '/') { + log_e("%s does not start with /", fpath); + return FileImplPtr(); + } + + char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 2); + if (!temp) { + log_e("malloc failed"); + return FileImplPtr(); + } + + strcpy(temp, _mountpoint); + strcat(temp, fpath); + + struct stat st; + //file found + if (!stat(temp, &st)) { + free(temp); + if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) { + return std::make_shared(this, fpath, mode); + } + log_e("%s has wrong mode 0x%08X", fpath, st.st_mode); + return FileImplPtr(); + } + + //try to open this as directory (might be mount point) + DIR *d = opendir(temp); + if (d) { + closedir(d); + free(temp); + return std::make_shared(this, fpath, mode); + } + + //file not found but mode permits file creation without folder creation + if ((mode && mode[0] != 'r') && (!create)) { + free(temp); + return std::make_shared(this, fpath, mode); + } + + ////file not found but mode permits file creation and folder creation + if ((mode && mode[0] != 'r') && create) { + + char *token; + char *folder = (char *)malloc(strlen(fpath)); + + int start_index = 0; + int end_index = 0; + + token = strchr(fpath + 1, '/'); + end_index = (token - fpath); + + while (token != NULL) { + memcpy(folder, fpath + start_index, end_index - start_index); + folder[end_index - start_index] = '\0'; + + if (!VFSImpl::mkdir(folder)) { + log_e("Creating folder: %s failed!", folder); + return FileImplPtr(); + } + + token = strchr(token + 1, '/'); + if (token != NULL) { + end_index = (token - fpath); + memset(folder, 0, strlen(folder)); + } + } + + free(folder); + free(temp); + return std::make_shared(this, fpath, mode); + } + + log_e("%s does not exist, no permits for creation", temp); + free(temp); + return FileImplPtr(); +} + +bool VFSImpl::exists(const char *fpath) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return false; + } + + VFSFileImpl f(this, fpath, "r"); + if (f) { + f.close(); + return true; + } + return false; +} + +bool VFSImpl::rename(const char *pathFrom, const char *pathTo) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return false; + } + + if (!pathFrom || pathFrom[0] != '/' || !pathTo || pathTo[0] != '/') { + log_e("bad arguments"); + return false; + } + if (!exists(pathFrom)) { + log_e("%s does not exists", pathFrom); + return false; + } + size_t mountpointLen = strlen(_mountpoint); + char *temp1 = (char *)malloc(strlen(pathFrom) + mountpointLen + 1); + if (!temp1) { + log_e("malloc failed"); + return false; + } + char *temp2 = (char *)malloc(strlen(pathTo) + mountpointLen + 1); + if (!temp2) { + free(temp1); + log_e("malloc failed"); + return false; + } + + strcpy(temp1, _mountpoint); + strcat(temp1, pathFrom); + + strcpy(temp2, _mountpoint); + strcat(temp2, pathTo); + + auto rc = ::rename(temp1, temp2); + free(temp1); + free(temp2); + return rc == 0; +} + +bool VFSImpl::remove(const char *fpath) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return false; + } + + if (!fpath || fpath[0] != '/') { + log_e("bad arguments"); + return false; + } + + VFSFileImpl f(this, fpath, "r"); + if (!f || f.isDirectory()) { + if (f) { + f.close(); + } + log_e("%s does not exists or is directory", fpath); + return false; + } + f.close(); + + char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1); + if (!temp) { + log_e("malloc failed"); + return false; + } + + strcpy(temp, _mountpoint); + strcat(temp, fpath); + + auto rc = unlink(temp); + free(temp); + return rc == 0; +} + +bool VFSImpl::mkdir(const char *fpath) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return false; + } + + VFSFileImpl f(this, fpath, "r"); + if (f && f.isDirectory()) { + f.close(); + //log_w("%s already exists", fpath); + return true; + } else if (f) { + f.close(); + log_e("%s is a file", fpath); + return false; + } + + char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1); + if (!temp) { + log_e("malloc failed"); + return false; + } + + strcpy(temp, _mountpoint); + strcat(temp, fpath); + + auto rc = ::mkdir(temp, ACCESSPERMS); + free(temp); + return rc == 0; +} + +bool VFSImpl::rmdir(const char *fpath) { + if (!_mountpoint) { + log_e("File system is not mounted"); + return false; + } + + if (strcmp(_mountpoint, "/spiffs") == 0) { + log_e("rmdir is unnecessary in SPIFFS"); + return false; + } + + VFSFileImpl f(this, fpath, "r"); + if (!f || !f.isDirectory()) { + if (f) { + f.close(); + } + log_e("%s does not exists or is a file", fpath); + return false; + } + f.close(); + + char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1); + if (!temp) { + log_e("malloc failed"); + return false; + } + + strcpy(temp, _mountpoint); + strcat(temp, fpath); + + auto rc = ::rmdir(temp); + free(temp); + return rc == 0; +} + +VFSFileImpl::VFSFileImpl(VFSImpl *fs, const char *fpath, const char *mode) : _fs(fs), _f(NULL), _d(NULL), _path(NULL), _isDirectory(false), _written(false) { + char *temp = (char *)malloc(strlen(fpath) + strlen(_fs->_mountpoint) + 1); + if (!temp) { + return; + } + + strcpy(temp, _fs->_mountpoint); + strcat(temp, fpath); + + _path = strdup(fpath); + if (!_path) { + log_e("strdup(%s) failed", fpath); + free(temp); + return; + } + + if (!stat(temp, &_stat)) { + //file found + if (S_ISREG(_stat.st_mode)) { + _isDirectory = false; + _f = fopen(temp, mode); + if (!_f) { + log_e("fopen(%s) failed", temp); + } + if (_f && (_stat.st_blksize == 0)) { + setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE); + } + } else if (S_ISDIR(_stat.st_mode)) { + _isDirectory = true; + _d = opendir(temp); + if (!_d) { + log_e("opendir(%s) failed", temp); + } + } else { + log_e("Unknown type 0x%08X for file %s", ((_stat.st_mode) & _IFMT), temp); + } + } else { + //file not found + if (!mode || mode[0] == 'r') { + //try to open as directory + _d = opendir(temp); + if (_d) { + _isDirectory = true; + } else { + _isDirectory = false; + //log_w("stat(%s) failed", temp); + } + } else { + //lets create this new file + _isDirectory = false; + _f = fopen(temp, mode); + if (!_f) { + log_e("fopen(%s) failed", temp); + } + if (_f && (_stat.st_blksize == 0)) { + setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE); + } + } + } + free(temp); +} + +VFSFileImpl::~VFSFileImpl() { + close(); +} + +void VFSFileImpl::close() { + if (_path) { + free(_path); + _path = NULL; + } + if (_isDirectory && _d) { + closedir(_d); + _d = NULL; + _isDirectory = false; + } else if (_f) { + fclose(_f); + _f = NULL; + } +} + +VFSFileImpl::operator bool() { + return (_isDirectory && _d != NULL) || _f != NULL; +} + +time_t VFSFileImpl::getLastWrite() { + _getStat(); + return _stat.st_mtime; +} + +void VFSFileImpl::_getStat() const { + if (!_path) { + return; + } + char *temp = (char *)malloc(strlen(_path) + strlen(_fs->_mountpoint) + 1); + if (!temp) { + return; + } + + strcpy(temp, _fs->_mountpoint); + strcat(temp, _path); + + if (!stat(temp, &_stat)) { + _written = false; + } + free(temp); +} + +size_t VFSFileImpl::write(const uint8_t *buf, size_t size) { + if (_isDirectory || !_f || !buf || !size) { + return 0; + } + _written = true; + return fwrite(buf, 1, size, _f); +} + +size_t VFSFileImpl::read(uint8_t *buf, size_t size) { + if (_isDirectory || !_f || !buf || !size) { + return 0; + } + + return fread(buf, 1, size, _f); +} + +void VFSFileImpl::flush() { + if (_isDirectory || !_f) { + return; + } + fflush(_f); + // workaround for https://github.com/espressif/arduino-esp32/issues/1293 + fsync(fileno(_f)); +} + +bool VFSFileImpl::seek(uint32_t pos, SeekMode mode) { + if (_isDirectory || !_f) { + return false; + } + auto rc = fseek(_f, pos, mode); + return rc == 0; +} + +size_t VFSFileImpl::position() const { + if (_isDirectory || !_f) { + return 0; + } + return ftell(_f); +} + +size_t VFSFileImpl::size() const { + if (_isDirectory || !_f) { + return 0; + } + if (_written) { + _getStat(); + } + return _stat.st_size; +} + +/* +* Change size of files internal buffer used for read / write operations. +* Need to be called right after opening file before any other operation! +*/ +bool VFSFileImpl::setBufferSize(size_t size) { + if (_isDirectory || !_f) { + return 0; + } + int res = setvbuf(_f, NULL, _IOFBF, size); + return res == 0; +} + +const char *VFSFileImpl::path() const { + return (const char *)_path; +} + +const char *VFSFileImpl::name() const { + return pathToFileName(path()); +} + +//to implement +boolean VFSFileImpl::isDirectory(void) { + return _isDirectory; +} + +FileImplPtr VFSFileImpl::openNextFile(const char *mode) { + if (!_isDirectory || !_d) { + return FileImplPtr(); + } + struct dirent *file = readdir(_d); + if (file == NULL) { + return FileImplPtr(); + } + if (file->d_type != DT_REG && file->d_type != DT_DIR) { + return openNextFile(mode); + } + + size_t pathLen = strlen(_path); + size_t fileNameLen = strlen(file->d_name); + char *name = (char *)malloc(pathLen + fileNameLen + 2); + + if (name == NULL) { + return FileImplPtr(); + } + + strcpy(name, _path); + + if ((file->d_name[0] != '/') && (_path[pathLen - 1] != '/')) { + strcat(name, "/"); + } + + strcat(name, file->d_name); + + FileImplPtr fileImplPtr = std::make_shared(_fs, name, mode); + free(name); + return fileImplPtr; +} + +boolean VFSFileImpl::seekDir(long position) { + if (!_d) { + return false; + } + seekdir(_d, position); + return true; +} + +String VFSFileImpl::getNextFileName() { + if (!_isDirectory || !_d) { + return ""; + } + struct dirent *file = readdir(_d); + if (file == NULL) { + return ""; + } + if (file->d_type != DT_REG && file->d_type != DT_DIR) { + return ""; + } + String fname = String(file->d_name); + String name = String(_path); + if (!fname.startsWith("/") && !name.endsWith("/")) { + name += "/"; + } + name += fname; + return name; +} + +String VFSFileImpl::getNextFileName(bool *isDir) { + if (!_isDirectory || !_d) { + return ""; + } + struct dirent *file = readdir(_d); + if (file == NULL) { + return ""; + } + if (file->d_type != DT_REG && file->d_type != DT_DIR) { + return ""; + } + String fname = String(file->d_name); + String name = String(_path); + if (!fname.startsWith("/") && !name.endsWith("/")) { + name += "/"; + } + name += fname; + + // check entry is a directory + if (isDir) { + *isDir = (file->d_type == DT_DIR); + } + return name; +} + +void VFSFileImpl::rewindDirectory(void) { + if (!_isDirectory || !_d) { + return; + } + rewinddir(_d); +} diff --git a/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.json b/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.json new file mode 100644 index 000000000..4646f0aed --- /dev/null +++ b/test/fixtures/esp32_file_buffer/3.1.3/vfs_api.json @@ -0,0 +1,3 @@ +{ + "source": "// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#include \"vfs_api.h\"\n\nusing namespace fs;\n\n#define DEFAULT_FILE_BUFFER_SIZE 4096\n\nFileImplPtr VFSImpl::open(const char *fpath, const char *mode, const bool create) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return FileImplPtr();\n }\n\n if (!fpath || fpath[0] != '/') {\n log_e(\"%s does not start with /\", fpath);\n return FileImplPtr();\n }\n\n char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 2);\n if (!temp) {\n log_e(\"malloc failed\");\n return FileImplPtr();\n }\n\n strcpy(temp, _mountpoint);\n strcat(temp, fpath);\n\n struct stat st;\n //file found\n if (!stat(temp, &st)) {\n free(temp);\n if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) {\n return std::make_shared(this, fpath, mode);\n }\n log_e(\"%s has wrong mode 0x%08X\", fpath, st.st_mode);\n return FileImplPtr();\n }\n\n //try to open this as directory (might be mount point)\n DIR *d = opendir(temp);\n if (d) {\n closedir(d);\n free(temp);\n return std::make_shared(this, fpath, mode);\n }\n\n //file not found but mode permits file creation without folder creation\n if ((mode && mode[0] != 'r') && (!create)) {\n free(temp);\n return std::make_shared(this, fpath, mode);\n }\n\n ////file not found but mode permits file creation and folder creation\n if ((mode && mode[0] != 'r') && create) {\n\n char *token;\n char *folder = (char *)malloc(strlen(fpath));\n\n int start_index = 0;\n int end_index = 0;\n\n token = strchr(fpath + 1, '/');\n end_index = (token - fpath);\n\n while (token != NULL) {\n memcpy(folder, fpath + start_index, end_index - start_index);\n folder[end_index - start_index] = '\\0';\n\n if (!VFSImpl::mkdir(folder)) {\n log_e(\"Creating folder: %s failed!\", folder);\n return FileImplPtr();\n }\n\n token = strchr(token + 1, '/');\n if (token != NULL) {\n end_index = (token - fpath);\n memset(folder, 0, strlen(folder));\n }\n }\n\n free(folder);\n free(temp);\n return std::make_shared(this, fpath, mode);\n }\n\n log_e(\"%s does not exist, no permits for creation\", temp);\n free(temp);\n return FileImplPtr();\n}\n\nbool VFSImpl::exists(const char *fpath) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n VFSFileImpl f(this, fpath, \"r\");\n if (f) {\n f.close();\n return true;\n }\n return false;\n}\n\nbool VFSImpl::rename(const char *pathFrom, const char *pathTo) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (!pathFrom || pathFrom[0] != '/' || !pathTo || pathTo[0] != '/') {\n log_e(\"bad arguments\");\n return false;\n }\n if (!exists(pathFrom)) {\n log_e(\"%s does not exists\", pathFrom);\n return false;\n }\n size_t mountpointLen = strlen(_mountpoint);\n char *temp1 = (char *)malloc(strlen(pathFrom) + mountpointLen + 1);\n if (!temp1) {\n log_e(\"malloc failed\");\n return false;\n }\n char *temp2 = (char *)malloc(strlen(pathTo) + mountpointLen + 1);\n if (!temp2) {\n free(temp1);\n log_e(\"malloc failed\");\n return false;\n }\n\n strcpy(temp1, _mountpoint);\n strcat(temp1, pathFrom);\n\n strcpy(temp2, _mountpoint);\n strcat(temp2, pathTo);\n\n auto rc = ::rename(temp1, temp2);\n free(temp1);\n free(temp2);\n return rc == 0;\n}\n\nbool VFSImpl::remove(const char *fpath) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (!fpath || fpath[0] != '/') {\n log_e(\"bad arguments\");\n return false;\n }\n\n VFSFileImpl f(this, fpath, \"r\");\n if (!f || f.isDirectory()) {\n if (f) {\n f.close();\n }\n log_e(\"%s does not exists or is directory\", fpath);\n return false;\n }\n f.close();\n\n char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n strcpy(temp, _mountpoint);\n strcat(temp, fpath);\n\n auto rc = unlink(temp);\n free(temp);\n return rc == 0;\n}\n\nbool VFSImpl::mkdir(const char *fpath) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n VFSFileImpl f(this, fpath, \"r\");\n if (f && f.isDirectory()) {\n f.close();\n //log_w(\"%s already exists\", fpath);\n return true;\n } else if (f) {\n f.close();\n log_e(\"%s is a file\", fpath);\n return false;\n }\n\n char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n strcpy(temp, _mountpoint);\n strcat(temp, fpath);\n\n auto rc = ::mkdir(temp, ACCESSPERMS);\n free(temp);\n return rc == 0;\n}\n\nbool VFSImpl::rmdir(const char *fpath) {\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (strcmp(_mountpoint, \"/spiffs\") == 0) {\n log_e(\"rmdir is unnecessary in SPIFFS\");\n return false;\n }\n\n VFSFileImpl f(this, fpath, \"r\");\n if (!f || !f.isDirectory()) {\n if (f) {\n f.close();\n }\n log_e(\"%s does not exists or is a file\", fpath);\n return false;\n }\n f.close();\n\n char *temp = (char *)malloc(strlen(fpath) + strlen(_mountpoint) + 1);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n strcpy(temp, _mountpoint);\n strcat(temp, fpath);\n\n auto rc = ::rmdir(temp);\n free(temp);\n return rc == 0;\n}\n\nVFSFileImpl::VFSFileImpl(VFSImpl *fs, const char *fpath, const char *mode) : _fs(fs), _f(NULL), _d(NULL), _path(NULL), _isDirectory(false), _written(false) {\n char *temp = (char *)malloc(strlen(fpath) + strlen(_fs->_mountpoint) + 1);\n if (!temp) {\n return;\n }\n\n strcpy(temp, _fs->_mountpoint);\n strcat(temp, fpath);\n\n _path = strdup(fpath);\n if (!_path) {\n log_e(\"strdup(%s) failed\", fpath);\n free(temp);\n return;\n }\n\n if (!stat(temp, &_stat)) {\n //file found\n if (S_ISREG(_stat.st_mode)) {\n _isDirectory = false;\n _f = fopen(temp, mode);\n if (!_f) {\n log_e(\"fopen(%s) failed\", temp);\n }\n if (_f && (_stat.st_blksize == 0)) {\n setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE);\n }\n } else if (S_ISDIR(_stat.st_mode)) {\n _isDirectory = true;\n _d = opendir(temp);\n if (!_d) {\n log_e(\"opendir(%s) failed\", temp);\n }\n } else {\n log_e(\"Unknown type 0x%08X for file %s\", ((_stat.st_mode) & _IFMT), temp);\n }\n } else {\n //file not found\n if (!mode || mode[0] == 'r') {\n //try to open as directory\n _d = opendir(temp);\n if (_d) {\n _isDirectory = true;\n } else {\n _isDirectory = false;\n //log_w(\"stat(%s) failed\", temp);\n }\n } else {\n //lets create this new file\n _isDirectory = false;\n _f = fopen(temp, mode);\n if (!_f) {\n log_e(\"fopen(%s) failed\", temp);\n }\n if (_f && (_stat.st_blksize == 0)) {\n setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE);\n }\n }\n }\n free(temp);\n}\n\nVFSFileImpl::~VFSFileImpl() {\n close();\n}\n\nvoid VFSFileImpl::close() {\n if (_path) {\n free(_path);\n _path = NULL;\n }\n if (_isDirectory && _d) {\n closedir(_d);\n _d = NULL;\n _isDirectory = false;\n } else if (_f) {\n fclose(_f);\n _f = NULL;\n }\n}\n\nVFSFileImpl::operator bool() {\n return (_isDirectory && _d != NULL) || _f != NULL;\n}\n\ntime_t VFSFileImpl::getLastWrite() {\n _getStat();\n return _stat.st_mtime;\n}\n\nvoid VFSFileImpl::_getStat() const {\n if (!_path) {\n return;\n }\n char *temp = (char *)malloc(strlen(_path) + strlen(_fs->_mountpoint) + 1);\n if (!temp) {\n return;\n }\n\n strcpy(temp, _fs->_mountpoint);\n strcat(temp, _path);\n\n if (!stat(temp, &_stat)) {\n _written = false;\n }\n free(temp);\n}\n\nsize_t VFSFileImpl::write(const uint8_t *buf, size_t size) {\n if (_isDirectory || !_f || !buf || !size) {\n return 0;\n }\n _written = true;\n return fwrite(buf, 1, size, _f);\n}\n\nsize_t VFSFileImpl::read(uint8_t *buf, size_t size) {\n if (_isDirectory || !_f || !buf || !size) {\n return 0;\n }\n\n return fread(buf, 1, size, _f);\n}\n\nvoid VFSFileImpl::flush() {\n if (_isDirectory || !_f) {\n return;\n }\n fflush(_f);\n // workaround for https://github.com/espressif/arduino-esp32/issues/1293\n fsync(fileno(_f));\n}\n\nbool VFSFileImpl::seek(uint32_t pos, SeekMode mode) {\n if (_isDirectory || !_f) {\n return false;\n }\n auto rc = fseek(_f, pos, mode);\n return rc == 0;\n}\n\nsize_t VFSFileImpl::position() const {\n if (_isDirectory || !_f) {\n return 0;\n }\n return ftell(_f);\n}\n\nsize_t VFSFileImpl::size() const {\n if (_isDirectory || !_f) {\n return 0;\n }\n if (_written) {\n _getStat();\n }\n return _stat.st_size;\n}\n\n/*\n* Change size of files internal buffer used for read / write operations.\n* Need to be called right after opening file before any other operation!\n*/\nbool VFSFileImpl::setBufferSize(size_t size) {\n if (_isDirectory || !_f) {\n return 0;\n }\n int res = setvbuf(_f, NULL, _IOFBF, size);\n return res == 0;\n}\n\nconst char *VFSFileImpl::path() const {\n return (const char *)_path;\n}\n\nconst char *VFSFileImpl::name() const {\n return pathToFileName(path());\n}\n\n//to implement\nboolean VFSFileImpl::isDirectory(void) {\n return _isDirectory;\n}\n\nFileImplPtr VFSFileImpl::openNextFile(const char *mode) {\n if (!_isDirectory || !_d) {\n return FileImplPtr();\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return FileImplPtr();\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return openNextFile(mode);\n }\n\n size_t pathLen = strlen(_path);\n size_t fileNameLen = strlen(file->d_name);\n char *name = (char *)malloc(pathLen + fileNameLen + 2);\n\n if (name == NULL) {\n return FileImplPtr();\n }\n\n strcpy(name, _path);\n\n if ((file->d_name[0] != '/') && (_path[pathLen - 1] != '/')) {\n strcat(name, \"/\");\n }\n\n strcat(name, file->d_name);\n\n FileImplPtr fileImplPtr = std::make_shared(_fs, name, mode);\n free(name);\n return fileImplPtr;\n}\n\nboolean VFSFileImpl::seekDir(long position) {\n if (!_d) {\n return false;\n }\n seekdir(_d, position);\n return true;\n}\n\nString VFSFileImpl::getNextFileName() {\n if (!_isDirectory || !_d) {\n return \"\";\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return \"\";\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return \"\";\n }\n String fname = String(file->d_name);\n String name = String(_path);\n if (!fname.startsWith(\"/\") && !name.endsWith(\"/\")) {\n name += \"/\";\n }\n name += fname;\n return name;\n}\n\nString VFSFileImpl::getNextFileName(bool *isDir) {\n if (!_isDirectory || !_d) {\n return \"\";\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return \"\";\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return \"\";\n }\n String fname = String(file->d_name);\n String name = String(_path);\n if (!fname.startsWith(\"/\") && !name.endsWith(\"/\")) {\n name += \"/\";\n }\n name += fname;\n\n // check entry is a directory\n if (isDir) {\n *isDir = (file->d_type == DT_DIR);\n }\n return name;\n}\n\nvoid VFSFileImpl::rewindDirectory(void) {\n if (!_isDirectory || !_d) {\n return;\n }\n rewinddir(_d);\n}\n" +} diff --git a/test/fixtures/esp32_file_buffer/3.3.11/esp_arduino_version.h b/test/fixtures/esp32_file_buffer/3.3.11/esp_arduino_version.h new file mode 100644 index 000000000..574bcf26f --- /dev/null +++ b/test/fixtures/esp32_file_buffer/3.3.11/esp_arduino_version.h @@ -0,0 +1,51 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Major version number (X.x.x) */ +#define ESP_ARDUINO_VERSION_MAJOR 3 +/** Minor version number (x.X.x) */ +#define ESP_ARDUINO_VERSION_MINOR 3 +/** Patch version number (x.x.X) */ +#define ESP_ARDUINO_VERSION_PATCH 11 + +/** + * Macro to convert ARDUINO version number into an integer + * + * To be used in comparisons, such as ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) + */ +#define ESP_ARDUINO_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) + +/** + * Current ARDUINO version, as an integer + * + * To be used in comparisons, such as ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) + */ +#define ESP_ARDUINO_VERSION ESP_ARDUINO_VERSION_VAL(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH) + +/** + * Current ARDUINO version, as string + */ +#define df2xstr(s) #s +#define df2str(s) df2xstr(s) +#define ESP_ARDUINO_VERSION_STR df2str(ESP_ARDUINO_VERSION_MAJOR) "." df2str(ESP_ARDUINO_VERSION_MINOR) "." df2str(ESP_ARDUINO_VERSION_PATCH) + +#ifdef __cplusplus +} +#endif diff --git a/test/fixtures/esp32_file_buffer/3.3.11/vfs_api.json b/test/fixtures/esp32_file_buffer/3.3.11/vfs_api.json new file mode 100644 index 000000000..d16a760cf --- /dev/null +++ b/test/fixtures/esp32_file_buffer/3.3.11/vfs_api.json @@ -0,0 +1,3 @@ +{ + "source": "// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#include \"Arduino.h\"\n#include \"vfs_api.h\"\n#include \n#include \n#include \n\nusing namespace fs;\n\n#define DEFAULT_FILE_BUFFER_SIZE 4096\n\nFileImplPtr VFSImpl::open(const char *fpath, const char *mode, const bool create) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return FileImplPtr();\n }\n\n if (!fpath || fpath[0] != '/') {\n log_e(\"%s does not start with /\", fpath);\n return FileImplPtr();\n }\n\n // Try to open as file first - let the file operation handle errors\n if (mode && mode[0] != 'r') {\n // For write modes, attempt to create directories if needed\n if (create) {\n char *token;\n char *folder = (char *)malloc(strlen(fpath) + 1);\n\n if (!folder) {\n log_e(\"Folder name malloc failed\");\n return FileImplPtr();\n }\n\n int start_index = 0;\n int end_index = 0;\n\n token = strchr(fpath + 1, '/');\n if (token != NULL) {\n end_index = (token - fpath);\n }\n\n while (token != NULL) {\n memcpy(folder, fpath + start_index, end_index - start_index);\n folder[end_index - start_index] = '\\0';\n\n if (!VFSImpl::mkdir(folder)) {\n log_e(\"Creating folder: %s failed!\", folder);\n free(folder);\n return FileImplPtr();\n }\n\n token = strchr(token + 1, '/');\n if (token != NULL) {\n end_index = (token - fpath);\n memset(folder, 0, strlen(folder));\n }\n }\n\n free(folder);\n }\n\n // Try to open the file directly - let fopen handle errors\n return std::make_shared(this, fpath, mode);\n }\n\n // For read mode, let the VFSFileImpl constructor handle the file opening\n // This avoids the TOCTOU race condition while maintaining proper functionality\n return std::make_shared(this, fpath, mode);\n}\n\nbool VFSImpl::exists(const char *fpath) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n VFSFileImpl f(this, fpath, \"r\");\n if (f) {\n f.close();\n return true;\n }\n return false;\n}\n\nbool VFSImpl::rename(const char *pathFrom, const char *pathTo) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (!pathFrom || pathFrom[0] != '/' || !pathTo || pathTo[0] != '/') {\n log_e(\"bad arguments\");\n return false;\n }\n\n size_t mountpointLen = strlen(_mountpoint);\n size_t temp1Len = mountpointLen + strlen(pathFrom) + 1;\n char *temp1 = (char *)malloc(temp1Len);\n if (!temp1) {\n log_e(\"malloc failed\");\n return false;\n }\n size_t temp2Len = mountpointLen + strlen(pathTo) + 1;\n char *temp2 = (char *)malloc(temp2Len);\n if (!temp2) {\n free(temp1);\n log_e(\"malloc failed\");\n return false;\n }\n\n snprintf(temp1, temp1Len, \"%s%s\", _mountpoint, pathFrom);\n snprintf(temp2, temp2Len, \"%s%s\", _mountpoint, pathTo);\n\n // Let rename() handle the error if source doesn't exist\n auto rc = ::rename(temp1, temp2);\n free(temp1);\n free(temp2);\n return rc == 0;\n}\n\nbool VFSImpl::remove(const char *fpath) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (!fpath || fpath[0] != '/') {\n log_e(\"bad arguments\");\n return false;\n }\n\n size_t tempLen = strlen(_mountpoint) + strlen(fpath) + 1;\n char *temp = (char *)malloc(tempLen);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n snprintf(temp, tempLen, \"%s%s\", _mountpoint, fpath);\n\n // Let unlink() handle the error if file doesn't exist\n auto rc = unlink(temp);\n free(temp);\n return rc == 0;\n}\n\nbool VFSImpl::mkdir(const char *fpath) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n size_t tempLen = strlen(_mountpoint) + strlen(fpath) + 1;\n char *temp = (char *)malloc(tempLen);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n snprintf(temp, tempLen, \"%s%s\", _mountpoint, fpath);\n\n // Note: the stat() \u2192 opendir() \u2192 ::mkdir() sequence below contains a\n // TOCTOU (time-of-check / time-of-use) window between individual syscalls.\n // The per-filesystem mutex (_mtx) held by this function ensures that no\n // other Arduino FS API call on the same filesystem can interleave here.\n // Code that bypasses the Arduino FS layer (direct fopen/stat/mkdir calls)\n // is not covered by this mutex.\n\n // stat() reports the path type without consuming a file descriptor.\n struct stat st;\n if (stat(temp, &st) == 0) {\n free(temp);\n if (S_ISDIR(st.st_mode)) {\n //log_w(\"%s already exists\", fpath);\n return true;\n }\n log_e(\"%s is a file\", fpath);\n return false;\n }\n\n // stat() failed (path does not exist). On SPIFFS the filesystem is flat and\n // virtual directories are always reachable via opendir() regardless of\n // whether any files with that path prefix exist yet. A successful opendir()\n // here means the path is an accessible virtual directory \u2013 treat it as\n // already present so we avoid calling ::mkdir() (which SPIFFS does not\n // support and would return an error).\n DIR *d = opendir(temp);\n if (d) {\n closedir(d);\n free(temp);\n return true;\n }\n\n auto rc = ::mkdir(temp, ACCESSPERMS);\n free(temp);\n return rc == 0;\n}\n\nbool VFSImpl::rmdir(const char *fpath) {\n auto lock = fsLock();\n if (!_mountpoint) {\n log_e(\"File system is not mounted\");\n return false;\n }\n\n if (strcmp(_mountpoint, \"/spiffs\") == 0) {\n log_e(\"rmdir is unnecessary in SPIFFS\");\n return false;\n }\n\n size_t tempLen = strlen(_mountpoint) + strlen(fpath) + 1;\n char *temp = (char *)malloc(tempLen);\n if (!temp) {\n log_e(\"malloc failed\");\n return false;\n }\n\n snprintf(temp, tempLen, \"%s%s\", _mountpoint, fpath);\n\n // Let rmdir() handle the error if directory doesn't exist\n auto rc = ::rmdir(temp);\n free(temp);\n return rc == 0;\n}\n\nVFSFileImpl::VFSFileImpl(VFSImpl *fs, const char *fpath, const char *mode)\n : _fs(fs), _f(NULL), _d(NULL), _path(NULL), _isDirectory(false), _stat{}, _written(false) {\n if (!mode) {\n log_w(\"mode is NULL, using default read mode\");\n mode = \"r\";\n }\n\n size_t tempLen = strlen(_fs->_mountpoint) + strlen(fpath) + 1;\n char *temp = (char *)malloc(tempLen);\n if (!temp) {\n return;\n }\n\n snprintf(temp, tempLen, \"%s%s\", _fs->_mountpoint, fpath);\n\n _path = strdup(fpath);\n if (!_path) {\n log_e(\"strdup(%s) failed\", fpath);\n free(temp);\n return;\n }\n\n // For read mode, detect whether the path is a directory or a regular file.\n // The per-filesystem mutex held by VFSImpl::open serializes this constructor\n // against other VFSImpl entry points (open/exists/rename/remove/mkdir/rmdir)\n // on the same filesystem. VFSFileImpl I/O operations (read/write/close) and\n // code that bypasses the Arduino FS layer (direct fopen/stat calls) are NOT\n // covered by the mutex.\n if (mode[0] == 'r') {\n _f = fopen(temp, mode);\n if (_f) {\n // fopen() succeeded \u2014 but on POSIX-style VFS layers (including ESP-IDF's\n // LittleFS VFS), open(2) can succeed on a directory with O_RDONLY. Use\n // stat to confirm the path is a regular file before treating it as one.\n if (fstat(fileno(_f), &_stat) == 0 && S_ISDIR(_stat.st_mode)) {\n // Path is a directory; close the file handle and open as a directory.\n fclose(_f);\n _f = NULL;\n _d = opendir(temp);\n _isDirectory = (_d != NULL);\n if (!_d) {\n log_e(\"opendir(%s) failed\", temp);\n }\n } else {\n _isDirectory = false;\n if (_stat.st_blksize == 0) {\n setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE);\n }\n }\n } else {\n // fopen failed \u2014 check via stat whether this is a directory.\n // stat() does not consume a file descriptor, so it can succeed even when\n // the filesystem's open-file limit has been reached. If stat reports a\n // regular file we skip opendir() so an exhausted fd pool is not\n // misidentified as a directory. If stat itself fails (as it does for\n // SPIFFS virtual directories which have no named entry of their own) we\n // still try opendir() because SPIFFS virtual directories are only\n // reachable that way.\n [[maybe_unused]]\n int fopen_errno = errno;\n struct stat dir_stat;\n int stat_rc = stat(temp, &dir_stat);\n bool statIsFile = (stat_rc == 0 && !S_ISDIR(dir_stat.st_mode));\n if (!statIsFile) {\n _d = opendir(temp);\n _isDirectory = (_d != NULL);\n } else {\n log_e(\"fopen(%s, %s) failed: %d (%s)\", temp, mode, fopen_errno, strerror(fopen_errno));\n }\n }\n } else {\n //lets create this new file\n _isDirectory = false;\n _f = fopen(temp, mode);\n if (!_f) {\n log_e(\"fopen(%s) failed\", temp);\n }\n if (!stat(temp, &_stat)) {\n if (_f && (_stat.st_blksize == 0)) {\n setvbuf(_f, NULL, _IOFBF, DEFAULT_FILE_BUFFER_SIZE);\n }\n } else {\n log_e(\"stat(%s) failed\", temp);\n }\n }\n free(temp);\n}\n\nVFSFileImpl::~VFSFileImpl() {\n close();\n}\n\nvoid VFSFileImpl::close() {\n if (_path) {\n free(_path);\n _path = NULL;\n }\n if (_isDirectory && _d) {\n closedir(_d);\n _d = NULL;\n _isDirectory = false;\n } else if (_f) {\n fclose(_f);\n _f = NULL;\n }\n}\n\nVFSFileImpl::operator bool() {\n return (_isDirectory && _d != NULL) || _f != NULL;\n}\n\ntime_t VFSFileImpl::getLastWrite() {\n _getStat();\n return _stat.st_mtime;\n}\n\nvoid VFSFileImpl::_getStat() const {\n if (!_path) {\n return;\n }\n size_t tempLen = strlen(_fs->_mountpoint) + strlen(_path) + 1;\n char *temp = (char *)malloc(tempLen);\n if (!temp) {\n return;\n }\n\n snprintf(temp, tempLen, \"%s%s\", _fs->_mountpoint, _path);\n\n if (!stat(temp, &_stat)) {\n _written = false;\n }\n free(temp);\n}\n\nsize_t VFSFileImpl::write(const uint8_t *buf, size_t size) {\n if (_isDirectory || !_f || !buf || !size) {\n return 0;\n }\n _written = true;\n return fwrite(buf, 1, size, _f);\n}\n\nsize_t VFSFileImpl::read(uint8_t *buf, size_t size) {\n if (_isDirectory || !_f || !buf || !size) {\n return 0;\n }\n\n return fread(buf, 1, size, _f);\n}\n\nvoid VFSFileImpl::flush() {\n if (_isDirectory || !_f) {\n return;\n }\n fflush(_f);\n // workaround for https://github.com/espressif/arduino-esp32/issues/1293\n fsync(fileno(_f));\n}\n\nbool VFSFileImpl::seek(uint32_t pos, SeekMode mode) {\n if (_isDirectory || !_f) {\n return false;\n }\n auto rc = fseek(_f, pos, mode);\n return rc == 0;\n}\n\nsize_t VFSFileImpl::position() const {\n if (_isDirectory || !_f) {\n return 0;\n }\n return ftell(_f);\n}\n\nsize_t VFSFileImpl::size() const {\n if (_isDirectory || !_f) {\n return 0;\n }\n if (_written) {\n _getStat();\n }\n return _stat.st_size;\n}\n\n/*\n* Change size of files internal buffer used for read / write operations.\n* Need to be called right after opening file before any other operation!\n*/\nbool VFSFileImpl::setBufferSize(size_t size) {\n if (_isDirectory || !_f) {\n return 0;\n }\n int res = setvbuf(_f, NULL, _IOFBF, size);\n return res == 0;\n}\n\nconst char *VFSFileImpl::path() const {\n return (const char *)_path;\n}\n\nconst char *VFSFileImpl::name() const {\n return pathToFileName(path());\n}\n\n//to implement\nboolean VFSFileImpl::isDirectory(void) {\n return _isDirectory;\n}\n\nFileImplPtr VFSFileImpl::openNextFile(const char *mode) {\n if (!_isDirectory || !_d) {\n return FileImplPtr();\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return FileImplPtr();\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return openNextFile(mode);\n }\n\n size_t pathLen = strlen(_path);\n size_t fileNameLen = strlen(file->d_name);\n size_t nameLen = pathLen + fileNameLen + 2;\n char *name = (char *)malloc(nameLen);\n\n if (name == NULL) {\n return FileImplPtr();\n }\n\n if ((file->d_name[0] != '/') && (_path[pathLen - 1] != '/')) {\n snprintf(name, nameLen, \"%s/%s\", _path, file->d_name);\n } else {\n snprintf(name, nameLen, \"%s%s\", _path, file->d_name);\n }\n\n FileImplPtr fileImplPtr = std::make_shared(_fs, name, mode);\n free(name);\n return fileImplPtr;\n}\n\nboolean VFSFileImpl::seekDir(long position) {\n if (!_d) {\n return false;\n }\n seekdir(_d, position);\n return true;\n}\n\nString VFSFileImpl::getNextFileName() {\n if (!_isDirectory || !_d) {\n return \"\";\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return \"\";\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return \"\";\n }\n String fname = String(file->d_name);\n String name = String(_path);\n if (!fname.startsWith(\"/\") && !name.endsWith(\"/\")) {\n name += \"/\";\n }\n name += fname;\n return name;\n}\n\nString VFSFileImpl::getNextFileName(bool *isDir) {\n if (!_isDirectory || !_d) {\n return \"\";\n }\n struct dirent *file = readdir(_d);\n if (file == NULL) {\n return \"\";\n }\n if (file->d_type != DT_REG && file->d_type != DT_DIR) {\n return \"\";\n }\n String fname = String(file->d_name);\n String name = String(_path);\n if (!fname.startsWith(\"/\") && !name.endsWith(\"/\")) {\n name += \"/\";\n }\n name += fname;\n\n // check entry is a directory\n if (isDir) {\n *isDir = (file->d_type == DT_DIR);\n }\n return name;\n}\n\nvoid VFSFileImpl::rewindDirectory(void) {\n if (!_isDirectory || !_d) {\n return;\n }\n rewinddir(_d);\n}\n" +} diff --git a/test/fixtures/esp32_file_buffer/README.txt b/test/fixtures/esp32_file_buffer/README.txt index 584b29067..38a8d087d 100644 --- a/test/fixtures/esp32_file_buffer/README.txt +++ b/test/fixtures/esp32_file_buffer/README.txt @@ -27,3 +27,24 @@ model 32-bit pointer widths rather than borrowing the host libc's FILE layout. Backend buffer admission is not a wall-clock guarantee: SPIFFS metadata, garbage collection, flush, close and publication still require hardware tests. + +Arduino-ESP32 3.1.3 and 3.3.11: +The versioned directories contain exact upstream version headers and VFS source +encoded in the JSON "source" field. JSON escaping preserves the upstream bytes +while keeping repository fixture files ASCII. The build copy replaces only the +reviewed Unicode comment punctuation; the shared SDK remains unmodified. +https://github.com/espressif/arduino-esp32/blob/3.1.3/libraries/FS/src/vfs_api.cpp +https://github.com/espressif/arduino-esp32/blob/3.3.11/libraries/FS/src/vfs_api.cpp +Both source variants retain their complete upstream copyright/license notices. +3.1.3 source SHA256 b879998d67c5c6fc36845831af60b03338365031b4ca15d638d5ff6389a5c155 +3.3.11 source SHA256 3d345bcb3b764f08c6e923dbacc96f27da10edb12beb774de439228057d14a54 + +newlib4/ contains the exact shared FILE-layout and generated version headers +from Xtensa 14.2.0+20260121 and RISC-V 13.2.0+20240530, newlib 4.3.0. +Both installed reent.h files match these complete upstream files byte for byte: +https://github.com/espressif/newlib-esp32/blob/esp-4.3.0_20260121/newlib/libc/include/sys/reent.h +https://github.com/espressif/newlib-esp32/blob/esp-4.3.0_20240530/newlib/libc/include/sys/reent.h +https://github.com/espressif/newlib-esp32/blob/esp-4.3.0_20260121/COPYING.NEWLIB +reent SHA256 c7ba657f911f7e27b7449bda36aa4efa80a025c726768dabb8d4a792f535ec9a +The reviewed FILE prefix has the same 32-bit _bf/_size offsets as newlib 3.3.0. +Arduino 3 uses a separate newlib 4.3.0 version guard; other versions fail closed. diff --git a/test/fixtures/esp32_file_buffer/newlib4/_newlib_version.h b/test/fixtures/esp32_file_buffer/newlib4/_newlib_version.h new file mode 100644 index 000000000..b082eec0d --- /dev/null +++ b/test/fixtures/esp32_file_buffer/newlib4/_newlib_version.h @@ -0,0 +1,11 @@ +/* _newlib_version.h. Generated from _newlib_version.hin by configure. */ +/* Version macros for internal and downstream use. */ +#ifndef _NEWLIB_VERSION_H__ +#define _NEWLIB_VERSION_H__ 1 + +#define _NEWLIB_VERSION "4.3.0" +#define __NEWLIB__ 4 +#define __NEWLIB_MINOR__ 3 +#define __NEWLIB_PATCHLEVEL__ 0 + +#endif /* !_NEWLIB_VERSION_H__ */ diff --git a/test/fixtures/esp32_file_buffer/newlib4/newlib_sys_reent.h b/test/fixtures/esp32_file_buffer/newlib4/newlib_sys_reent.h new file mode 100644 index 000000000..6d8b005b2 --- /dev/null +++ b/test/fixtures/esp32_file_buffer/newlib4/newlib_sys_reent.h @@ -0,0 +1,896 @@ +/* This header file provides the reentrancy. */ + +/* WARNING: All identifiers here must begin with an underscore. This file is + included by stdio.h and others and we therefore must only use identifiers + in the namespace allotted to us. */ + +#ifndef _SYS_REENT_H_ +#ifdef __cplusplus +extern "C" { +#endif +#define _SYS_REENT_H_ + +#include <_ansi.h> +#include +#include + +#define _NULL 0 + +#ifndef __Long +#if __LONG_MAX__ == 2147483647L +#define __Long long +typedef unsigned __Long __ULong; +#elif __INT_MAX__ == 2147483647 +#define __Long int +typedef unsigned __Long __ULong; +#endif +#endif + +#if !defined( __Long) +#include +#endif + +#ifndef __machine_flock_t_defined +#include +typedef _LOCK_RECURSIVE_T _flock_t; +#endif + +#ifndef __Long +#define __Long __int32_t +typedef __uint32_t __ULong; +#endif + +struct _reent; + +struct __locale_t; + +/* + * If _REENT_SMALL is defined, we make struct _reent as small as possible, + * by having nearly everything possible allocated at first use. + */ + +struct _Bigint +{ + struct _Bigint *_next; + int _k, _maxwds, _sign, _wds; + __ULong _x[1]; +}; + +/* needed by reentrant structure */ +struct __tm +{ + int __tm_sec; + int __tm_min; + int __tm_hour; + int __tm_mday; + int __tm_mon; + int __tm_year; + int __tm_wday; + int __tm_yday; + int __tm_isdst; +}; + +/* + * atexit() support. + */ + +#define _ATEXIT_SIZE 32 /* must be at least 32 to guarantee ANSI conformance */ + +struct _on_exit_args { + void * _fnargs[_ATEXIT_SIZE]; /* user fn args */ + void * _dso_handle[_ATEXIT_SIZE]; + /* Bitmask is set if user function takes arguments. */ + __ULong _fntypes; /* type of exit routine - + Must have at least _ATEXIT_SIZE bits */ + /* Bitmask is set if function was registered via __cxa_atexit. */ + __ULong _is_cxa; +}; + +#ifdef _REENT_SMALL +struct _atexit { + struct _atexit *_next; /* next in list */ + int _ind; /* next index in this table */ + void (*_fns[_ATEXIT_SIZE])(void); /* the table itself */ + struct _on_exit_args * _on_exit_args_ptr; +}; +# define _ATEXIT_INIT {_NULL, 0, {_NULL}, _NULL} +#else +struct _atexit { + struct _atexit *_next; /* next in list */ + int _ind; /* next index in this table */ + /* Some entries may already have been called, and will be NULL. */ + void (*_fns[_ATEXIT_SIZE])(void); /* the table itself */ + struct _on_exit_args _on_exit_args; +}; +# define _ATEXIT_INIT {_NULL, 0, {_NULL}, {{_NULL}, {_NULL}, 0, 0}} +#endif + +/* + * Stdio buffers. + * + * This and __FILE are defined here because we need them for struct _reent, + * but we don't want stdio.h included when stdlib.h is. + */ + +struct __sbuf { + unsigned char *_base; + int _size; +}; + +/* + * Stdio state variables. + * + * The following always hold: + * + * if (_flags&(__SLBF|__SWR)) == (__SLBF|__SWR), + * _lbfsize is -_bf._size, else _lbfsize is 0 + * if _flags&__SRD, _w is 0 + * if _flags&__SWR, _r is 0 + * + * This ensures that the getc and putc macros (or inline functions) never + * try to write or read from a file that is in `read' or `write' mode. + * (Moreover, they can, and do, automatically switch from read mode to + * write mode, and back, on "r+" and "w+" files.) + * + * _lbfsize is used only to make the inline line-buffered output stream + * code as compact as possible. + * + * _ub, _up, and _ur are used when ungetc() pushes back more characters + * than fit in the current _bf, or when ungetc() pushes back a character + * that does not match the previous one in _bf. When this happens, + * _ub._base becomes non-nil (i.e., a stream has ungetc() data iff + * _ub._base!=NULL) and _up and _ur save the current values of _p and _r. + */ + +#define _REENT_SMALL_CHECK_INIT(ptr) /* nothing */ + +/* Cygwin must use __sFILE64 for backward compatibility, even though + it's not defining __LARGE64_FILES anymore. To make sure that __sFILE + is never defined, disable it here explicitely. */ +#ifndef __CYGWIN__ + +struct __sFILE { + unsigned char *_p; /* current position in (some) buffer */ + int _r; /* read space left for getc() */ + int _w; /* write space left for putc() */ + short _flags; /* flags, below; this FILE is free if 0 */ + short _file; /* fileno, if Unix descriptor, else -1 */ + struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */ + int _lbfsize; /* 0 or -_bf._size, for inline putc */ + +#ifdef _REENT_SMALL + struct _reent *_data; +#endif + + /* operations */ + void * _cookie; /* cookie passed to io functions */ + + _READ_WRITE_RETURN_TYPE (*_read) (struct _reent *, void *, + char *, _READ_WRITE_BUFSIZE_TYPE); + _READ_WRITE_RETURN_TYPE (*_write) (struct _reent *, void *, + const char *, + _READ_WRITE_BUFSIZE_TYPE); + _fpos_t (*_seek) (struct _reent *, void *, _fpos_t, int); + int (*_close) (struct _reent *, void *); + + /* separate buffer for long sequences of ungetc() */ + struct __sbuf _ub; /* ungetc buffer */ + unsigned char *_up; /* saved _p when _p is doing ungetc data */ + int _ur; /* saved _r when _r is counting ungetc data */ + + /* tricks to meet minimum requirements even when malloc() fails */ + unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */ + unsigned char _nbuf[1]; /* guarantee a getc() buffer */ + + /* separate buffer for fgetline() when line crosses buffer boundary */ + struct __sbuf _lb; /* buffer for fgetline() */ + + /* Unix stdio files get aligned to block boundaries on fseek() */ + int _blksize; /* stat.st_blksize (may be != _bf._size) */ + _off_t _offset; /* current lseek offset */ + +#ifndef _REENT_SMALL + struct _reent *_data; /* Here for binary compatibility? Remove? */ +#endif + +#ifndef __SINGLE_THREAD__ + _flock_t _lock; /* for thread-safety locking */ +#endif + _mbstate_t _mbstate; /* for wide char stdio functions. */ + int _flags2; /* for future use */ +}; + +#endif /* !__CYGWIN__ */ + +#ifdef __CUSTOM_FILE_IO__ + +/* Get custom _FILE definition. */ +#include + +#else /* !__CUSTOM_FILE_IO__ */ +/* Cygwin must use __sFILE64 for backward compatibility, even though + it's not defining __LARGE64_FILES anymore. It also has to make + sure the name is the same to satisfy C++ name mangling. Overloading + _fpos64_t just fixes a build problem. The _seek64 function is + actually never used without __LARGE64_FILES being defined. */ +#if defined (__LARGE64_FILES) || defined (__CYGWIN__) +#ifdef __CYGWIN__ +#define _fpos64_t _fpos_t +#endif + +struct __sFILE64 { + unsigned char *_p; /* current position in (some) buffer */ + int _r; /* read space left for getc() */ + int _w; /* write space left for putc() */ + short _flags; /* flags, below; this FILE is free if 0 */ + short _file; /* fileno, if Unix descriptor, else -1 */ + struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */ + int _lbfsize; /* 0 or -_bf._size, for inline putc */ + + struct _reent *_data; + + /* operations */ + void * _cookie; /* cookie passed to io functions */ + + _READ_WRITE_RETURN_TYPE (*_read) (struct _reent *, void *, + char *, _READ_WRITE_BUFSIZE_TYPE); + _READ_WRITE_RETURN_TYPE (*_write) (struct _reent *, void *, + const char *, + _READ_WRITE_BUFSIZE_TYPE); + _fpos_t (*_seek) (struct _reent *, void *, _fpos_t, int); + int (*_close) (struct _reent *, void *); + + /* separate buffer for long sequences of ungetc() */ + struct __sbuf _ub; /* ungetc buffer */ + unsigned char *_up; /* saved _p when _p is doing ungetc data */ + int _ur; /* saved _r when _r is counting ungetc data */ + + /* tricks to meet minimum requirements even when malloc() fails */ + unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */ + unsigned char _nbuf[1]; /* guarantee a getc() buffer */ + + /* separate buffer for fgetline() when line crosses buffer boundary */ + struct __sbuf _lb; /* buffer for fgetline() */ + + /* Unix stdio files get aligned to block boundaries on fseek() */ + int _blksize; /* stat.st_blksize (may be != _bf._size) */ + int _flags2; /* for future use */ + + _off64_t _offset; /* current lseek offset */ + _fpos64_t (*_seek64) (struct _reent *, void *, _fpos64_t, int); + +#ifndef __SINGLE_THREAD__ + _flock_t _lock; /* for thread-safety locking */ +#endif + _mbstate_t _mbstate; /* for wide char stdio functions. */ +}; +typedef struct __sFILE64 __FILE; +#else +typedef struct __sFILE __FILE; +#endif /* __LARGE64_FILES */ +#endif /* !__CUSTOM_FILE_IO__ */ + +extern __FILE __sf[3]; + +struct _glue +{ + struct _glue *_next; + int _niobs; + __FILE *_iobs; +}; + +extern struct _glue __sglue; + +/* + * rand48 family support + * + * Copyright (c) 1993 Martin Birgmeier + * All rights reserved. + * + * You may redistribute unmodified or modified versions of this source + * code provided that the above copyright notice and this and the + * following conditions are retained. + * + * This software is provided ``as is'', and comes with no warranties + * of any kind. I shall in no event be liable for anything that happens + * to anyone/anything when using this software. + */ +#define _RAND48_SEED_0 (0x330e) +#define _RAND48_SEED_1 (0xabcd) +#define _RAND48_SEED_2 (0x1234) +#define _RAND48_MULT_0 (0xe66d) +#define _RAND48_MULT_1 (0xdeec) +#define _RAND48_MULT_2 (0x0005) +#define _RAND48_ADD (0x000b) +struct _rand48 { + unsigned short _seed[3]; + unsigned short _mult[3]; + unsigned short _add; +#ifdef _REENT_SMALL + /* Put this in here as well, for good luck. */ + __extension__ unsigned long long _rand_next; +#endif +}; + +/* How big the some arrays are. */ +#define _REENT_EMERGENCY_SIZE 25 +#define _REENT_ASCTIME_SIZE 26 +#define _REENT_SIGNAL_SIZE 24 + +#ifndef _REENT_THREAD_LOCAL + +#ifdef _REENT_BACKWARD_BINARY_COMPAT +#define _REENT_INIT_RESERVED_0 0, +#define _REENT_INIT_RESERVED_1 0, +#define _REENT_INIT_RESERVED_2 0, +#define _REENT_INIT_RESERVED_6_7 _NULL, _ATEXIT_INIT, +#define _REENT_INIT_RESERVED_8 {_NULL, 0, _NULL}, +#else +#define _REENT_INIT_RESERVED_0 /* Nothing to initialize */ +#define _REENT_INIT_RESERVED_1 /* Nothing to initialize */ +#define _REENT_INIT_RESERVED_2 /* Nothing to initialize */ +#define _REENT_INIT_RESERVED_6_7 /* Nothing to initialize */ +#define _REENT_INIT_RESERVED_8 /* Nothing to initialize */ +#endif + +/* + * struct _reent + * + * This structure contains the thread-local objects needed by the library. + * It's raison d'etre is to facilitate threads by making all library routines + * reentrant. The exit handler support and FILE maintenance use dedicated + * global objects which are not included in this structure. + */ + +#ifdef _REENT_SMALL + +struct _mprec +{ + /* used by mprec routines */ + struct _Bigint *_result; + int _result_k; + struct _Bigint *_p5s; + struct _Bigint **_freelist; +}; + + +struct _misc_reent +{ + /* miscellaneous reentrant data */ + char *_strtok_last; + _mbstate_t _mblen_state; + _mbstate_t _wctomb_state; + _mbstate_t _mbtowc_state; + char _l64a_buf[8]; + int _getdate_err; + _mbstate_t _mbrlen_state; + _mbstate_t _mbrtowc_state; + _mbstate_t _mbsrtowcs_state; + _mbstate_t _wcrtomb_state; + _mbstate_t _wcsrtombs_state; +}; + +/* This version of _reent is laid out with "int"s in pairs, to help + * ports with 16-bit int's but 32-bit pointers, align nicely. */ +struct _reent +{ + /* As an exception to the above put _errno first for binary + compatibility with non _REENT_SMALL targets. */ + int _errno; /* local copy of errno */ + + /* FILE is a big struct and may change over time. To try to achieve binary + compatibility with future versions, put stdin,stdout,stderr here. + These are pointers into member __sf defined below. */ + __FILE *_stdin, *_stdout, *_stderr; /* XXX */ + + int _inc; /* used by tmpnam */ + + char *_emergency; + +#ifdef _REENT_BACKWARD_BINARY_COMPAT + int _reserved_0; + int _reserved_1; +#endif + struct __locale_t *_locale;/* per-thread locale */ + + struct _mprec *_mp; + + void (*__cleanup) (struct _reent *); + + int _gamma_signgam; + + /* used by some fp conversion routines */ + int _cvtlen; /* should be size_t */ + char *_cvtbuf; + + struct _rand48 *_r48; + struct __tm *_localtime_buf; + char *_asctime_buf; + + /* signal info */ + void (** _sig_func)(int); + +#ifdef _REENT_BACKWARD_BINARY_COMPAT + struct _atexit *_reserved_6; + struct _atexit _reserved_7; + struct _glue _reserved_8; +#endif + + __FILE *__sf; /* file descriptors */ + struct _misc_reent *_misc; /* strtok, multibyte states */ + char *_signal_buf; /* strsignal */ +}; + +# define _REENT_INIT(var) \ + { 0, \ + &__sf[0], \ + &__sf[1], \ + &__sf[2], \ + 0, \ + _NULL, \ + _REENT_INIT_RESERVED_0 \ + _REENT_INIT_RESERVED_1 \ + _NULL, \ + _NULL, \ + _NULL, \ + 0, \ + 0, \ + _NULL, \ + _NULL, \ + _NULL, \ + _NULL, \ + _NULL, \ + _REENT_INIT_RESERVED_6_7 \ + _REENT_INIT_RESERVED_8 \ + _NULL, \ + _NULL, \ + _NULL \ + } + +#define _REENT_INIT_PTR_ZEROED(var) \ + { (var)->_stdin = &__sf[0]; \ + (var)->_stdout = &__sf[1]; \ + (var)->_stderr = &__sf[2]; \ + } + +/* Specify how to handle reent_check malloc failures. */ +#ifdef _REENT_CHECK_VERIFY +#include +#define __reent_assert(x) ((x) ? (void)0 : __assert_func(__FILE__, __LINE__, (char *)0, "REENT malloc succeeded")) +#else +#define __reent_assert(x) ((void)0) +#endif + +#ifdef __CUSTOM_FILE_IO__ +#error Custom FILE I/O and _REENT_SMALL not currently supported. +#endif + +/* Generic _REENT check macro. */ +#define _REENT_CHECK(var, what, type, size, init) do { \ + struct _reent *_r = (var); \ + if (_r->what == NULL) { \ + _r->what = (type)malloc(size); \ + __reent_assert(_r->what); \ + init; \ + } \ +} while (0) + +#define _REENT_CHECK_TM(var) \ + _REENT_CHECK(var, _localtime_buf, struct __tm *, sizeof *((var)->_localtime_buf), \ + /* nothing */) + +#define _REENT_CHECK_ASCTIME_BUF(var) \ + _REENT_CHECK(var, _asctime_buf, char *, _REENT_ASCTIME_SIZE, \ + memset((var)->_asctime_buf, 0, _REENT_ASCTIME_SIZE)) + +/* Handle the dynamically allocated rand48 structure. */ +#define _REENT_INIT_RAND48(var) do { \ + struct _reent *_r = (var); \ + _r->_r48->_seed[0] = _RAND48_SEED_0; \ + _r->_r48->_seed[1] = _RAND48_SEED_1; \ + _r->_r48->_seed[2] = _RAND48_SEED_2; \ + _r->_r48->_mult[0] = _RAND48_MULT_0; \ + _r->_r48->_mult[1] = _RAND48_MULT_1; \ + _r->_r48->_mult[2] = _RAND48_MULT_2; \ + _r->_r48->_add = _RAND48_ADD; \ + _r->_r48->_rand_next = 1; \ +} while (0) +#define _REENT_CHECK_RAND48(var) \ + _REENT_CHECK(var, _r48, struct _rand48 *, sizeof *((var)->_r48), _REENT_INIT_RAND48((var))) + +#define _REENT_INIT_MP(var) do { \ + struct _reent *_r = (var); \ + _r->_mp->_result_k = 0; \ + _r->_mp->_result = _r->_mp->_p5s = _NULL; \ + _r->_mp->_freelist = _NULL; \ +} while (0) +#define _REENT_CHECK_MP(var) \ + _REENT_CHECK(var, _mp, struct _mprec *, sizeof *((var)->_mp), _REENT_INIT_MP(var)) + +#define _REENT_CHECK_EMERGENCY(var) \ + _REENT_CHECK(var, _emergency, char *, _REENT_EMERGENCY_SIZE, /* nothing */) + +#define _REENT_INIT_MISC(var) do { \ + struct _reent *_r = (var); \ + _r->_misc->_strtok_last = _NULL; \ + _r->_misc->_mblen_state.__count = 0; \ + _r->_misc->_mblen_state.__value.__wch = 0; \ + _r->_misc->_wctomb_state.__count = 0; \ + _r->_misc->_wctomb_state.__value.__wch = 0; \ + _r->_misc->_mbtowc_state.__count = 0; \ + _r->_misc->_mbtowc_state.__value.__wch = 0; \ + _r->_misc->_mbrlen_state.__count = 0; \ + _r->_misc->_mbrlen_state.__value.__wch = 0; \ + _r->_misc->_mbrtowc_state.__count = 0; \ + _r->_misc->_mbrtowc_state.__value.__wch = 0; \ + _r->_misc->_mbsrtowcs_state.__count = 0; \ + _r->_misc->_mbsrtowcs_state.__value.__wch = 0; \ + _r->_misc->_wcrtomb_state.__count = 0; \ + _r->_misc->_wcrtomb_state.__value.__wch = 0; \ + _r->_misc->_wcsrtombs_state.__count = 0; \ + _r->_misc->_wcsrtombs_state.__value.__wch = 0; \ + _r->_misc->_l64a_buf[0] = '\0'; \ + _r->_misc->_getdate_err = 0; \ +} while (0) +#define _REENT_CHECK_MISC(var) \ + _REENT_CHECK(var, _misc, struct _misc_reent *, sizeof *((var)->_misc), _REENT_INIT_MISC(var)) + +#define _REENT_CHECK_SIGNAL_BUF(var) \ + _REENT_CHECK(var, _signal_buf, char *, _REENT_SIGNAL_SIZE, /* nothing */) + +#define _REENT_SIGNGAM(ptr) ((ptr)->_gamma_signgam) +#define _REENT_RAND_NEXT(ptr) ((ptr)->_r48->_rand_next) +#define _REENT_RAND48_SEED(ptr) ((ptr)->_r48->_seed) +#define _REENT_RAND48_MULT(ptr) ((ptr)->_r48->_mult) +#define _REENT_RAND48_ADD(ptr) ((ptr)->_r48->_add) +#define _REENT_MP_RESULT(ptr) ((ptr)->_mp->_result) +#define _REENT_MP_RESULT_K(ptr) ((ptr)->_mp->_result_k) +#define _REENT_MP_P5S(ptr) ((ptr)->_mp->_p5s) +#define _REENT_MP_FREELIST(ptr) ((ptr)->_mp->_freelist) +#define _REENT_ASCTIME_BUF(ptr) ((ptr)->_asctime_buf) +#define _REENT_TM(ptr) ((ptr)->_localtime_buf) +#define _REENT_STRTOK_LAST(ptr) ((ptr)->_misc->_strtok_last) +#define _REENT_MBLEN_STATE(ptr) ((ptr)->_misc->_mblen_state) +#define _REENT_MBTOWC_STATE(ptr)((ptr)->_misc->_mbtowc_state) +#define _REENT_WCTOMB_STATE(ptr)((ptr)->_misc->_wctomb_state) +#define _REENT_MBRLEN_STATE(ptr) ((ptr)->_misc->_mbrlen_state) +#define _REENT_MBRTOWC_STATE(ptr) ((ptr)->_misc->_mbrtowc_state) +#define _REENT_MBSRTOWCS_STATE(ptr) ((ptr)->_misc->_mbsrtowcs_state) +#define _REENT_WCRTOMB_STATE(ptr) ((ptr)->_misc->_wcrtomb_state) +#define _REENT_WCSRTOMBS_STATE(ptr) ((ptr)->_misc->_wcsrtombs_state) +#define _REENT_L64A_BUF(ptr) ((ptr)->_misc->_l64a_buf) +#define _REENT_GETDATE_ERR_P(ptr) (&((ptr)->_misc->_getdate_err)) +#define _REENT_SIGNAL_BUF(ptr) ((ptr)->_signal_buf) + +#else /* !_REENT_SMALL */ + +struct _reent +{ + int _errno; /* local copy of errno */ + + /* FILE is a big struct and may change over time. To try to achieve binary + compatibility with future versions, put stdin,stdout,stderr here. + These are pointers into member __sf defined below. */ + __FILE *_stdin, *_stdout, *_stderr; + + int _inc; /* used by tmpnam */ + char _emergency[_REENT_EMERGENCY_SIZE]; + +#ifdef _REENT_BACKWARD_BINARY_COMPAT + int _reserved_1; +#endif + struct __locale_t *_locale;/* per-thread locale */ + +#ifdef _REENT_BACKWARD_BINARY_COMPAT + int _reserved_0; +#endif + + void (*__cleanup) (struct _reent *); + + /* used by mprec routines */ + struct _Bigint *_result; + int _result_k; + struct _Bigint *_p5s; + struct _Bigint **_freelist; + + /* used by some fp conversion routines */ + int _cvtlen; /* should be size_t */ + char *_cvtbuf; + + union + { + struct + { +#ifdef _REENT_BACKWARD_BINARY_COMPAT + unsigned int _reserved_2; +#endif + char * _strtok_last; + char _asctime_buf[_REENT_ASCTIME_SIZE]; + struct __tm _localtime_buf; + int _gamma_signgam; + __extension__ unsigned long long _rand_next; + struct _rand48 _r48; + _mbstate_t _mblen_state; + _mbstate_t _mbtowc_state; + _mbstate_t _wctomb_state; + char _l64a_buf[8]; + char _signal_buf[_REENT_SIGNAL_SIZE]; + int _getdate_err; + _mbstate_t _mbrlen_state; + _mbstate_t _mbrtowc_state; + _mbstate_t _mbsrtowcs_state; + _mbstate_t _wcrtomb_state; + _mbstate_t _wcsrtombs_state; + int _h_errno; + } _reent; +#ifdef _REENT_BACKWARD_BINARY_COMPAT + struct + { + unsigned char * _reserved_3[30]; + unsigned int _reserved_4[30]; + } _reserved_5; +#endif + } _new; + +#ifdef _REENT_BACKWARD_BINARY_COMPAT + struct _atexit *_reserved_6; + struct _atexit _reserved_7; +#endif + + /* signal info */ + void (**_sig_func)(int); +}; + +#define _REENT_INIT(var) \ + { 0, \ + &__sf[0], \ + &__sf[1], \ + &__sf[2], \ + 0, \ + "", \ + _REENT_INIT_RESERVED_1 \ + _NULL, \ + _REENT_INIT_RESERVED_0 \ + _NULL, \ + _NULL, \ + 0, \ + _NULL, \ + _NULL, \ + 0, \ + _NULL, \ + { \ + { \ + _REENT_INIT_RESERVED_2 \ + _NULL, \ + "", \ + {0, 0, 0, 0, 0, 0, 0, 0, 0}, \ + 0, \ + 1, \ + { \ + {_RAND48_SEED_0, _RAND48_SEED_1, _RAND48_SEED_2}, \ + {_RAND48_MULT_0, _RAND48_MULT_1, _RAND48_MULT_2}, \ + _RAND48_ADD \ + }, \ + {0, {0}}, \ + {0, {0}}, \ + {0, {0}}, \ + "", \ + "", \ + 0, \ + {0, {0}}, \ + {0, {0}}, \ + {0, {0}}, \ + {0, {0}}, \ + {0, {0}} \ + } \ + }, \ + _REENT_INIT_RESERVED_6_7 \ + _NULL \ + } + +#define _REENT_INIT_PTR_ZEROED(var) \ + { (var)->_stdin = &__sf[0]; \ + (var)->_stdout = &__sf[1]; \ + (var)->_stderr = &__sf[2]; \ + (var)->_new._reent._rand_next = 1; \ + (var)->_new._reent._r48._seed[0] = _RAND48_SEED_0; \ + (var)->_new._reent._r48._seed[1] = _RAND48_SEED_1; \ + (var)->_new._reent._r48._seed[2] = _RAND48_SEED_2; \ + (var)->_new._reent._r48._mult[0] = _RAND48_MULT_0; \ + (var)->_new._reent._r48._mult[1] = _RAND48_MULT_1; \ + (var)->_new._reent._r48._mult[2] = _RAND48_MULT_2; \ + (var)->_new._reent._r48._add = _RAND48_ADD; \ + } + +#define _REENT_CHECK_RAND48(ptr) /* nothing */ +#define _REENT_CHECK_MP(ptr) /* nothing */ +#define _REENT_CHECK_TM(ptr) /* nothing */ +#define _REENT_CHECK_ASCTIME_BUF(ptr) /* nothing */ +#define _REENT_CHECK_EMERGENCY(ptr) /* nothing */ +#define _REENT_CHECK_MISC(ptr) /* nothing */ +#define _REENT_CHECK_SIGNAL_BUF(ptr) /* nothing */ + +#define _REENT_SIGNGAM(ptr) ((ptr)->_new._reent._gamma_signgam) +#define _REENT_RAND_NEXT(ptr) ((ptr)->_new._reent._rand_next) +#define _REENT_RAND48_SEED(ptr) ((ptr)->_new._reent._r48._seed) +#define _REENT_RAND48_MULT(ptr) ((ptr)->_new._reent._r48._mult) +#define _REENT_RAND48_ADD(ptr) ((ptr)->_new._reent._r48._add) +#define _REENT_MP_RESULT(ptr) ((ptr)->_result) +#define _REENT_MP_RESULT_K(ptr) ((ptr)->_result_k) +#define _REENT_MP_P5S(ptr) ((ptr)->_p5s) +#define _REENT_MP_FREELIST(ptr) ((ptr)->_freelist) +#define _REENT_ASCTIME_BUF(ptr) ((ptr)->_new._reent._asctime_buf) +#define _REENT_TM(ptr) (&(ptr)->_new._reent._localtime_buf) +#define _REENT_STRTOK_LAST(ptr) ((ptr)->_new._reent._strtok_last) +#define _REENT_MBLEN_STATE(ptr) ((ptr)->_new._reent._mblen_state) +#define _REENT_MBTOWC_STATE(ptr)((ptr)->_new._reent._mbtowc_state) +#define _REENT_WCTOMB_STATE(ptr)((ptr)->_new._reent._wctomb_state) +#define _REENT_MBRLEN_STATE(ptr)((ptr)->_new._reent._mbrlen_state) +#define _REENT_MBRTOWC_STATE(ptr)((ptr)->_new._reent._mbrtowc_state) +#define _REENT_MBSRTOWCS_STATE(ptr)((ptr)->_new._reent._mbsrtowcs_state) +#define _REENT_WCRTOMB_STATE(ptr)((ptr)->_new._reent._wcrtomb_state) +#define _REENT_WCSRTOMBS_STATE(ptr)((ptr)->_new._reent._wcsrtombs_state) +#define _REENT_L64A_BUF(ptr) ((ptr)->_new._reent._l64a_buf) +#define _REENT_SIGNAL_BUF(ptr) ((ptr)->_new._reent._signal_buf) +#define _REENT_GETDATE_ERR_P(ptr) (&((ptr)->_new._reent._getdate_err)) + +#endif /* !_REENT_SMALL */ + +#define _REENT_CLEANUP(_ptr) ((_ptr)->__cleanup) +#define _REENT_CVTBUF(_ptr) ((_ptr)->_cvtbuf) +#define _REENT_CVTLEN(_ptr) ((_ptr)->_cvtlen) +#define _REENT_EMERGENCY(_ptr) ((_ptr)->_emergency) +#define _REENT_ERRNO(_ptr) ((_ptr)->_errno) +#define _REENT_INC(_ptr) ((_ptr)->_inc) +#define _REENT_LOCALE(_ptr) ((_ptr)->_locale) +#define _REENT_SIG_FUNC(_ptr) ((_ptr)->_sig_func) +#define _REENT_STDIN(_ptr) ((_ptr)->_stdin) +#define _REENT_STDOUT(_ptr) ((_ptr)->_stdout) +#define _REENT_STDERR(_ptr) ((_ptr)->_stderr) + +#define _REENT_INIT_PTR(var) \ + { memset((var), 0, sizeof(*(var))); \ + _REENT_INIT_PTR_ZEROED(var); \ + } + +/* + * All references to struct _reent are via this pointer. + * Internally, newlib routines that need to reference it should use _REENT. + */ + +#ifndef __ATTRIBUTE_IMPURE_PTR__ +#define __ATTRIBUTE_IMPURE_PTR__ +#endif + +extern struct _reent *_impure_ptr __ATTRIBUTE_IMPURE_PTR__; + +#ifndef __ATTRIBUTE_IMPURE_DATA__ +#define __ATTRIBUTE_IMPURE_DATA__ +#endif + +extern struct _reent _impure_data __ATTRIBUTE_IMPURE_DATA__; + +/* #define _REENT_ONLY define this to get only reentrant routines */ + +#if defined(__DYNAMIC_REENT__) && !defined(__SINGLE_THREAD__) +#ifndef __getreent + struct _reent * __getreent (void); +#endif +# define _REENT (__getreent()) +#else /* __SINGLE_THREAD__ || !__DYNAMIC_REENT__ */ +# define _REENT _impure_ptr +#endif /* __SINGLE_THREAD__ || !__DYNAMIC_REENT__ */ + +#define _REENT_IS_NULL(_ptr) ((_ptr) == NULL) + +#define _GLOBAL_REENT (&_impure_data) + +#else /* _REENT_THREAD_LOCAL */ + +#define _REENT NULL +#define _GLOBAL_REENT NULL + +/* + * Since _REENT is defined as NULL, this macro ensures that calls to + * CHECK_INIT() do not automatically fail. + */ +#define _REENT_IS_NULL(_ptr) 0 + +#define _REENT_CHECK_RAND48(ptr) /* nothing */ +#define _REENT_CHECK_MP(ptr) /* nothing */ +#define _REENT_CHECK_TM(ptr) /* nothing */ +#define _REENT_CHECK_ASCTIME_BUF(ptr) /* nothing */ +#define _REENT_CHECK_EMERGENCY(ptr) /* nothing */ +#define _REENT_CHECK_MISC(ptr) /* nothing */ +#define _REENT_CHECK_SIGNAL_BUF(ptr) /* nothing */ + +extern _Thread_local char _tls_asctime_buf[_REENT_ASCTIME_SIZE]; +#define _REENT_ASCTIME_BUF(_ptr) (_tls_asctime_buf) +extern _Thread_local char *_tls_cvtbuf; +#define _REENT_CVTBUF(_ptr) (_tls_cvtbuf) +extern _Thread_local int _tls_cvtlen; +#define _REENT_CVTLEN(_ptr) (_tls_cvtlen) +extern _Thread_local void (*_tls_cleanup)(struct _reent *); +#define _REENT_CLEANUP(_ptr) (_tls_cleanup) +extern _Thread_local char _tls_emergency; +#define _REENT_EMERGENCY(_ptr) (_tls_emergency) +extern _Thread_local int _tls_errno; +#define _REENT_ERRNO(_ptr) (_tls_errno) +extern _Thread_local int _tls_getdate_err; +#define _REENT_GETDATE_ERR_P(_ptr) (&_tls_getdate_err) +extern _Thread_local int _tls_inc; +#define _REENT_INC(_ptr) (_tls_inc) +extern _Thread_local char _tls_l64a_buf[8]; +#define _REENT_L64A_BUF(_ptr) (_tls_l64a_buf) +extern _Thread_local struct __locale_t *_tls_locale; +#define _REENT_LOCALE(_ptr) (_tls_locale) +extern _Thread_local _mbstate_t _tls_mblen_state; +#define _REENT_MBLEN_STATE(_ptr) (_tls_mblen_state) +extern _Thread_local _mbstate_t _tls_mbrlen_state; +#define _REENT_MBRLEN_STATE(_ptr) (_tls_mbrlen_state) +extern _Thread_local _mbstate_t _tls_mbrtowc_state; +#define _REENT_MBRTOWC_STATE(_ptr) (_tls_mbrtowc_state) +extern _Thread_local _mbstate_t _tls_mbsrtowcs_state; +#define _REENT_MBSRTOWCS_STATE(_ptr) (_tls_mbsrtowcs_state) +extern _Thread_local _mbstate_t _tls_mbtowc_state; +#define _REENT_MBTOWC_STATE(_ptr) (_tls_mbtowc_state) +extern _Thread_local struct _Bigint **_tls_mp_freelist; +#define _REENT_MP_FREELIST(_ptr) (_tls_mp_freelist) +extern _Thread_local struct _Bigint *_tls_mp_p5s; +#define _REENT_MP_P5S(_ptr) (_tls_mp_p5s) +extern _Thread_local struct _Bigint *_tls_mp_result; +#define _REENT_MP_RESULT(_ptr) (_tls_mp_result) +extern _Thread_local int _tls_mp_result_k; +#define _REENT_MP_RESULT_K(_ptr) (_tls_mp_result_k) +extern _Thread_local unsigned short _tls_rand48_add; +#define _REENT_RAND48_ADD(_ptr) (_tls_rand48_add) +extern _Thread_local unsigned short _tls_rand48_mult[3]; +#define _REENT_RAND48_MULT(_ptr) (_tls_rand48_mult) +extern _Thread_local unsigned short _tls_rand48_seed[3]; +#define _REENT_RAND48_SEED(_ptr) (_tls_rand48_seed) +extern _Thread_local unsigned long long _tls_rand_next; +#define _REENT_RAND_NEXT(_ptr) (_tls_rand_next) +extern _Thread_local void (**_tls_sig_func)(int); +#define _REENT_SIG_FUNC(_ptr) (_tls_sig_func) +extern _Thread_local char _tls_signal_buf[_REENT_SIGNAL_SIZE]; +#define _REENT_SIGNAL_BUF(_ptr) (_tls_signal_buf) +extern _Thread_local int _tls_gamma_signgam; +#define _REENT_SIGNGAM(_ptr) (_tls_gamma_signgam) +extern _Thread_local __FILE *_tls_stdin; +#define _REENT_STDIN(_ptr) (_tls_stdin) +extern _Thread_local __FILE *_tls_stdout; +#define _REENT_STDOUT(_ptr) (_tls_stdout) +extern _Thread_local __FILE *_tls_stderr; +#define _REENT_STDERR(_ptr) (_tls_stderr) +extern _Thread_local char *_tls_strtok_last; +#define _REENT_STRTOK_LAST(_ptr) (_tls_strtok_last) +extern _Thread_local struct __tm _tls_localtime_buf; +#define _REENT_TM(_ptr) (&_tls_localtime_buf) +extern _Thread_local _mbstate_t _tls_wctomb_state; +#define _REENT_WCTOMB_STATE(_ptr) (_tls_wctomb_state) +extern _Thread_local _mbstate_t _tls_wcrtomb_state; +#define _REENT_WCRTOMB_STATE(_ptr) (_tls_wcrtomb_state) +extern _Thread_local _mbstate_t _tls_wcsrtombs_state; +#define _REENT_WCSRTOMBS_STATE(_ptr) (_tls_wcsrtombs_state) + +#endif /* !_REENT_THREAD_LOCAL */ + +/* This value is used in stdlib/misc.c. reent/reent.c has to know it + as well to make sure the freelist is correctly free'd. Therefore + we define it here, rather than in stdlib/misc.c, as before. */ +#define _Kmax (sizeof (size_t) << 3) + +extern struct _atexit *__atexit; /* points to head of LIFO stack */ +extern struct _atexit __atexit0; /* one guaranteed table, required by ANSI */ + +extern void (*__stdio_exit_handler) (void); + +void _reclaim_reent (struct _reent *); + +extern int _fwalk_sglue (struct _reent *, int (*)(struct _reent *, __FILE *), + struct _glue *); + +#ifdef __cplusplus +} +#endif +#endif /* _SYS_REENT_H_ */ diff --git a/test/test_esp32_file_buffer_fix.py b/test/test_esp32_file_buffer_fix.py index c5b53738e..43c2e8664 100644 --- a/test/test_esp32_file_buffer_fix.py +++ b/test/test_esp32_file_buffer_fix.py @@ -2,6 +2,7 @@ """Portable witnesses for the exact pinned Arduino/newlib buffer admission fix.""" import hashlib import importlib.util +import json from pathlib import Path import subprocess import tempfile @@ -14,6 +15,11 @@ FIX = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(FIX) RAW = (FIXTURES / "vfs_api.cpp").read_text() PATCHED = FIX.patched_vfs_source(RAW) +SDK_VARIANTS = [((2, 0, 17), RAW, PATCHED, FIXTURES)] +for version in ((3, 1, 3), (3, 3, 11)): + fixture = FIXTURES / ".".join(map(str, version)) + raw = json.loads((fixture / "vfs_api.json").read_text(encoding="ascii"))["source"] + SDK_VARIANTS.append((version, raw, FIX.patched_vfs_source(raw, version), fixture)) def method(source, signature): @@ -156,6 +162,23 @@ int main() { ''' +class Node: + def __init__(self, path): self.path = path + def srcnode(self): return self + def get_abspath(self): return str(self.path) +class Env(dict): + def __init__(self, defines, build): + super().__init__(CPPDEFINES=defines) + self.build, self.paths, self.middleware = build, [], [] + def subst(self, value): + assert value == "$BUILD_DIR" + return str(self.build) + def File(self, path): return path + def AppendUnique(self, **kwargs): + for path in kwargs["CPPPATH"]: + if path not in self.paths: self.paths.append(path) + def AddBuildMiddleware(self, callback, pattern): self.middleware.append((callback, pattern)) + class FileBufferFixTest(unittest.TestCase): def compile_run(self, source, *, success=True, compile_failure=False, headers=None): with tempfile.TemporaryDirectory(prefix="mesh-fs-buffer-") as directory: @@ -183,25 +206,31 @@ class FileBufferFixTest(unittest.TestCase): self.assertIn("file.setBufferSize(251)", result.stderr) def test_actual_method_honored_fallback_errors_zero_and_no_io(self): - self.compile_run(METHOD_PREFIX + method(PATCHED, "bool VFSFileImpl::setBufferSize(") + METHOD_MAIN) + for version, _, patched, _ in SDK_VARIANTS: + with self.subTest(version=version): + self.compile_run(METHOD_PREFIX + method(patched, "bool VFSFileImpl::setBufferSize(") + METHOD_MAIN) def test_upstream_success_only_negative_control_rejects_fallback(self): - self.compile_run(METHOD_PREFIX + method(RAW, "bool VFSFileImpl::setBufferSize(") + METHOD_MAIN, - success=False) + for version, raw, _, _ in SDK_VARIANTS: + with self.subTest(version=version): + self.compile_run(METHOD_PREFIX + method(raw, "bool VFSFileImpl::setBufferSize(") + METHOD_MAIN, + success=False) def test_actual_transaction_rejects_writer_and_verifier_fallback_before_io(self): transaction = (ROOT / "src/helpers/ContactFileTransaction.h").read_text().replace( '#include "IdentityStore.h"', '#include "identity_seam.h"').replace( '#include "PersistentStoreFormat.h"', '#include ').replace( '#pragma once\n', '', 1) - self.compile_run(METHOD_PREFIX + method(PATCHED, "bool VFSFileImpl::setBufferSize(") - + TRANSACTION_MOCK + transaction + TRANSACTION_MAIN, - headers={"identity_seam.h": "#pragma once\n"}) + for version, _, patched, _ in SDK_VARIANTS: + with self.subTest(version=version): + self.compile_run(METHOD_PREFIX + method(patched, "bool VFSFileImpl::setBufferSize(") + + TRANSACTION_MOCK + transaction + TRANSACTION_MAIN, + headers={"identity_seam.h": "#pragma once\n"}) - def layout_source(self): + def layout_source(self, fixture=FIXTURES, guard=FIX.LAYOUT_GUARD): # Execute the exact header's reviewed prefix with 32-bit pointer values # on a native 64-bit host. The target copy additionally asserts void*32. - reent = (FIXTURES / "newlib_sys_reent.h").read_text() + reent = (fixture / "newlib_sys_reent.h").read_text() sbuf = method(reent, "struct __sbuf {") + ";\n" start = reent.index("struct __sFILE {") end = reent.index("#ifdef _REENT_SMALL", start) @@ -209,7 +238,7 @@ class FileBufferFixTest(unittest.TestCase): fields = (sbuf + prefix).replace("unsigned char *", "ModelPointer ") source = "#include \nusing ModelPointer = uint32_t;\n" + fields source += "using FILE = __sFILE;\n" - source += FIX.LAYOUT_GUARD.replace("sizeof(void*)", "sizeof(ModelPointer)") + source += guard.replace("sizeof(void*)", "sizeof(ModelPointer)") source += "int main() {}\n" return source @@ -223,6 +252,57 @@ class FileBufferFixTest(unittest.TestCase): self.compile_run(self.layout_source(), headers={"newlib.h": version.replace( "#define __NEWLIB__ 3", "#define __NEWLIB__ 4")}, compile_failure=True) + def test_arduino3_actual_newlib_prefix_rejects_changed_layout_and_abi(self): + fixture = FIXTURES / "newlib4" + version = (fixture / "_newlib_version.h").read_text(encoding="ascii") + source = self.layout_source(fixture, FIX.LAYOUT_GUARD_ARDUINO3) + self.compile_run(source, headers={"newlib.h": version}) + for broken in (source.replace("struct __sbuf _bf;", "int added; struct __sbuf _bf;"), + source.replace("int\t_size;", "long long\t_size;")): + self.compile_run(broken, headers={"newlib.h": version}, compile_failure=True) + self.compile_run(source, headers={"newlib.h": version.replace( + "#define __NEWLIB_MINOR__ 3", "#define __NEWLIB_MINOR__ 4")}, compile_failure=True) + self.assertEqual(hashlib.sha256((fixture / "newlib_sys_reent.h").read_bytes()).hexdigest(), + "c7ba657f911f7e27b7449bda36aa4efa80a025c726768dabb8d4a792f535ec9a") + + def test_every_supported_sdk_uses_its_own_source_pin_and_keeps_shared_sdk_unchanged(self): + with tempfile.TemporaryDirectory(prefix="mesh-fs-multi-sdk-") as directory: + temp = Path(directory) + source = temp / "sdk/libraries/FS/src/vfs_api.cpp" + source.parent.mkdir(parents=True) + header = temp / "sdk/cores/esp32/esp_arduino_version.h" + header.parent.mkdir(parents=True) + node = Node(source) + for version, raw, patched, fixture in SDK_VARIANTS: + with self.subTest(version=version): + original_header = (fixture / "esp_arduino_version.h").read_bytes() + source.write_text(raw, encoding="utf-8") + header.write_bytes(original_header) + self.assertEqual(FIX.require_pinned_framework(source), version) + self.assertTrue(patched.isascii()) + self.assertEqual(FIX.patched_vfs_source(patched, version), patched) + self.assertEqual(FIX.patched_vfs_source(raw.replace("\n", "\r\n"), version), patched) + for mode in (None, 0, 1): + defines = ["ESP32_PLATFORM"] + if mode is not None: + defines += [("ARDUINO_USB_MODE", mode), ("ARDUINO_USB_CDC_ON_BOOT", 1)] + env = Env(defines, temp / (str(version) + str(mode))) + result = Path(FIX.replace_vfs_source(env, node)) + self.assertEqual(result.read_text(encoding="ascii"), patched) + self.assertEqual(source.read_text(encoding="utf-8"), raw) + self.assertEqual(header.read_bytes(), original_header) + before = result.stat().st_mtime_ns + self.assertEqual(Path(FIX.replace_vfs_source(env, node)).stat().st_mtime_ns, before) + for changed in (raw + "\n", patched + "\n", + raw.replace("return res == 0;", "return true;", 1)): + with self.assertRaises(RuntimeError): FIX.patched_vfs_source(changed, version) + for other_version, _, _, _ in SDK_VARIANTS: + if other_version != version: + with self.assertRaises(RuntimeError): FIX.patched_vfs_source(raw, other_version) + header.write_bytes(original_header + b"\n#define ESP_ARDUINO_VERSION_MAJOR 9\n") + with self.assertRaises(RuntimeError): FIX.replace_vfs_source(env, node) + with self.assertRaises(RuntimeError): FIX.patched_vfs_source(RAW, (3, 3, 12)) + def test_exact_fingerprints_shape_normalization_and_idempotence(self): self.assertEqual(hashlib.sha256((FIXTURES / "vfs_api.cpp").read_bytes()).hexdigest(), FIX.PINNED_VFS_SHA256) self.assertEqual(FIX.patched_vfs_source(RAW.replace("\n", "\r\n")), PATCHED) @@ -240,22 +320,6 @@ class FileBufferFixTest(unittest.TestCase): self.assertEqual(hashlib.sha256((FIXTURES / name).read_bytes()).hexdigest(), digest) def test_build_local_copy_all_usb_modes_guards_and_shared_bytes(self): - class Node: - def __init__(self, path): self.path = path - def srcnode(self): return self - def get_abspath(self): return str(self.path) - class Env(dict): - def __init__(self, defines, build): - super().__init__(CPPDEFINES=defines) - self.build, self.paths, self.middleware = build, [], [] - def subst(self, value): - assert value == "$BUILD_DIR" - return str(self.build) - def File(self, path): return path - def AppendUnique(self, **kwargs): - for path in kwargs["CPPPATH"]: - if path not in self.paths: self.paths.append(path) - def AddBuildMiddleware(self, callback, pattern): self.middleware.append((callback, pattern)) with tempfile.TemporaryDirectory(prefix="mesh-fs-copy-") as directory: temp = Path(directory) source = temp / "sdk/libraries/FS/src/vfs_api.cpp"