Skip to content

Commit e45db8a

Browse files
Introduce optional RAM disk for file I/O
Adds a new build option, `FORTE_FILEIO_RAMDISK`, to enable an in-memory file system using `fmemopen`. This allows files to be loaded into RAM and accessed via standard file I/O functions. This feature is particularly useful for embedded systems or performance-critical scenarios where traditional file system access is not feasible or desired. Enabling the option requires the `fmemopen` function to be available on the target system.
1 parent 1a034ea commit e45db8a

9 files changed

Lines changed: 325 additions & 4 deletions

File tree

.github/workflows/cmake-multi-platform.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ jobs:
3939
c_compiler: clang
4040
cpp_compiler: clang++
4141
architecture: "posix"
42+
- os: ubuntu-latest
43+
c_compiler: gcc
44+
cpp_compiler: g++
45+
architecture: "posix-systemtests-ramdisk"
4246
exclude:
4347
- os: windows-latest
4448
c_compiler: gcc
@@ -77,7 +81,7 @@ jobs:
7781
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
7882
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON
7983
-S ${{ github.workspace }}
80-
--preset ${{ matrix.architecture }}-systemtests
84+
--preset ${{ matrix.architecture == 'posix-systemtests-ramdisk' && 'posix-systemtests-ramdisk' || format('{0}-systemtests', matrix.architecture) }}
8185
${{ matrix.os == 'windows-latest' && format('-DFORTE_TESTS_INC_DIRS={0}/boost.1.84.0/lib/native/include', github.workspace) || '' }}
8286
8387
- name: Build

CMakePresets.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,20 @@
230230
"posix"
231231
]
232232
},
233+
{
234+
"name": "posix-systemtests-ramdisk",
235+
"displayName": "System Tests (Posix, RAMDISK)",
236+
"inherits": [
237+
"systemtests",
238+
"posix"
239+
],
240+
"cacheVariables": {
241+
"FORTE_FILEIO_RAMDISK": {
242+
"type": "BOOL",
243+
"value": "ON"
244+
}
245+
}
246+
},
233247
{
234248
"name": "windows",
235249
"hidden": true,

Jenkinsfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,19 @@ spec:
4949
}
5050
}
5151
}
52+
stage('Build static RAMDISK') {
53+
steps {
54+
container('build') {
55+
cmakeBuild installation: 'CMake 3.14.5', buildDir: 'static-ramdisk', generator: 'Unix Makefiles', buildType: 'Debug', cmakeArgs: '-DFORTE_ARCHITECTURE=Posix -DFORTE_TESTS=ON -DFORTE_SYSTEM_TESTS=ON -DFORTE_FILEIO_RAMDISK=ON -DFORTE_LOGLEVEL=LOGDEBUG -DFORTE_EventChainExternalEventListSize=32 -DFORTE_MODULE_CONVERT=ON -DFORTE_MODULE_IEC61131=ON -DFORTE_MODULE_UTILS=ON -DFORTE_MODULE_RT_Events=ON -DFORTE_MODULE_RECONFIGURATION=ON -DFORTE_IO=ON -DFORTE_COM_ETH=ON -DFORTE_COM_FBDK=ON -DFORTE_COM_LOCAL=ON -DFORTE_COM_RAW=ON -DFORTE_COM_HTTP=ON -DFORTE_COM_OPC_UA=ON -DFORTE_COM_OPC_UA_INCLUDE_DIR=${WORKDIR}/open62541/binStatic -DFORTE_COM_OPC_UA_LIB_DIR=${WORKDIR}/open62541/binStatic/bin -DFORTE_COM_OPC_UA_LIB=libopen62541.a', steps: [[args: 'all']]
56+
}
57+
}
58+
}
59+
stage('Test static RAMDISK') {
60+
steps {
61+
container('build') {
62+
ctest installation: 'CMake 3.14.5', workingDir: 'static-ramdisk', arguments: '--verbose'
63+
}
64+
}
65+
}
5266
}
5367
}

core/arch/common/include/forte/arch/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#############################################################################
2-
# Copyright (c) 2025 Martin Erich Jobst
2+
# Copyright (c) 2025 Martin Erich Jobst and others
33
#
44
# This program and the accompanying materials are made available under the
55
# terms of the Eclipse Public License 2.0 which is available at
@@ -21,6 +21,7 @@ target_sources(forte-core PUBLIC
2121
forte_printer.h
2222
forte_specific_architecture.h
2323
threadbase.h
24+
$<$<BOOL:${FORTE_FILEIO_RAMDISK}>:${CMAKE_CURRENT_SOURCE_DIR}/forte_ramdisk.h>
2425
)
2526

2627
set_source_files_properties(
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agrartechnik GmbH
3+
* This program and the accompanying materials are made available under the
4+
* terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0.
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Franz Höpfinger
11+
* - initial API and implementation and/or initial documentation
12+
*******************************************************************************/
13+
14+
#pragma once
15+
16+
#ifdef __cplusplus
17+
extern "C" {
18+
#endif
19+
20+
int forte_ramdisk_load(const char *filename);
21+
void forte_ramdisk_unload(const char *filename);
22+
23+
#ifdef __cplusplus
24+
}
25+
#endif
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agrartechnik GmbH
3+
* This program and the accompanying materials are made available under the
4+
* terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0.
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Franz Höpfinger
11+
* - initial API and implementation and/or initial documentation
12+
*******************************************************************************/
13+
14+
#include "forte/arch/forte_fileio.h"
15+
#include "forte/arch/forte_ramdisk.h"
16+
#include <cstdio>
17+
#include <cstring>
18+
#include <fstream>
19+
#include <map>
20+
#include <mutex>
21+
#include <string>
22+
#include <vector>
23+
24+
struct RamdiskFileInfo {
25+
std::string filename;
26+
char mode;
27+
};
28+
29+
static std::map<std::string, std::vector<char>> g_ramdiskEntries;
30+
static std::map<void *, RamdiskFileInfo> g_ramdiskOpenFiles;
31+
static std::mutex g_ramdiskMutex;
32+
33+
extern "C" {
34+
35+
char *forte_getenv(const char *env_var) {
36+
return getenv(env_var);
37+
}
38+
39+
size_t forte_strnlen_s(const char *str, size_t strsz) {
40+
if (nullptr == str) {
41+
return 0;
42+
}
43+
return strnlen(str, strsz);
44+
}
45+
46+
int forte_ramdisk_load(const char *filename) {
47+
if (filename == nullptr) {
48+
return -1;
49+
}
50+
51+
std::ifstream file(filename, std::ios::binary | std::ios::ate);
52+
if (!file) {
53+
return -1;
54+
}
55+
56+
std::streamsize size = file.tellg();
57+
if (size < 0) {
58+
return -1;
59+
}
60+
61+
file.seekg(0, std::ios::beg);
62+
63+
std::vector<char> buffer(static_cast<size_t>(size));
64+
if (!file.read(buffer.data(), size)) {
65+
return -1;
66+
}
67+
68+
std::lock_guard<std::mutex> lock(g_ramdiskMutex);
69+
g_ramdiskEntries[filename] = std::move(buffer);
70+
return 0;
71+
}
72+
73+
void forte_ramdisk_unload(const char *filename) {
74+
if (filename == nullptr) {
75+
return;
76+
}
77+
std::lock_guard<std::mutex> lock(g_ramdiskMutex);
78+
g_ramdiskEntries.erase(filename);
79+
}
80+
81+
void *forte_fopen(const char *filename, const char *mode) {
82+
if (filename != nullptr && mode != nullptr) {
83+
std::lock_guard<std::mutex> lock(g_ramdiskMutex);
84+
auto it = g_ramdiskEntries.find(filename);
85+
if (it != g_ramdiskEntries.end()) {
86+
// Only use RAMDISK for read modes; write modes fallback to real file
87+
if (mode[0] == 'r') {
88+
FILE *file = fmemopen(it->second.data(), it->second.size(), mode);
89+
if (file != nullptr) {
90+
g_ramdiskOpenFiles[file] = {filename, mode[0]};
91+
}
92+
return file;
93+
}
94+
}
95+
}
96+
return fopen(filename, mode);
97+
}
98+
99+
int forte_fclose(void *file) {
100+
FILE *f = static_cast<FILE *>(file);
101+
{
102+
std::lock_guard<std::mutex> lock(g_ramdiskMutex);
103+
auto it = g_ramdiskOpenFiles.find(f);
104+
if (it != g_ramdiskOpenFiles.end()) {
105+
if (it->second.mode == 'r') {
106+
// Remove from RAMDISK; vector memory freed automatically by RAII
107+
g_ramdiskEntries.erase(it->second.filename);
108+
}
109+
g_ramdiskOpenFiles.erase(it);
110+
}
111+
}
112+
return fclose(f);
113+
}
114+
115+
char *forte_fgets(char *str, int count, void *file) {
116+
return fgets(str, count, static_cast<FILE *>(file));
117+
}
118+
119+
int forte_fseek(void *file, long offset, int whence) {
120+
return fseek(static_cast<FILE *>(file), offset, whence);
121+
}
122+
123+
long forte_ftell(void *file) {
124+
return ftell(static_cast<FILE *>(file));
125+
}
126+
127+
int forte_feof(void *file) {
128+
return feof(static_cast<FILE *>(file));
129+
}
130+
131+
size_t forte_fread(void *ptr, size_t itemsize, size_t nitems, void *file) {
132+
return fread(ptr, itemsize, nitems, static_cast<FILE *>(file));
133+
}
134+
135+
size_t forte_fwrite(const void *ptr, size_t itemsize, size_t nitems, void *file) {
136+
return fwrite(ptr, itemsize, nitems, static_cast<FILE *>(file));
137+
}
138+
139+
} // extern "C"

core/arch/posix/src/CMakeLists.txt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#*******************************************************************************
2-
# Copyright (c) 2010 - 2024 ACIN, Profactor GmbH, fortiss GmbH, Jose Cabral
2+
# Copyright (c) 2010 ACIN, Profactor GmbH, fortiss GmbH, Jose Cabral and others
33
# This program and the accompanying materials are made available under the
44
# terms of the Eclipse Public License 2.0 which is available at
55
# http://www.eclipse.org/legal/epl-2.0.
@@ -13,9 +13,18 @@
1313
# *******************************************************************************/
1414

1515
option(FORTE_COM_SER "Enable Forte serial line communication" OFF)
16+
option(FORTE_FILEIO_RAMDISK "Use RAM disk for file I/O" OFF)
1617

1718
include(CheckSymbolExists)
1819

20+
if(FORTE_FILEIO_RAMDISK)
21+
check_symbol_exists(fmemopen stdio.h HAVE_FMEMOPEN)
22+
if(NOT HAVE_FMEMOPEN)
23+
message(FATAL_ERROR "fmemopen not found, but FORTE_FILEIO_RAMDISK is enabled")
24+
endif()
25+
target_compile_definitions(forte-core PUBLIC FORTE_FILEIO_RAMDISK)
26+
endif()
27+
1928
target_sources(forte-core PRIVATE
2029
forte_architecture_time.cpp
2130
forte_sem.cpp
@@ -26,7 +35,8 @@ target_sources(forte-core PRIVATE
2635
../../common/src/forte_specific_architecture.cpp
2736
../../common/src/forte_standard_time.cpp
2837
../../common/src/genforte_printer.cpp
29-
../../common/src/genforte_fileio.cpp
38+
$<$<NOT:$<BOOL:${FORTE_FILEIO_RAMDISK}>>:${CMAKE_CURRENT_SOURCE_DIR}/../../common/src/genforte_fileio.cpp>
39+
$<$<BOOL:${FORTE_FILEIO_RAMDISK}>:${CMAKE_CURRENT_SOURCE_DIR}/../../common/src/genforte_fileio_ramdisk.cpp>
3040
../../common/src/utils/timespec_utils.cpp
3141
$<$<BOOL:${BUILD_SHARED_LIBS}>:${CMAKE_CURRENT_SOURCE_DIR}/load_option.cpp>
3242
$<$<BOOL:${FORTE_COM_SER}>:${CMAKE_CURRENT_SOURCE_DIR}/posixsercommlayer.cpp>

tests/core/util/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ forte_test_add_sourcefile_cpp(
1717
testsingleton.cpp singeltontest.cpp singletontest2ndunit.cpp
1818
parameterParserTest.cpp
1919
string_utils_test.cpp
20+
ramdisk_test.cpp
2021
)

tests/core/util/ramdisk_test.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agrartechnik GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Franz Höpfinger - initial API and implementation and/or initial documentation
12+
*******************************************************************************/
13+
14+
#include <boost/test/unit_test.hpp>
15+
#include <cstdio>
16+
#include <cstdlib>
17+
#include <cstring>
18+
#include <fstream>
19+
#include <iostream>
20+
21+
#ifdef FORTE_FILEIO_RAMDISK
22+
#include "forte/arch/forte_ramdisk.h"
23+
#include "forte/arch/forte_fileio.h"
24+
25+
namespace forte::arch::test {
26+
27+
BOOST_AUTO_TEST_SUITE(RAMDISK_function_test)
28+
29+
BOOST_AUTO_TEST_CASE(ramdisk_load_and_read_test) {
30+
// Create a temporary file with known content
31+
const char *testContent = "Line1;Content1\nLine2;Content2\nLine3;Content3\n";
32+
const char *tempFileName = "/tmp/forte_ramdisk_test_file.txt";
33+
34+
std::ofstream outFile(tempFileName);
35+
outFile << testContent;
36+
outFile.close();
37+
38+
// Load file into RAMDISK
39+
int result = forte_ramdisk_load(tempFileName);
40+
BOOST_CHECK_EQUAL(result, 0);
41+
42+
// Open via RAMDISK (should use fmemopen internally)
43+
void *file = forte_fopen(tempFileName, "r");
44+
BOOST_CHECK(file != nullptr);
45+
46+
if (file != nullptr) {
47+
char buffer[256];
48+
49+
// Read first line
50+
char *line1 = forte_fgets(buffer, sizeof(buffer), file);
51+
BOOST_CHECK(line1 != nullptr);
52+
BOOST_CHECK(strcmp(buffer, "Line1;Content1\n") == 0);
53+
54+
// Read second line
55+
char *line2 = forte_fgets(buffer, sizeof(buffer), file);
56+
BOOST_CHECK(line2 != nullptr);
57+
BOOST_CHECK(strcmp(buffer, "Line2;Content2\n") == 0);
58+
59+
// Read third line
60+
char *line3 = forte_fgets(buffer, sizeof(buffer), file);
61+
BOOST_CHECK(line3 != nullptr);
62+
BOOST_CHECK(strcmp(buffer, "Line3;Content3\n") == 0);
63+
64+
// Close should free the buffer for read mode
65+
int closeResult = forte_fclose(file);
66+
BOOST_CHECK_EQUAL(closeResult, 0);
67+
}
68+
69+
// Clean up temporary file
70+
std::remove(tempFileName);
71+
}
72+
73+
BOOST_AUTO_TEST_CASE(ramdisk_fallback_to_real_file_test) {
74+
// Test that files NOT in RAMDISK still work via normal fopen
75+
const char *testContent = "FallbackTest;Data\n";
76+
const char *tempFileName = "/tmp/forte_ramdisk_fallback_test.txt";
77+
78+
std::ofstream outFile(tempFileName);
79+
outFile << testContent;
80+
outFile.close();
81+
82+
// Do NOT load into RAMDISK - should fallback to real file
83+
void *file = forte_fopen(tempFileName, "r");
84+
BOOST_CHECK(file != nullptr);
85+
86+
if (file != nullptr) {
87+
char buffer[256];
88+
char *line = forte_fgets(buffer, sizeof(buffer), file);
89+
BOOST_CHECK(line != nullptr);
90+
BOOST_CHECK(strcmp(buffer, "FallbackTest;Data\n") == 0);
91+
92+
int closeResult = forte_fclose(file);
93+
BOOST_CHECK_EQUAL(closeResult, 0);
94+
}
95+
96+
// Clean up temporary file
97+
std::remove(tempFileName);
98+
}
99+
100+
BOOST_AUTO_TEST_SUITE_END()
101+
102+
} // namespace forte::arch::test
103+
104+
#else // FORTE_FILEIO_RAMDISK not defined
105+
// Provide an empty test suite so the file compiles even without RAMDISK
106+
namespace forte::arch::test {
107+
BOOST_AUTO_TEST_SUITE(RAMDISK_function_test)
108+
BOOST_AUTO_TEST_CASE(dummy_test) {
109+
BOOST_CHECK(true); // Always passes when RAMDISK is not available
110+
}
111+
BOOST_AUTO_TEST_SUITE_END()
112+
} // namespace forte::arch::test
113+
#endif // FORTE_FILEIO_RAMDISK

0 commit comments

Comments
 (0)