├── .appveyor.yml
├── .cmake
└── FindCriterion.cmake
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
├── include
└── fmem.h.in
├── src
├── alloc.c
├── alloc.h
├── fmem-fopencookie.c
├── fmem-funopen.c
├── fmem-open_memstream.c
├── fmem-tmpfile.c
└── fmem-winapi-tmpfile.c
└── test
├── CMakeLists.txt
└── tests.c
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.0-b{build}-{branch}
2 |
3 | os: Visual Studio 2015
4 |
5 | environment:
6 | matrix:
7 | - COMPILER: mingw
8 | GENERATOR: "MSYS Makefiles"
9 | CPPFLAGS: -D__USE_MINGW_ANSI_STDIO
10 | - COMPILER: mingw
11 | GENERATOR: "MSYS Makefiles"
12 | - COMPILER: msvc
13 | GENERATOR: "Visual Studio 14 2015 Win64"
14 | BUILD_FLAGS: /verbosity:m
15 |
16 | init:
17 | - git config --global core.autocrlf input
18 | - set MSYSTEM=MINGW64
19 |
20 | # Disable windows process crash popup
21 | # See: https://msdn.microsoft.com/en-us/library/bb513638%28VS.85%29.aspx
22 | - reg add "HKLM\SYSTEM\CurrentControlSet\Control\Windows" /f /v ErrorMode /t REG_DWORD /d 2
23 | - reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting" /f /v DontShowUI /t REG_DWORD /d 1
24 | - reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting" /f /v Disable /t REG_DWORD /d 1
25 |
26 | - set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%
27 | - set PATH=%PATH%;%APPVEYOR_BUILD_FOLDER%\build;C:\criterion-v2.3.2\bin
28 |
29 | # Remove Xamarin msbuild warnings
30 | - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets"
31 |
32 | clone_depth: 5
33 |
34 | configuration:
35 | - RelWithDebInfo
36 |
37 | install:
38 | # Install Criterion
39 | - sh -c 'curl -L https://github.com/Snaipe/Criterion/releases/download/v2.3.2/criterion-v2.3.2-windows-msvc-x86_64.tar.bz2 | tar -xjf - -C /c/'
40 |
41 | # Configure project
42 | - 'mkdir build && cd build'
43 | - >
44 | cmake
45 | -Wno-dev
46 | -DCMAKE_BUILD_TYPE="%CONFIGURATION%"
47 | -DCMAKE_PREFIX_PATH="C:/criterion-v2.3.2"
48 | %CMAKE_OPTS%
49 | -G "%GENERATOR%"
50 | ..
51 |
52 | build_script:
53 | - cmake --build . -- %BUILD_FLAGS%
54 |
55 | test_script:
56 | - cp C:\criterion-v2.3.2\bin\criterion.dll %APPVEYOR_BUILD_FOLDER%\build\test\
57 | - ps: |
58 | ctest --output-on-failure
59 | if (-not $lastexitcode -eq 0) {
60 | type Testing/Temporary/LastTest.log
61 | $host.setshouldexit(1)
62 | }
63 |
--------------------------------------------------------------------------------
/.cmake/FindCriterion.cmake:
--------------------------------------------------------------------------------
1 | # This file is licensed under the WTFPL version 2 -- you can see the full
2 | # license over at http://www.wtfpl.net/txt/copying/
3 | #
4 | # - Try to find Criterion
5 | #
6 | # Once done this will define
7 | # CRITERION_FOUND - System has Criterion
8 | # CRITERION_INCLUDE_DIRS - The Criterion include directories
9 | # CRITERION_LIBRARIES - The libraries needed to use Criterion
10 |
11 | find_package(PkgConfig)
12 |
13 | find_path(CRITERION_INCLUDE_DIR criterion/criterion.h
14 | PATH_SUFFIXES criterion)
15 |
16 | find_library(CRITERION_LIBRARY NAMES criterion libcriterion)
17 |
18 | set(CRITERION_LIBRARIES ${CRITERION_LIBRARY})
19 | set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR})
20 |
21 | include(FindPackageHandleStandardArgs)
22 | # handle the QUIET and REQUIRED arguments and set CRITERION_FOUND to TRUE
23 | # if all listed variables are TRUE
24 | find_package_handle_standard_args(Criterion DEFAULT_MSG
25 | CRITERION_LIBRARY CRITERION_INCLUDE_DIR)
26 |
27 | mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY)
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: c
2 | sudo: required
3 |
4 | matrix:
5 | include:
6 | # open_memstream
7 | - compiler: gcc
8 | # fopencookie
9 | - env: [CMAKE_EXTRA_ARGS="-DHAVE_OPEN_MEMSTREAM=FALSE"]
10 | compiler: gcc
11 | # tmpfile
12 | - env: [CMAKE_EXTRA_ARGS="-DHAVE_OPEN_MEMSTREAM=FALSE -DHAVE_FOPENCOOKIE=FALSE"]
13 | compiler: gcc
14 | # funopen
15 | - os: osx
16 |
17 | before_install:
18 | - |
19 | if [ "${TRAVIS_OS_NAME}" = linux ]; then
20 | sudo add-apt-repository -y ppa:snaipewastaken/ppa
21 | sudo apt-get update
22 | sudo apt-get install -qq -y criterion-dev
23 | else
24 | brew install snaipe/soft/criterion
25 | fi
26 |
27 | script:
28 | - mkdir build && cd $_
29 | - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ${CMAKE_EXTRA_ARGS} ..
30 | - make
31 | - ./test/unit_tests
32 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2017 Franklin "Snaipe" Mathieu.
2 | # Redistribution and use of this file is allowed according to the terms of the MIT license.
3 | # For details see the LICENSE file distributed with Mimick.
4 |
5 | cmake_minimum_required (VERSION 2.8)
6 |
7 | project (fmem C)
8 |
9 | list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/.cmake")
10 |
11 | include (CheckSymbolExists)
12 | include (CheckCSourceCompiles)
13 | include (GNUInstallDirs)
14 |
15 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
16 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -pedantic")
17 | endif ()
18 |
19 | list (APPEND CMAKE_REQUIRED_DEFINITIONS
20 | -D_GNU_SOURCE
21 | -D_CRT_RAND_S
22 | -DVC_EXTRALEAN
23 | -DWIN32_LEAN_AND_MEAN)
24 |
25 | check_symbol_exists (open_memstream stdio.h HAVE_OPEN_MEMSTREAM)
26 | check_symbol_exists (fopencookie stdio.h HAVE_FOPENCOOKIE)
27 | check_symbol_exists (funopen stdio.h HAVE_FUNOPEN)
28 | check_symbol_exists (tmpfile stdio.h HAVE_TMPFILE)
29 | check_symbol_exists (rand_s stdlib.h HAVE_WINAPI_RAND_S)
30 | check_symbol_exists (CreateFile windows.h HAVE_WINAPI_CREATEFILE)
31 | check_symbol_exists (CloseHandle windows.h HAVE_WINAPI_CLOSEHANDLE)
32 | check_symbol_exists (GetFileSize windows.h HAVE_WINAPI_GETFILESIZE)
33 | check_symbol_exists (CreateFileMapping windows.h HAVE_WINAPI_CREATEFILEMAPPING)
34 | check_symbol_exists (MapViewOfFile windows.h HAVE_WINAPI_MAPVIEWOFFILE)
35 | check_symbol_exists (UnmapViewOfFile windows.h HAVE_WINAPI_UNMAPVIEWOFFILE)
36 | check_symbol_exists (GetTempPath windows.h HAVE_WINAPI_GETTEMPPATH)
37 | check_symbol_exists (_open_osfhandle io.h HAVE_WINAPI_OPEN_OSFHANDLE)
38 | check_symbol_exists (_get_osfhandle io.h HAVE_WINAPI_GET_OSFHANDLE)
39 | check_symbol_exists (_fdopen stdio.h HAVE_WINAPI_FDOPEN)
40 | check_symbol_exists (_fileno stdio.h HAVE_WINAPI_FILENO)
41 | check_symbol_exists (_close io.h HAVE_WINAPI_CLOSE)
42 |
43 | set (SOURCES)
44 |
45 | if (HAVE_OPEN_MEMSTREAM)
46 | list (APPEND SOURCES src/fmem-open_memstream.c)
47 | elseif (HAVE_FOPENCOOKIE)
48 | list (APPEND SOURCES
49 | src/alloc.c
50 | src/alloc.h
51 | src/fmem-fopencookie.c)
52 | elseif (HAVE_FUNOPEN)
53 | list (APPEND SOURCES
54 | src/alloc.c
55 | src/alloc.h
56 | src/fmem-funopen.c)
57 | elseif (HAVE_WINAPI_CREATEFILE
58 | AND HAVE_WINAPI_CLOSEHANDLE
59 | AND HAVE_WINAPI_GETFILESIZE
60 | AND HAVE_WINAPI_CREATEFILEMAPPING
61 | AND HAVE_WINAPI_MAPVIEWOFFILE
62 | AND HAVE_WINAPI_UNMAPVIEWOFFILE
63 | AND HAVE_WINAPI_GETTEMPPATH
64 | AND HAVE_WINAPI_FDOPEN
65 | AND HAVE_WINAPI_FILENO
66 | AND HAVE_WINAPI_CLOSE
67 | AND HAVE_WINAPI_OPEN_OSFHANDLE
68 | AND HAVE_WINAPI_GET_OSFHANDLE
69 | AND HAVE_WINAPI_RAND_S)
70 | list (APPEND SOURCES src/fmem-winapi-tmpfile.c)
71 | elseif (HAVE_TMPFILE)
72 | list (APPEND SOURCES src/fmem-tmpfile.c)
73 | else ()
74 | message (FATAL_ERROR "No memory stream implementation found")
75 | endif ()
76 |
77 | include_directories (include src ${PROJECT_BINARY_DIR}/gen)
78 | add_library (fmem ${SOURCES})
79 |
80 | get_property (FMEM_LIBTYPE
81 | TARGET fmem
82 | PROPERTY TYPE)
83 |
84 | set (CMAKE_REQUIRED_DEFINITIONS -fvisibility=hidden)
85 | check_c_source_compiles (
86 | "__attribute__((visibility(\"default\"))) int main(void) { return 0; }"
87 | CC_HAVE_VISIBILITY)
88 | set (CMAKE_REQUIRED_DEFINITIONS)
89 |
90 | if ("${FMEM_LIBTYPE}" MATCHES "SHARED_LIBRARY")
91 | if (WIN32)
92 | set (EXPORT_MACROS
93 | "#ifdef FMEM_BUILD_LIBRARY
94 | # define FMEM_API __declspec(dllexport)
95 | #else /* !FMEM_BUILD_LIBRARY */
96 | # define FMEM_API __declspec(dllimport)
97 | #endif /* !FMEM_BUILD_LIBRARY */")
98 | add_definitions (-DFMEM_BUILD_LIBRARY)
99 | elseif (CC_HAVE_VISIBILITY)
100 | set (EXPORT_MACROS "#define FMEM_API __attribute__((visibility(\"default\")))")
101 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
102 | endif ()
103 | else ()
104 | set (EXPORT_MACROS "#define FMEM_API")
105 | endif ()
106 |
107 | configure_file (
108 | ${PROJECT_SOURCE_DIR}/include/fmem.h.in
109 | ${PROJECT_BINARY_DIR}/gen/fmem.h
110 | @ONLY)
111 |
112 | install(TARGETS fmem
113 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
114 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
115 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
116 |
117 | install(FILES
118 | fmem.h
119 | ${PROJECT_BINARY_DIR}/gen/fmem-export.h
120 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
121 |
122 | include (CTest)
123 |
124 | if (BUILD_TESTING)
125 | find_package (Criterion REQUIRED)
126 | add_subdirectory (test)
127 | endif ()
128 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2017 Franklin "Snaipe" Mathieu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fmem
2 |
3 | [](https://travis-ci.org/Snaipe/fmem)
4 | [](https://ci.appveyor.com/project/Snaipe/fmem/branch/master)
5 |
6 | A cross-platform library for opening memory-backed libc streams.
7 |
8 | This library was written for [Criterion][criterion] to implement stringification functions for user-defined types.
9 |
10 | ## Rationale
11 |
12 | C doesn't define any way to open "virtual" streams that write to memory rather than a real file. A lot of libc implementations roll their own nonstandard mechanisms to achieve this, namely `open_memstream`, or `fmemopen`. Other implementations provide more generic functions to call users functions for various operations on the file, like `funopen` or `fopencookie`. Finally, some implementations support none of these nonstandard functions.
13 |
14 | fmem tries in sequence the following implementations:
15 |
16 | * `open_memstream`.
17 | * `fopencookie`, with growing dynamic buffer.
18 | * `funopen`, with growing dynamic buffer.
19 | * WinAPI temporary memory-backed file.
20 |
21 | When no other mean is available, fmem falls back to `tmpfile()`.
22 |
23 | [criterion]: https://github.com/Snaipe/Criterion
24 |
--------------------------------------------------------------------------------
/include/fmem.h.in:
--------------------------------------------------------------------------------
1 | #ifndef FMEM_H_
2 | #define FMEM_H_
3 |
4 | #include
5 |
6 | @EXPORT_MACROS@
7 |
8 | struct fmem_reserved {
9 | char reserved[32];
10 | };
11 |
12 | typedef struct fmem_reserved fmem;
13 |
14 | FMEM_API void fmem_init(fmem *file);
15 | FMEM_API void fmem_term(fmem *file);
16 | FMEM_API FILE *fmem_open(fmem *file, const char *mode);
17 | FMEM_API void fmem_mem(fmem *file, void **mem, size_t *size);
18 |
19 | #endif /* !FMEM_H_ */
20 |
--------------------------------------------------------------------------------
/src/alloc.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "alloc.h"
6 |
7 | static inline size_t golden_growth_ceil(size_t n)
8 | {
9 | /* This effectively is a return ceil(n * φ).
10 | φ is approximatively 207 / (2^7), so we shift our result by
11 | 6, then perform our ceil by adding the remainder of the last division
12 | by 2 of the result to itself. */
13 |
14 | n = (n * 207) >> 6;
15 | n = (n >> 1) + (n & 1);
16 | return n;
17 | }
18 |
19 | int fmemi_grow(struct fmem_stream *stream, size_t required)
20 | {
21 | if (stream->cursor > SIZE_MAX - required) {
22 | errno = EOVERFLOW;
23 | return -1;
24 | }
25 | required += stream->cursor;
26 |
27 | size_t newsize = stream->region_size;
28 | if (required <= newsize) {
29 | return 0;
30 | }
31 |
32 | while (required > newsize) {
33 | newsize = golden_growth_ceil(newsize);
34 | }
35 |
36 | char *newmem = realloc(stream->buf->mem, newsize);
37 | if (!newmem) {
38 | return -1;
39 | }
40 | stream->buf->mem = newmem;
41 | stream->region_size = newsize;
42 | return 0;
43 | }
44 |
45 | int fmemi_cursor(struct fmemi_buf *buf, struct fmem_stream *from)
46 | {
47 | if (from->buf->size < from->cursor) {
48 | return -1;
49 | }
50 |
51 | buf->mem = from->buf->mem + from->cursor;
52 | buf->size = from->region_size - from->cursor;
53 | return 0;
54 | }
55 |
56 | size_t fmemi_copy(struct fmemi_buf *to, struct fmemi_buf *from)
57 | {
58 | size_t copied = from->size < to->size ? from->size : to->size;
59 | memcpy(to->mem, from->mem, copied);
60 | return copied;
61 | }
62 |
--------------------------------------------------------------------------------
/src/alloc.h:
--------------------------------------------------------------------------------
1 | #ifndef ALLOC_H_
2 | #define ALLOC_H_
3 |
4 | #include
5 |
6 | struct fmemi_buf {
7 | char *mem;
8 | size_t size;
9 | };
10 |
11 | struct fmem_stream {
12 | struct fmemi_buf *buf;
13 | size_t cursor;
14 | size_t region_size;
15 | };
16 |
17 | int fmemi_grow(struct fmem_stream *stream, size_t required);
18 | int fmemi_cursor(struct fmemi_buf *buf, struct fmem_stream *from);
19 | size_t fmemi_copy(struct fmemi_buf *to, struct fmemi_buf *from);
20 |
21 | #endif /* !ALLOC_H_ */
22 |
--------------------------------------------------------------------------------
/src/fmem-fopencookie.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 | #include
5 |
6 | #include "alloc.h"
7 | #include "fmem.h"
8 |
9 | union fmem_conv {
10 | fmem *fm;
11 | struct fmemi_buf *buf;
12 | };
13 |
14 | void fmem_init(fmem *file)
15 | {
16 | union fmem_conv cv = { .fm = file };
17 | memset(cv.buf, 0, sizeof (*cv.buf));
18 | }
19 |
20 | void fmem_term(fmem *file)
21 | {
22 | union fmem_conv cv = { .fm = file };
23 | free(cv.buf->mem);
24 | }
25 |
26 | static ssize_t mem_write(void *cookie, const char *buf, size_t size)
27 | {
28 | struct fmem_stream *stream = cookie;
29 |
30 | struct fmemi_buf from = { (char *) buf, size };
31 | struct fmemi_buf to;
32 |
33 | if (fmemi_grow(stream, size) < 0) {
34 | return -1;
35 | }
36 | if (fmemi_cursor(&to, stream) < 0) {
37 | return 0;
38 | }
39 |
40 | size_t copied = fmemi_copy(&to, &from);
41 | stream->cursor += copied;
42 | if (stream->buf->size < stream->cursor)
43 | stream->buf->size = stream->cursor;
44 | return copied;
45 | }
46 |
47 | static ssize_t mem_read(void *cookie, char *buf, size_t size)
48 | {
49 | struct fmem_stream *stream = cookie;
50 |
51 | struct fmemi_buf to = { buf, size };
52 | struct fmemi_buf from;
53 |
54 | if (fmemi_cursor(&from, stream) < 0) {
55 | return 0;
56 | }
57 |
58 | size_t copied = fmemi_copy(&to, &from);
59 | stream->cursor += copied;
60 | return copied;
61 | }
62 |
63 | static int mem_seek(void *cookie, off64_t *off, int whence)
64 | {
65 | struct fmem_stream *stream = cookie;
66 |
67 | size_t newoff;
68 | switch (whence) {
69 | case SEEK_SET: newoff = *off; break;
70 | case SEEK_CUR: newoff = stream->cursor + *off; break;
71 | case SEEK_END: newoff = stream->buf->size + *off; break;
72 | default: errno = EINVAL; return -1;
73 | }
74 | if (newoff > stream->buf->size || (off64_t)newoff < 0) {
75 | return -1;
76 | }
77 | *off = newoff;
78 | stream->cursor = newoff;
79 | return 0;
80 | }
81 |
82 | static int mem_close(void *cookie)
83 | {
84 | free(cookie);
85 | return 0;
86 | }
87 |
88 | FILE *fmem_open(fmem *file, const char *mode)
89 | {
90 | static cookie_io_functions_t funcs = {
91 | .read = mem_read,
92 | .write = mem_write,
93 | .seek = mem_seek,
94 | .close = mem_close,
95 | };
96 |
97 | union fmem_conv cv = { .fm = file };
98 |
99 | free(cv.buf->mem);
100 | cv.buf->mem = malloc(128);
101 | if (!cv.buf->mem)
102 | return NULL;
103 |
104 | struct fmem_stream *stream = malloc(sizeof (*stream));
105 | if (!stream) {
106 | free(cv.buf->mem);
107 | cv.buf->mem = NULL;
108 | return NULL;
109 | }
110 |
111 | *stream = (struct fmem_stream) {
112 | .buf = cv.buf,
113 | .region_size = 128,
114 | };
115 |
116 | FILE *f = fopencookie(stream, mode, funcs);
117 | if (!f)
118 | free(stream);
119 | return f;
120 | }
121 |
122 | void fmem_mem(fmem *file, void **mem, size_t *size)
123 | {
124 | union fmem_conv cv = { .fm = file };
125 | *mem = cv.buf->mem;
126 | *size = cv.buf->size;
127 | }
128 |
--------------------------------------------------------------------------------
/src/fmem-funopen.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "alloc.h"
7 | #include "fmem.h"
8 |
9 | union fmem_conv {
10 | fmem *fm;
11 | struct fmemi_buf *buf;
12 | };
13 |
14 | void fmem_init(fmem *file)
15 | {
16 | union fmem_conv cv = { .fm = file };
17 | memset(cv.buf, 0, sizeof (*cv.buf));
18 | }
19 |
20 | void fmem_term(fmem *file)
21 | {
22 | union fmem_conv cv = { .fm = file };
23 | free(cv.buf->mem);
24 | }
25 |
26 | static int mem_write(void *cookie, const char *buf, int size)
27 | {
28 | if (size < 0) {
29 | errno = EINVAL;
30 | return -1;
31 | }
32 | struct fmem_stream *stream = cookie;
33 |
34 | struct fmemi_buf from = { (char *) buf, size };
35 | struct fmemi_buf to;
36 |
37 | if (fmemi_grow(stream, size) < 0) {
38 | return -1;
39 | }
40 | if (fmemi_cursor(&to, stream) < 0) {
41 | return 0;
42 | }
43 |
44 | size_t copied = fmemi_copy(&to, &from);
45 | stream->cursor += copied;
46 | if (stream->buf->size < stream->cursor)
47 | stream->buf->size = stream->cursor;
48 | if (copied > INT_MAX) {
49 | errno = EOVERFLOW;
50 | return -1;
51 | }
52 | return copied;
53 | }
54 |
55 | static int mem_read(void *cookie, char *buf, int size)
56 | {
57 | if (size < 0) {
58 | errno = EINVAL;
59 | return -1;
60 | }
61 | struct fmem_stream *stream = cookie;
62 |
63 | struct fmemi_buf to = { buf, size };
64 | struct fmemi_buf from;
65 |
66 | if (fmemi_cursor(&from, stream) < 0) {
67 | return 0;
68 | }
69 |
70 | size_t copied = fmemi_copy(&to, &from);
71 | stream->cursor += copied;
72 | if (copied > INT_MAX) {
73 | errno = EOVERFLOW;
74 | return -1;
75 | }
76 | return copied;
77 | }
78 |
79 | static off_t mem_seek(void *cookie, off_t off, int whence)
80 | {
81 | struct fmem_stream *stream = cookie;
82 |
83 | size_t newoff;
84 | switch (whence) {
85 | case SEEK_SET: newoff = off; break;
86 | case SEEK_CUR: newoff = stream->cursor + off; break;
87 | case SEEK_END: newoff = stream->buf->size + off; break;
88 | default: errno = EINVAL; return -1;
89 | }
90 | if (newoff > stream->buf->size || (off_t)newoff < 0
91 | || newoff > (size_t)OFF_MAX) {
92 | errno = EOVERFLOW;
93 | return -1;
94 | }
95 | stream->cursor = newoff;
96 | return newoff;
97 | }
98 |
99 | static int mem_close(void *cookie)
100 | {
101 | free(cookie);
102 | return 0;
103 | }
104 |
105 | FILE *fmem_open(fmem *file, const char *mode)
106 | {
107 | union fmem_conv cv = { .fm = file };
108 |
109 | free(cv.buf->mem);
110 | cv.buf->mem = malloc(128);
111 | if (!cv.buf->mem)
112 | return NULL;
113 |
114 | struct fmem_stream *stream = malloc(sizeof (*stream));
115 | if (!stream) {
116 | free(cv.buf->mem);
117 | cv.buf->mem = NULL;
118 | return NULL;
119 | }
120 |
121 | *stream = (struct fmem_stream) {
122 | .buf = cv.buf,
123 | .region_size = 128,
124 | };
125 |
126 | FILE *f = funopen(stream, mem_read, mem_write, mem_seek, mem_close);
127 | if (!f)
128 | free(stream);
129 | return f;
130 | }
131 |
132 | void fmem_mem(fmem *file, void **mem, size_t *size)
133 | {
134 | union fmem_conv cv = { .fm = file };
135 | *mem = cv.buf->mem;
136 | *size = cv.buf->size;
137 | }
138 |
--------------------------------------------------------------------------------
/src/fmem-open_memstream.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 | #include
5 |
6 | #include "alloc.h"
7 | #include "fmem.h"
8 |
9 | union fmem_conv {
10 | fmem *fm;
11 | struct fmemi_buf *buf;
12 | };
13 |
14 | void fmem_init(fmem *file)
15 | {
16 | union fmem_conv cv = { .fm = file };
17 | memset(cv.buf, 0, sizeof (*cv.buf));
18 | }
19 |
20 | void fmem_term(fmem *file)
21 | {
22 | union fmem_conv cv = { .fm = file };
23 | free(cv.buf->mem);
24 | }
25 |
26 | FILE *fmem_open(fmem *file, const char *mode)
27 | {
28 | (void) mode;
29 |
30 | union fmem_conv cv = { .fm = file };
31 | free(cv.buf->mem);
32 | return open_memstream(&cv.buf->mem, &cv.buf->size);
33 | }
34 |
35 | void fmem_mem(fmem *file, void **mem, size_t *size)
36 | {
37 | union fmem_conv cv = { .fm = file };
38 | *mem = cv.buf->mem;
39 | *size = cv.buf->size;
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/fmem-tmpfile.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "fmem.h"
6 |
7 | struct fmem_impl {
8 | FILE *file;
9 | void *buf;
10 | };
11 |
12 | union fmem_conv {
13 | fmem *fm;
14 | struct fmem_impl *impl;
15 | };
16 |
17 | void fmem_init(fmem *file)
18 | {
19 | union fmem_conv cv = { .fm = file };
20 | memset(cv.impl, 0, sizeof (*cv.impl));
21 | }
22 |
23 | void fmem_term(fmem *file)
24 | {
25 | union fmem_conv cv = { .fm = file };
26 | free(cv.impl->buf);
27 | }
28 |
29 | FILE *fmem_open(fmem *file, const char *mode)
30 | {
31 | union fmem_conv cv = { .fm = file };
32 | FILE *f = tmpfile();
33 | if (f) {
34 | free(cv.impl->buf);
35 | cv.impl->file = f;
36 | }
37 | return f;
38 | }
39 |
40 | void fmem_mem(fmem *file, void **mem, size_t *size)
41 | {
42 | union fmem_conv cv = { .fm = file };
43 | *mem = NULL;
44 | *size = 0;
45 |
46 | free(cv.impl->buf);
47 | cv.impl->buf = NULL;
48 |
49 | fseek(cv.impl->file, 0, SEEK_END);
50 | long bufsize = ftell(cv.impl->file);
51 | if (bufsize < 0) {
52 | return;
53 | }
54 |
55 | void *buf = malloc(bufsize);
56 | if (!buf) {
57 | return;
58 | }
59 |
60 | rewind(cv.impl->file);
61 | if (fread(buf, 1, bufsize, cv.impl->file) < (size_t)bufsize) {
62 | free(buf);
63 | return;
64 | }
65 | *mem = buf;
66 | *size = bufsize;
67 | }
68 |
--------------------------------------------------------------------------------
/src/fmem-winapi-tmpfile.c:
--------------------------------------------------------------------------------
1 | #define _CRT_RAND_S
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "fmem.h"
11 |
12 | struct fmem_winimpl {
13 | FILE *file;
14 | HANDLE mapping;
15 | void *base;
16 | };
17 |
18 | union fmem_conv {
19 | fmem *fm;
20 | struct fmem_winimpl *impl;
21 | };
22 |
23 | void fmem_init(fmem *file)
24 | {
25 | union fmem_conv cv = { .fm = file };
26 | memset(cv.impl, 0, sizeof (*cv.impl));
27 | }
28 |
29 | void fmem_term(fmem *file)
30 | {
31 | union fmem_conv cv = { .fm = file };
32 | if (cv.impl->mapping != NULL) {
33 | UnmapViewOfFile(cv.impl->base);
34 | CloseHandle(cv.impl->mapping);
35 | }
36 | }
37 |
38 | FILE *fmem_open(fmem *file, const char *mode)
39 | {
40 | union fmem_conv cv = { .fm = file };
41 | char path[MAX_PATH];
42 |
43 | DWORD rc = GetTempPathA(sizeof (path), path);
44 | errno = ENAMETOOLONG;
45 | if (rc > sizeof (path))
46 | return NULL;
47 |
48 | errno = EIO;
49 | if (rc == 0)
50 | return NULL;
51 |
52 | HANDLE handle = INVALID_HANDLE_VALUE;
53 | do {
54 | unsigned int randnum;
55 | errno = rand_s(&randnum);
56 | if (errno)
57 | return NULL;
58 |
59 | int wb = snprintf(&path[rc], MAX_PATH - rc, "\\fmem%x.tmp", randnum);
60 | if (wb < 0)
61 | return NULL;
62 | if ((DWORD)wb > (DWORD)MAX_PATH - rc) {
63 | errno = ENAMETOOLONG;
64 | return NULL;
65 | }
66 |
67 | handle = CreateFileA(path,
68 | GENERIC_READ | GENERIC_WRITE,
69 | 0,
70 | NULL,
71 | CREATE_NEW,
72 | FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
73 | NULL);
74 | } while (handle == INVALID_HANDLE_VALUE);
75 |
76 | int fd = _open_osfhandle((intptr_t) handle, _O_RDWR);
77 | if (fd == -1) {
78 | CloseHandle(handle);
79 | return NULL;
80 | }
81 |
82 | FILE *f = _fdopen(fd, mode);
83 | if (!f)
84 | _close(fd);
85 | cv.impl->file = f;
86 | return f;
87 | }
88 |
89 | void fmem_mem(fmem *file, void **mem, size_t *size)
90 | {
91 | union fmem_conv cv = { .fm = file };
92 | *mem = NULL;
93 | *size = 0;
94 |
95 | if (!cv.impl->file)
96 | return;
97 |
98 | HANDLE handle = (HANDLE) _get_osfhandle(_fileno(cv.impl->file));
99 |
100 | DWORD filesize = GetFileSize(handle, NULL);
101 | if (filesize == INVALID_FILE_SIZE)
102 | return;
103 |
104 | if (cv.impl->mapping) {
105 | UnmapViewOfFile(cv.impl->base);
106 | CloseHandle(cv.impl->mapping);
107 | }
108 |
109 | HANDLE mapping = CreateFileMapping(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
110 | if (!mapping)
111 | return;
112 |
113 | void *base = MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
114 | if (!base) {
115 | CloseHandle(mapping);
116 | return;
117 | }
118 |
119 | cv.impl->mapping = mapping;
120 | cv.impl->base = base;
121 |
122 | *mem = base;
123 | *size = filesize;
124 | }
125 |
--------------------------------------------------------------------------------
/test/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set (TEST_SOURCES tests.c)
2 |
3 | include_directories (SYSTEM ${CRITERION_INCLUDE_DIRS})
4 |
5 | add_executable (unit_tests ${TEST_SOURCES})
6 | target_link_libraries (unit_tests fmem ${CRITERION_LIBRARIES})
7 |
8 | add_test (unit_tests unit_tests)
9 |
--------------------------------------------------------------------------------
/test/tests.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "fmem.h"
5 |
6 | static fmem fm;
7 |
8 | void setup(void) { fmem_init(&fm); }
9 | void teardown(void) { fmem_term(&fm); }
10 |
11 | TestSuite(fmem, .init = setup, .fini = teardown);
12 |
13 | #define assert_written(Fmem, What) do { \
14 | const char *__str = (What); \
15 | void *__base; \
16 | size_t __size; \
17 | fmem_mem(&(Fmem), &__base, &__size); \
18 | cr_assert_eq(__size, strlen(__str), \
19 | "size is %llu, not %llu", \
20 | (unsigned long long) __size, \
21 | (unsigned long long) strlen(__str)); \
22 | cr_assert(!memcmp((char *)__base, __str, __size)); \
23 | } while (0)
24 |
25 | Test(fmem, open)
26 | {
27 | FILE *f = fmem_open(&fm, "w+");
28 | cr_assert(f);
29 | fclose(f);
30 | }
31 |
32 | Test(fmem, mem)
33 | {
34 | const char *str = "Hello world\n";
35 |
36 | FILE *f = fmem_open(&fm, "w+");
37 | fprintf(f, "%s", str);
38 | fflush(f);
39 |
40 | assert_written(fm, str);
41 | fclose(f);
42 | }
43 |
44 | Test(fmem, append)
45 | {
46 | FILE *f = fmem_open(&fm, "w+");
47 |
48 | fprintf(f, "abcd");
49 | fflush(f);
50 |
51 | assert_written(fm, "abcd");
52 |
53 | fprintf(f, "efgh");
54 | fflush(f);
55 |
56 | assert_written(fm, "abcdefgh");
57 | fclose(f);
58 | }
59 |
60 | Test(fmem, reopen)
61 | {
62 | FILE *f = fmem_open(&fm, "w+");
63 | fprintf(f, "abcd");
64 | fflush(f);
65 |
66 | assert_written(fm, "abcd");
67 | fclose(f);
68 |
69 | f = fmem_open(&fm, "w+");
70 | fprintf(f, "efgh");
71 | fflush(f);
72 |
73 | assert_written(fm, "efgh");
74 | fclose(f);
75 | }
76 |
77 | Test(fmem, cursor)
78 | {
79 | FILE *f = fmem_open(&fm, "w+");
80 | fprintf(f, "abcd");
81 | fseek(f, 2, SEEK_SET);
82 | fprintf(f, "efgh");
83 | fflush(f);
84 |
85 | assert_written(fm, "abefgh");
86 |
87 | fclose(f);
88 | }
89 |
90 | Test(fmem, large)
91 | {
92 | char buf[4096];
93 | memset(buf, 0xcc, sizeof (buf));
94 |
95 | FILE *f = fmem_open(&fm, "w+");
96 |
97 | for (size_t i = 0; i < 1024; ++i) {
98 | size_t written = fwrite(buf, 1, sizeof (buf), f);
99 | if (written < sizeof (buf)) {
100 | cr_assert_fail("could only write %llu/%llu bytes",
101 | (unsigned long long) written,
102 | (unsigned long long) sizeof (buf));
103 | }
104 | }
105 |
106 | fflush(f);
107 |
108 | void *base;
109 | size_t size;
110 | fmem_mem(&fm, &base, &size);
111 |
112 | cr_assert_eq(size, 1024 * sizeof (buf),
113 | "size is %llu, not %llu",
114 | (unsigned long long) size,
115 | (unsigned long long) 1024 * sizeof (buf));
116 |
117 | fclose(f);
118 | }
119 |
--------------------------------------------------------------------------------