This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/intern/BLI_mmap.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

234 lines
6.9 KiB
C
Raw Normal View History

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup bli
*/
#include "BLI_mmap.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "MEM_guardedalloc.h"
#include <string.h>
#ifndef WIN32
# include <stdlib.h>
# include <signal.h>
# include <sys/mman.h> // for mmap
# include <unistd.h> // for read close
#else
# include "BLI_winstuff.h"
# include <io.h> // for open close read
#endif
struct BLI_mmap_file {
/* The address to which the file was mapped. */
char *memory;
/* The length of the file (and therefore the mapped region). */
size_t length;
/* Platform-specific handle for the mapping. */
void *handle;
/* Flag to indicate IO errors. Needs to be volatile since it's being set from
* within the signal handler, which is not part of the normal execution flow. */
volatile bool io_error;
};
#ifndef WIN32
/* When using memory-mapped files, any IO errors will result in a SIGBUS signal.
* Therefore, we need to catch that signal and stop reading the file in question.
* To do so, we keep a list of all current FileDatas that use memory-mapped files,
* and if a SIGBUS is caught, we check if the failed address is inside one of the
* mapped regions.
* If it is, we set a flag to indicate a failed read and remap the memory in
* question to a zero-backed region in order to avoid additional signals.
* The code that actually reads the memory area has to check whether the flag was
* set after it's done reading.
* If the error occurred outside of a memory-mapped region, we call the previous
* handler if one was configured and abort the process otherwise.
*/
struct error_handler_data {
ListBase open_mmaps;
char configured;
void (*next_handler)(int, siginfo_t *, void *);
} error_handler = {0};
static void sigbus_handler(int sig, siginfo_t *siginfo, void *ptr)
{
/* We only handle SIGBUS here for now. */
BLI_assert(sig == SIGBUS);
char *error_addr = (char *)siginfo->si_addr;
/* Find the file that this error belongs to. */
LISTBASE_FOREACH (LinkData *, link, &error_handler.open_mmaps) {
BLI_mmap_file *file = link->data;
/* Is the address where the error occurred in this file's mapped range? */
if (error_addr >= file->memory && error_addr < file->memory + file->length) {
file->io_error = true;
/* Replace the mapped memory with zeroes. */
mmap(file->memory, file->length, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
return;
}
}
/* Fall back to other handler if there was one. */
if (error_handler.next_handler) {
error_handler.next_handler(sig, siginfo, ptr);
}
else {
fprintf(stderr, "Unhandled SIGBUS caught\n");
abort();
}
}
/* Ensures that the error handler is set up and ready. */
static bool sigbus_handler_setup(void)
{
if (!error_handler.configured) {
struct sigaction newact = {0}, oldact = {0};
newact.sa_sigaction = sigbus_handler;
newact.sa_flags = SA_SIGINFO;
if (sigaction(SIGBUS, &newact, &oldact)) {
return false;
}
/* Remember the previously configured handler to fall back to it if the error
* does not belong to any of the mapped files. */
error_handler.next_handler = oldact.sa_sigaction;
error_handler.configured = 1;
}
return true;
}
/* Adds a file to the list that the error handler checks. */
static void sigbus_handler_add(BLI_mmap_file *file)
{
BLI_addtail(&error_handler.open_mmaps, BLI_genericNodeN(file));
}
/* Removes a file from the list that the error handler checks. */
static void sigbus_handler_remove(BLI_mmap_file *file)
{
LinkData *link = BLI_findptr(&error_handler.open_mmaps, file, offsetof(LinkData, data));
BLI_freelinkN(&error_handler.open_mmaps, link);
}
#endif
BLI_mmap_file *BLI_mmap_open(int fd)
{
void *memory, *handle = NULL;
size_t length = BLI_lseek(fd, 0, SEEK_END);
#ifndef WIN32
/* Ensure that the SIGBUS handler is configured. */
if (!sigbus_handler_setup()) {
return NULL;
}
/* Map the given file to memory. */
memory = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (memory == MAP_FAILED) {
return NULL;
}
#else
/* Convert the POSIX-style file descriptor to a Windows handle. */
void *file_handle = (void *)_get_osfhandle(fd);
/* Memory mapping on Windows is a two-step process - first we create a mapping,
* then we create a view into that mapping.
* In our case, one view that spans the entire file is enough. */
handle = CreateFileMapping(file_handle, NULL, PAGE_READONLY, 0, 0, NULL);
if (handle == NULL) {
return NULL;
}
memory = MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0);
if (memory == NULL) {
CloseHandle(handle);
return NULL;
}
#endif
/* Now that the mapping was successful, allocate memory and set up the BLI_mmap_file. */
BLI_mmap_file *file = MEM_callocN(sizeof(BLI_mmap_file), __func__);
file->memory = memory;
file->handle = handle;
file->length = length;
#ifndef WIN32
/* Register the file with the error handler. */
sigbus_handler_add(file);
#endif
return file;
}
bool BLI_mmap_read(BLI_mmap_file *file, void *dest, size_t offset, size_t length)
{
/* If a previous read has already failed or we try to read past the end,
* don't even attempt to read any further. */
if (file->io_error || (offset + length > file->length)) {
return false;
}
#ifndef WIN32
/* If an error occurs in this call, sigbus_handler will be called and will set
* file->io_error to true. */
memcpy(dest, file->memory + offset, length);
#else
/* On Windows, we use exception handling to be notified of errors. */
__try {
memcpy(dest, file->memory + offset, length);
}
__except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH) {
file->io_error = true;
return false;
}
#endif
return !file->io_error;
}
void *BLI_mmap_get_pointer(BLI_mmap_file *file)
{
return file->memory;
}
void BLI_mmap_free(BLI_mmap_file *file)
{
#ifndef WIN32
munmap((void *)file->memory, file->length);
sigbus_handler_remove(file);
#else
UnmapViewOfFile(file->memory);
CloseHandle(file->handle);
#endif
MEM_freeN(file);
}