The following compares 2 simple code snippets in the first one, there is a lifecycle memory leak at the (0 == read_bytes) predicate.
[Header]
/**
* @brief A completely memory-safe not internally synchronized API that provides a dynamic
* way of reading and writing to files without a [realloc] overhead; by delegating memory
* allocation as a part of the pre-processor states to the caller, and the memory deallocation or further reallocation calls
* as a part of the accepting states to another function of the caller API or Application.
* @note Synchronization (using polling or mutexes) could be introduced through the lifecycle struct
* [file_op_processor] for file operation processor.
* @author pavl_g.
* @date 2025-10
*/
#ifndef _FILE_OPERATIONS_H_
#define _FILE_OPERATIONS_H_
#ifndef _ELECTRO_MIO
#ifdef __ANDROID__
#define __off_t size_t
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <electrostatic/electronetsoft/util/utilities.h>
struct read_op_processor {
void (*op_preprocessor)(file_mem *, void *);
void (*op_postprocessor)(file_mem *, void *);
void (*on_bytes_processed)(file_mem *, ssize_t, void *); /* Executed on each successful read operation. */
void (*on_eof_reached)(file_mem *); /* Executed when EOF is reached. Could be used to chain calls to memory deallocators. */
void (*on_last_char_sampled)(file_mem *, void *caller);
void (*on_error_encountered)(file_mem *, int, void *caller);
};
struct write_op_processor {
void (*op_preprocessor)(file_mem *, void *);
void (*op_postprocessor)(file_mem *, void *);
void (*on_bytes_processed)(file_mem *, ssize_t, void *); /* Executed on each successful read operation. */
void (*on_eob_reached)(file_mem *);
void (*on_last_char_sampled)(file_mem *, void *caller);
void (*on_error_encountered)(file_mem *, int, void *caller);
};
struct update_op_processor {
void (*on_update_nbytes)(file_mem *, ssize_t); /* Executed when updating the number of bytes attr (of the buffer for the file mem model) is commenced */
void (*on_update_size)(file_mem *, __off_t); /* Executed when updating size is commenced. */
void (*on_update_pos)(file_mem *, __off_t); /* Executed when updating the position is commenced. */
void (*update_model_preprocessor)(file_mem *, void *caller); /* Executed */
void (*update_model_postprocessor)(file_mem *, void *caller); /* Executed after the file mem model attrs update has finished. Could be used to chain calls to memory allocators. */
};
struct serializer_op_processor {
void (*serializer_init_preprocessor)(pipe_serializer *);
void (*serializer_init_postprocessor)(pipe_serializer *);
};
struct file_mem {
int fd;
char trailing; /* The trailing byte of the buffer.*/
ssize_t n_bytes; /* Total number of bytes to allocate for the buffer in memory
* including the number of read or write bytes
* starting from the current pos based on
* the file size, and the trailing byte. */
ssize_t bytes_processed;
char *buffer;
__off_t file_size; /* Cached file size in bytes */
__off_t file_pos; /* Cached file position */
uint8_t __auto_update_attrs; /* Conditional flag to specify whether auto-update
* in the pre-processing stage is enabled. */
uint8_t __continue_after_eof; /* Conditional flag to specify whether to continue after the EOF. */
};
/**
* @brief A pipe-serializer object creates a pipe that has
* a write end and read end; both ends are mapped to the same
* filesystems, in which the write operations are available to be read
* by the memory model processor. Therefore, both memory models utilize
* a shared memory buffer, but they possess different filesystems and thus
* different file positions.
*/
struct pipe_serializer {
file_mem read_end;
file_mem write_end;
};
/**
* @brief Delegates to allocate a heap buffer and reads a file identified by this file descriptor
* into the heap memory; returning the memory address into the
* [buffer] pointer of the file memory model object.
*
* @param mem a pointer to the file memory model struct.
* @param _processor a pointer to the file read operations processor; that links the API to the user application.
* @param __processor a pointer to the file attrs update processors.
*/
status_code read_into_mem(file_mem *, read_op_processor *, update_op_processor *);
status_code update_file_attrs(file_mem *, update_op_processor *);
/**
* @brief Writes a pre-allocated buffer to a file identified by a file descriptor from
* the file memory model structure; until reaching the trailing character which is excluded
* from the this write operation and marks the accepting state of this machine.
*
* @param mem a pointer to the file memory model struct.
* @param processor a pointer to the file operations processor; that links the API to the user application.
*/
status_code write_from_mem(file_mem *, write_op_processor *, update_op_processor *);
status_code init_serializer(pipe_serializer *, serializer_op_processor *, update_op_processor *);
#ifdef __cplusplus
}
#endif
#endif
#endif
Note
Notice, the op_postprocessor algorithm is unreachable.
#include <electrostatic/electronetsoft/util/filesystem/file_operations.h>
#include <electrostatic/electronetsoft/util/filesystem/file_status.h>
#include <electrostatic/electronetsoft/util/filesystem/file_verify.h>
status_code read_into_mem(file_mem *mem,
read_op_processor *_processor,
update_op_processor *__processor) {
// pre-processing automata -- Input validation
if (rvalue(mem) == NULL) {
return EUNDEFINEDBUFFER;
}
if (NULL != _processor && NULL != _processor->op_preprocessor) {
_processor->op_preprocessor(mem, &read_into_mem);
}
// pre-processing automata -- Calculating the file size, current position,
// and the number of bytes to read
if (mem->__auto_update_attrs) {
status_code ___status = update_file_attrs(mem, __processor);
if (PASS != ___status) {
return ___status;
}
}
if (NULL == mem->buffer) {
return EUNDEFINEDBUFFER;
}
// processing automata -- Reading n_bytes into heap buffer
// starting from the current file position
ssize_t read_bytes = 0;
ssize_t total_bytes = 0;
while (1) {
read_bytes = read(mem->fd, (mem->buffer + total_bytes), /* Advance linearly over the File Mem model buffer
* with the read bytes */
(mem->n_bytes - 1) - total_bytes); /* Retro-advance on the number of available bytes
* on the corresponding physical file model. */
total_bytes += read_bytes;
mem->bytes_processed = total_bytes;
// post processing automata
if (-1 == read_bytes) {
// terminated with error; report error!
if (NULL != _processor && NULL != _processor->on_error_encountered) {
_processor->on_error_encountered(mem, errno, &read_into_mem);
}
return errno;
} else if (0 == read_bytes) {
// EOF terminate!
mem->buffer[total_bytes] = mem->trailing; /* add a null-terminating character */
if (NULL != _processor && NULL != _processor->on_eof_reached) {
_processor->on_eof_reached(mem);
}
// post-processing sub-state (HACK!)
if (mem->__continue_after_eof) {
continue;
}
>>>> return PASS; <<<<
} else if (read_bytes > 0) {
// execute on_read
if (NULL != _processor && NULL != _processor->on_bytes_processed) {
_processor->on_bytes_processed(mem, read_bytes, &read_into_mem);
}
// sample out if all the requested characters are read
// even though the EOF is not reached, yet.
if ((mem->n_bytes - 1) == total_bytes) {
mem->buffer[total_bytes] = mem->trailing; /* add a null-terminating character */
if (NULL != _processor && NULL != _processor->on_last_char_sampled) {
_processor->on_last_char_sampled(mem, &read_into_mem);
}
}
} else {
return UNEXPECTED_ERROR;
}
}
if (NULL != _processor && NULL != _processor->op_postprocessor) {
_processor->op_postprocessor(mem, &read_into_mem);
}
return PASS;
}
#include <electrostatic/electronetsoft/util/filesystem/file_operations.h>
#include <electrostatic/electronetsoft/util/filesystem/file_status.h>
#include <electrostatic/electronetsoft/util/filesystem/file_verify.h>
status_code read_into_mem(file_mem *mem,
read_op_processor *_processor,
update_op_processor *__processor) {
// pre-processing automata -- Input validation
if (rvalue(mem) == NULL) {
return EUNDEFINEDBUFFER;
}
if (NULL != _processor && NULL != _processor->op_preprocessor) {
_processor->op_preprocessor(mem, &read_into_mem);
}
// pre-processing automata -- Calculating the file size, current position,
// and the number of bytes to read
if (mem->__auto_update_attrs) {
status_code ___status = update_file_attrs(mem, __processor);
if (PASS != ___status) {
return ___status;
}
}
if (NULL == mem->buffer) {
return EUNDEFINEDBUFFER;
}
// processing automata -- Reading n_bytes into heap buffer
// starting from the current file position
ssize_t read_bytes = 0;
ssize_t total_bytes = 0;
while (1) {
read_bytes = read(mem->fd, (mem->buffer + total_bytes), /* Advance linearly over the File Mem model buffer
* with the read bytes */
(mem->n_bytes - 1) - total_bytes); /* Retro-advance on the number of available bytes
* on the corresponding physical file model. */
total_bytes += read_bytes;
mem->bytes_processed = total_bytes;
// post processing automata
if (-1 == read_bytes) {
// terminated with error; report error!
if (NULL != _processor && NULL != _processor->on_error_encountered) {
_processor->on_error_encountered(mem, errno, &read_into_mem);
}
return errno;
} else if (0 == read_bytes) {
// EOF terminate!
mem->buffer[total_bytes] = mem->trailing; /* add a null-terminating character */
if (NULL != _processor && NULL != _processor->on_eof_reached) {
_processor->on_eof_reached(mem);
}
// post-processing sub-state (HACK!)
if (mem->__continue_after_eof) {
continue;
}
>>>> break; <<<<
} else if (read_bytes > 0) {
// execute on_read
if (NULL != _processor && NULL != _processor->on_bytes_processed) {
_processor->on_bytes_processed(mem, read_bytes, &read_into_mem);
}
// sample out if all the requested characters are read
// even though the EOF is not reached, yet.
if ((mem->n_bytes - 1) == total_bytes) {
mem->buffer[total_bytes] = mem->trailing; /* add a null-terminating character */
if (NULL != _processor && NULL != _processor->on_last_char_sampled) {
_processor->on_last_char_sampled(mem, &read_into_mem);
}
}
} else {
return UNEXPECTED_ERROR;
}
}
if (NULL != _processor && NULL != _processor->op_postprocessor) {
_processor->op_postprocessor(mem, &read_into_mem);
}
return PASS;
}if (0 == read_bytes) {
// EOF terminate!
mem->buffer[total_bytes] = mem->trailing; /* add a null-terminating character */
if (NULL != _processor && NULL != _processor->on_eof_reached) {
_processor->on_eof_reached(mem);
}
// post-processing sub-state (HACK!)
if (mem->__continue_after_eof) {
continue;
}
>>>> return PASS; <<<<
}This prematurely returns from the reading routine without executing the op_postprocessor postprocessing algorithms which could be used to decrypt, decode, transfer the buffer to other entity or just free the memory. In cases of simple techdemos, it's utilized to copy the buffer data to the stdout buffer, dellocate the buffer from the heap memory, and close the file base protocol.