Blender  V2.93
BLI_mmap.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2020 Blender Foundation.
17  * All rights reserved.
18  */
19 
24 #include "BLI_mmap.h"
25 #include "BLI_fileops.h"
26 #include "BLI_listbase.h"
27 #include "MEM_guardedalloc.h"
28 
29 #include <string.h>
30 
31 #ifndef WIN32
32 # include <signal.h>
33 # include <stdlib.h>
34 # include <sys/mman.h> // for mmap
35 # include <unistd.h> // for read close
36 #else
37 # include "BLI_winstuff.h"
38 # include <io.h> // for open close read
39 #endif
40 
41 struct BLI_mmap_file {
42  /* The address to which the file was mapped. */
43  char *memory;
44 
45  /* The length of the file (and therefore the mapped region). */
46  size_t length;
47 
48  /* Platform-specific handle for the mapping. */
49  void *handle;
50 
51  /* Flag to indicate IO errors. Needs to be volatile since it's being set from
52  * within the signal handler, which is not part of the normal execution flow. */
53  volatile bool io_error;
54 };
55 
56 #ifndef WIN32
57 /* When using memory-mapped files, any IO errors will result in a SIGBUS signal.
58  * Therefore, we need to catch that signal and stop reading the file in question.
59  * To do so, we keep a list of all current FileDatas that use memory-mapped files,
60  * and if a SIGBUS is caught, we check if the failed address is inside one of the
61  * mapped regions.
62  * If it is, we set a flag to indicate a failed read and remap the memory in
63  * question to a zero-backed region in order to avoid additional signals.
64  * The code that actually reads the memory area has to check whether the flag was
65  * set after it's done reading.
66  * If the error occurred outside of a memory-mapped region, we call the previous
67  * handler if one was configured and abort the process otherwise.
68  */
69 
70 static struct error_handler_data {
72  char configured;
73  void (*next_handler)(int, siginfo_t *, void *);
74 } error_handler = {0};
75 
76 static void sigbus_handler(int sig, siginfo_t *siginfo, void *ptr)
77 {
78  /* We only handle SIGBUS here for now. */
79  BLI_assert(sig == SIGBUS);
80 
81  char *error_addr = (char *)siginfo->si_addr;
82  /* Find the file that this error belongs to. */
84  BLI_mmap_file *file = link->data;
85 
86  /* Is the address where the error occurred in this file's mapped range? */
87  if (error_addr >= file->memory && error_addr < file->memory + file->length) {
88  file->io_error = true;
89 
90  /* Replace the mapped memory with zeroes. */
91  const void *mapped_memory = mmap(
92  file->memory, file->length, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
93  if (mapped_memory == MAP_FAILED) {
94  fprintf(stderr, "SIGBUS handler: Error replacing mapped file with zeros\n");
95  }
96 
97  return;
98  }
99  }
100 
101  /* Fall back to other handler if there was one. */
103  error_handler.next_handler(sig, siginfo, ptr);
104  }
105  else {
106  fprintf(stderr, "Unhandled SIGBUS caught\n");
107  abort();
108  }
109 }
110 
111 /* Ensures that the error handler is set up and ready. */
112 static bool sigbus_handler_setup(void)
113 {
114  if (!error_handler.configured) {
115  struct sigaction newact = {0}, oldact = {0};
116 
117  newact.sa_sigaction = sigbus_handler;
118  newact.sa_flags = SA_SIGINFO;
119 
120  if (sigaction(SIGBUS, &newact, &oldact)) {
121  return false;
122  }
123 
124  /* Remember the previously configured handler to fall back to it if the error
125  * does not belong to any of the mapped files. */
126  error_handler.next_handler = oldact.sa_sigaction;
128  }
129 
130  return true;
131 }
132 
133 /* Adds a file to the list that the error handler checks. */
135 {
137 }
138 
139 /* Removes a file from the list that the error handler checks. */
141 {
144 }
145 #endif
146 
148 {
149  void *memory, *handle = NULL;
150  size_t length = BLI_lseek(fd, 0, SEEK_END);
151 
152 #ifndef WIN32
153  /* Ensure that the SIGBUS handler is configured. */
154  if (!sigbus_handler_setup()) {
155  return NULL;
156  }
157 
158  /* Map the given file to memory. */
159  memory = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
160  if (memory == MAP_FAILED) {
161  return NULL;
162  }
163 #else
164  /* Convert the POSIX-style file descriptor to a Windows handle. */
165  void *file_handle = (void *)_get_osfhandle(fd);
166  /* Memory mapping on Windows is a two-step process - first we create a mapping,
167  * then we create a view into that mapping.
168  * In our case, one view that spans the entire file is enough. */
169  handle = CreateFileMapping(file_handle, NULL, PAGE_READONLY, 0, 0, NULL);
170  if (handle == NULL) {
171  return NULL;
172  }
173  memory = MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0);
174  if (memory == NULL) {
175  CloseHandle(handle);
176  return NULL;
177  }
178 #endif
179 
180  /* Now that the mapping was successful, allocate memory and set up the BLI_mmap_file. */
181  BLI_mmap_file *file = MEM_callocN(sizeof(BLI_mmap_file), __func__);
182  file->memory = memory;
183  file->handle = handle;
184  file->length = length;
185 
186 #ifndef WIN32
187  /* Register the file with the error handler. */
189 #endif
190 
191  return file;
192 }
193 
194 bool BLI_mmap_read(BLI_mmap_file *file, void *dest, size_t offset, size_t length)
195 {
196  /* If a previous read has already failed or we try to read past the end,
197  * don't even attempt to read any further. */
198  if (file->io_error || (offset + length > file->length)) {
199  return false;
200  }
201 
202 #ifndef WIN32
203  /* If an error occurs in this call, sigbus_handler will be called and will set
204  * file->io_error to true. */
205  memcpy(dest, file->memory + offset, length);
206 #else
207  /* On Windows, we use exception handling to be notified of errors. */
208  __try {
209  memcpy(dest, file->memory + offset, length);
210  }
211  __except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER :
212  EXCEPTION_CONTINUE_SEARCH) {
213  file->io_error = true;
214  return false;
215  }
216 #endif
217 
218  return !file->io_error;
219 }
220 
222 {
223  return file->memory;
224 }
225 
227 {
228 #ifndef WIN32
229  munmap((void *)file->memory, file->length);
231 #else
232  UnmapViewOfFile(file->memory);
233  CloseHandle(file->handle);
234 #endif
235 
236  MEM_freeN(file);
237 }
#define BLI_assert(a)
Definition: BLI_assert.h:58
File and directory operations.
int64_t BLI_lseek(int fd, int64_t offset, int whence)
Definition: storage.c:195
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:172
void BLI_freelinkN(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:281
struct LinkData * BLI_genericNodeN(void *data)
Definition: listbase.c:923
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:110
void * BLI_findptr(const struct ListBase *listbase, const void *ptr, const int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
static bool sigbus_handler_setup(void)
Definition: BLI_mmap.c:112
void BLI_mmap_free(BLI_mmap_file *file)
Definition: BLI_mmap.c:226
bool BLI_mmap_read(BLI_mmap_file *file, void *dest, size_t offset, size_t length)
Definition: BLI_mmap.c:194
static void sigbus_handler_remove(BLI_mmap_file *file)
Definition: BLI_mmap.c:140
static void sigbus_handler(int sig, siginfo_t *siginfo, void *ptr)
Definition: BLI_mmap.c:76
static struct error_handler_data error_handler
static void sigbus_handler_add(BLI_mmap_file *file)
Definition: BLI_mmap.c:134
BLI_mmap_file * BLI_mmap_open(int fd)
Definition: BLI_mmap.c:147
void * BLI_mmap_get_pointer(BLI_mmap_file *file)
Definition: BLI_mmap.c:221
Compatibility-like things for windows.
Read Guarded memory(de)allocation.
SIMD_FORCE_INLINE btScalar length(const btQuaternion &q)
Return the length of a quaternion.
Definition: btQuaternion.h:895
FILE * file
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:41
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:45
char * memory
Definition: BLI_mmap.c:43
void * handle
Definition: BLI_mmap.c:49
size_t length
Definition: BLI_mmap.c:46
volatile bool io_error
Definition: BLI_mmap.c:53
ListBase open_mmaps
Definition: BLI_mmap.c:71
void(* next_handler)(int, siginfo_t *, void *)
Definition: BLI_mmap.c:73
PointerRNA * ptr
Definition: wm_files.c:3157