openmv/common/file_utils.c
iabdalkader ccf6f14e7e common/file_utils: Fix file utils.
- Fix broken buffer_off/on, close etc..

Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
2025-10-15 21:01:29 +02:00

434 lines
13 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2013-2024 OpenMV, LLC.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Filesystem helper functions using MicroPython VFS interface.
*/
#include "imlib_config.h"
#if defined(IMLIB_ENABLE_IMAGE_FILE_IO)
#include <string.h>
#include <unistd.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/builtin.h"
#include "extmod/vfs.h"
#include "omv_common.h"
#include "fb_alloc.h"
#include "file_utils.h"
#define FF_MIN(x, y) (((x) < (y))?(x):(y))
// File buffering support
static uint32_t file_buffer_offset = 0;
static uint8_t *file_buffer_pointer = 0;
static uint32_t file_buffer_size = 0;
static uint32_t file_buffer_index = 0;
static inline void file_cleanup(file_t *fp) {
if (fp && fp->fp != MP_OBJ_NULL) {
file_buffer_init0();
mp_stream_close(fp->fp);
fp->fp = MP_OBJ_NULL;
}
}
// Error/exception functions
NORETURN static void file_read_fail(file_t *fp) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to read requested bytes!"));
}
NORETURN static void file_write_fail(file_t *fp) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to write requested bytes!"));
}
NORETURN static void file_expect_fail(file_t *fp) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unexpected value read!"));
}
NORETURN void file_raise_format(file_t *fp) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format!"));
}
NORETURN void file_raise_corrupted(file_t *fp) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File corrupted!"));
}
NORETURN void file_raise_error(file_t *fp, mp_rom_error_text_t msg) {
file_cleanup(fp);
mp_raise_msg(&mp_type_OSError, msg);
}
// Helper function to perform stream seek
static off_t file_seek_helper(file_t *fp, off_t offset, int whence) {
int err;
const mp_stream_p_t *stream_p = mp_get_stream_raise(fp->fp, MP_STREAM_OP_IOCTL);
struct mp_stream_seek_t seek_s;
seek_s.offset = offset;
seek_s.whence = whence;
mp_uint_t res = stream_p->ioctl(fp->fp, MP_STREAM_SEEK, (mp_uint_t) (uintptr_t) &seek_s, &err);
if (res == MP_STREAM_ERROR) {
file_raise_error(fp, MP_ERROR_TEXT("Seek failed"));
}
return seek_s.offset;
}
// Helper function to read from stream
static size_t file_read_helper(file_t *fp, void *buf, size_t len) {
int err;
const mp_stream_p_t *stream_p = mp_get_stream_raise(fp->fp, MP_STREAM_OP_READ);
mp_uint_t out_sz = stream_p->read(fp->fp, buf, len, &err);
if (out_sz == MP_STREAM_ERROR) {
file_raise_error(fp, MP_ERROR_TEXT("Read failed"));
}
return out_sz;
}
// Helper function to write to stream
static size_t file_write_helper(file_t *fp, const void *buf, size_t len) {
int err;
const mp_stream_p_t *stream_p = mp_get_stream_raise(fp->fp, MP_STREAM_OP_WRITE);
mp_uint_t out_sz = stream_p->write(fp->fp, buf, len, &err);
if (out_sz == MP_STREAM_ERROR) {
file_raise_error(fp, MP_ERROR_TEXT("Write failed"));
}
return out_sz;
}
void file_buffer_init0() {
file_buffer_offset = 0;
file_buffer_pointer = 0;
file_buffer_size = 0;
file_buffer_index = 0;
}
void file_buffer_on(file_t *fp) {
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
off_t current_pos = file_seek_helper(fp, 0, SEEK_CUR);
file_buffer_offset = current_pos % 4;
file_buffer_pointer = (uint8_t *) fb_alloc_all(&file_buffer_size, FB_ALLOC_PREFER_SIZE) + file_buffer_offset;
if (!file_buffer_size) {
mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("No memory!"));
}
file_buffer_size -= file_buffer_offset;
file_buffer_index = 0;
if (fp->flags & FA_READ) {
// Pre-fill buffer for reading
off_t file_end = file_seek_helper(fp, 0, SEEK_END);
file_seek_helper(fp, current_pos, SEEK_SET);
uint32_t file_remaining = file_end - current_pos;
uint32_t can_do = FF_MIN(file_buffer_size, file_remaining);
size_t bytes = file_read_helper(fp, file_buffer_pointer, can_do);
if (bytes != can_do) {
file_read_fail(fp);
}
}
}
void file_buffer_off(file_t *fp) {
// Attempt flush after clearing globals
if (file_buffer_index && fp && fp->fp && (fp->flags & FA_WRITE)) {
size_t bytes = file_write_helper(fp, file_buffer_pointer, file_buffer_index);
if (bytes != file_buffer_index) {
fb_free();
file_write_fail(fp);
}
}
fb_free();
file_buffer_init0();
}
OMV_ATTR_ALWAYS_INLINE static void file_fill(file_t *fp) {
if (file_buffer_index == file_buffer_size) {
file_buffer_pointer -= file_buffer_offset;
file_buffer_size += file_buffer_offset;
file_buffer_offset = 0;
file_buffer_index = 0;
// Calculate remaining bytes in file
off_t current_pos = file_seek_helper(fp, 0, SEEK_CUR);
off_t file_end = file_seek_helper(fp, 0, SEEK_END);
file_seek_helper(fp, current_pos, SEEK_SET);
uint32_t file_remaining = file_end - current_pos;
uint32_t can_do = FF_MIN(file_buffer_size, file_remaining);
size_t bytes = file_read_helper(fp, file_buffer_pointer, can_do);
if (bytes != can_do) {
file_read_fail(fp);
}
}
}
OMV_ATTR_ALWAYS_INLINE static void file_flush(file_t *fp) {
if (file_buffer_index == file_buffer_size) {
size_t bytes = file_write_helper(fp, file_buffer_pointer, file_buffer_index);
if (bytes != file_buffer_index) {
file_write_fail(fp);
}
file_buffer_pointer -= file_buffer_offset;
file_buffer_size += file_buffer_offset;
file_buffer_offset = 0;
file_buffer_index = 0;
}
}
void file_open(file_t *fp, const char *path, bool buffered, uint32_t flags) {
// Initialize fp to safe state in case open fails
fp->fp = MP_OBJ_NULL;
fp->flags = 0;
// Reject unsupported FA_READ | FA_WRITE without creation/append flags
// This combination would silently truncate the file (mode "wb")
if ((flags & (FA_READ | FA_WRITE)) == (FA_READ | FA_WRITE)) {
if (!(flags & (FA_CREATE_ALWAYS | FA_OPEN_APPEND | FA_OPEN_ALWAYS))) {
mp_raise_msg(&mp_type_ValueError,
MP_ERROR_TEXT("FA_READ|FA_WRITE requires FA_OPEN_ALWAYS or FA_CREATE_ALWAYS"));
}
}
// Convert flags to MicroPython mode string
const char *mode;
if (flags & FA_WRITE) {
if (flags & FA_CREATE_ALWAYS) {
mode = "wb";
} else if (flags & FA_OPEN_APPEND) {
mode = "ab";
} else if (flags & FA_OPEN_ALWAYS) {
mode = "r+b";
} else {
mode = "wb";
}
} else {
mode = "rb";
}
// Open file using MicroPython VFS
mp_obj_t args[2] = {
mp_obj_new_str_from_cstr(path),
mp_obj_new_str_from_cstr(mode)
};
fp->fp = mp_vfs_open(MP_ARRAY_SIZE(args), args, (mp_map_t *) &mp_const_empty_map);
fp->flags = (uint8_t) flags;
if (buffered) {
file_buffer_on(fp);
}
}
void file_close(file_t *fp) {
if (fp && fp->fp != MP_OBJ_NULL) {
if (file_buffer_pointer) {
file_buffer_off(fp);
}
mp_stream_close(fp->fp);
fp->fp = MP_OBJ_NULL;
}
}
void file_seek(file_t *fp, size_t offset) {
if (fp && fp->fp) {
file_seek_helper(fp, offset, SEEK_SET);
}
}
void file_truncate(file_t *fp) {
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
// Truncate file at current position by calling Python truncate() method
// Use getattr to get the truncate method
mp_obj_t truncate_str = mp_obj_new_str("truncate", 8);
mp_obj_t truncate_method = mp_load_attr(fp->fp, qstr_from_str(mp_obj_str_get_str(truncate_str)));
// Call truncate() method with no args (truncates at current position)
mp_call_function_0(truncate_method);
}
void file_sync(file_t *fp) {
int err;
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
const mp_stream_p_t *stream_p = mp_get_stream_raise(fp->fp, MP_STREAM_OP_WRITE);
mp_uint_t res = stream_p->ioctl(fp->fp, MP_STREAM_FLUSH, 0, &err);
if (res == MP_STREAM_ERROR) {
file_raise_error(fp, MP_ERROR_TEXT("Flush failed"));
}
}
size_t file_tell(file_t *fp) {
if (file_buffer_pointer) {
off_t stream_pos = file_seek_helper(fp, 0, SEEK_CUR);
if (fp->flags & FA_READ) {
return stream_pos - file_buffer_size + file_buffer_index;
} else {
return stream_pos + file_buffer_index;
}
}
return file_seek_helper(fp, 0, SEEK_CUR);
}
size_t file_size(file_t *fp) {
if (file_buffer_pointer) {
off_t current_pos = file_seek_helper(fp, 0, SEEK_CUR);
off_t file_end = file_seek_helper(fp, 0, SEEK_END);
file_seek_helper(fp, current_pos, SEEK_SET);
if (fp->flags & FA_READ) {
return file_end;
} else {
return file_end + file_buffer_index;
}
}
off_t current_pos = file_seek_helper(fp, 0, SEEK_CUR);
off_t file_end = file_seek_helper(fp, 0, SEEK_END);
file_seek_helper(fp, current_pos, SEEK_SET);
return file_end;
}
bool file_eof(file_t *fp) {
if (file_buffer_pointer && (fp->flags & FA_READ)) {
return file_buffer_index >= file_buffer_size;
}
off_t current_pos = file_seek_helper(fp, 0, SEEK_CUR);
off_t file_end = file_seek_helper(fp, 0, SEEK_END);
file_seek_helper(fp, current_pos, SEEK_SET);
return current_pos >= file_end;
}
void file_read(file_t *fp, void *data, size_t size) {
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
if (data == NULL) {
// Skip bytes
uint8_t byte;
if (file_buffer_pointer) {
for (size_t i = 0; i < size; i++) {
file_fill(fp);
byte = file_buffer_pointer[file_buffer_index++];
}
} else {
for (size_t i = 0; i < size; i++) {
size_t bytes = file_read_helper(fp, &byte, 1);
if (bytes != 1) {
file_read_fail(fp);
}
}
}
return;
}
if (file_buffer_pointer) {
if (size <= 4) {
for (size_t i = 0; i < size; i++) {
file_fill(fp);
((uint8_t *) data)[i] = file_buffer_pointer[file_buffer_index++];
}
} else {
while (size) {
file_fill(fp);
uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index;
uint32_t can_do = FF_MIN(size, file_buffer_space_left);
memcpy(data, file_buffer_pointer + file_buffer_index, can_do);
file_buffer_index += can_do;
data = ((uint8_t *) data) + can_do;
size -= can_do;
}
}
} else {
size_t bytes = file_read_helper(fp, data, size);
if (bytes != size) {
file_read_fail(fp);
}
}
}
void file_write(file_t *fp, const void *data, size_t size) {
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
if (file_buffer_pointer) {
// Buffer writes for performance
while (size) {
uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index;
uint32_t can_do = FF_MIN(size, file_buffer_space_left);
memcpy(file_buffer_pointer + file_buffer_index, data, can_do);
file_buffer_index += can_do;
data = ((const uint8_t *) data) + can_do;
size -= can_do;
file_flush(fp);
}
} else {
size_t bytes = file_write_helper(fp, data, size);
if (bytes != size) {
file_write_fail(fp);
}
}
}
void file_write_byte(file_t *fp, uint8_t value) {
file_write(fp, &value, 1);
}
void file_write_short(file_t *fp, uint16_t value) {
file_write(fp, &value, 2);
}
void file_write_long(file_t *fp, uint32_t value) {
file_write(fp, &value, 4);
}
void file_read_check(file_t *fp, const void *data, size_t size) {
if (!fp || fp->fp == MP_OBJ_NULL) {
return;
}
uint8_t buf[16];
while (size) {
size_t len = OMV_MIN(sizeof(buf), size);
file_read(fp, buf, len);
if (memcmp(data, buf, len)) {
file_expect_fail(fp);
}
size -= len;
data = ((const uint8_t *) data) + len;
}
}
#endif //IMLIB_ENABLE_IMAGE_FILE_IO