234 lines
6.9 KiB
C
234 lines
6.9 KiB
C
|
|
/*
|
||
|
|
* 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);
|
||
|
|
}
|