├── .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 | [![Unix Build Status](https://img.shields.io/travis/Snaipe/fmem/master.svg?label=linux)](https://travis-ci.org/Snaipe/fmem) 4 | [![Windows Build Status](https://img.shields.io/appveyor/ci/Snaipe/fmem/master.svg?label=windows)](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 | --------------------------------------------------------------------------------