1 /* map and unmap files in various ways
2  *
3  * Copyright: Nicos Dessipris
4  * Wriiten on: 13/02/1990
5  * Updated on:
6  * 10/5/93 J.Cupitt
7  *	- im_mapfilerw() added
8  * 13/12/94 JC
9  *	- ANSIfied
10  * 5/7/99 JC
11  *	- better error if unable to map rw
12  * 31/3/02 JC
13  *	- better mmap() fails error
14  * 19/9/02 JC
15  * 	- added im__mmap()/im__munmap() with windows versions
16  * 5/1/04 Lev Serebryakov
17  * 	- patched for freebsd compatibility
18  * 5/2/04 JC
19  *	- now records length as well as base, so we unmap the right amount of
20  *	  memory even if files change behind our back
21  * 1/1/10
22  * 	- set NOCACHE if we can ... helps OS X performance a lot
23  * 25/3/11
24  * 	- move to vips_ namespace
25  */
26 
27 /*
28 
29     This file is part of VIPS.
30 
31     VIPS is free software; you can redistribute it and/or modify
32     it under the terms of the GNU Lesser General Public License as published by
33     the Free Software Foundation; either version 2 of the License, or
34     (at your option) any later version.
35 
36     This program is distributed in the hope that it will be useful,
37     but WITHOUT ANY WARRANTY; without even the implied warranty of
38     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
39     GNU Lesser General Public License for more details.
40 
41     You should have received a copy of the GNU Lesser General Public License
42     along with this program; if not, write to the Free Software
43     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
44     02110-1301  USA
45 
46  */
47 
48 /*
49 
50     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
51 
52  */
53 
54 /*
55 #define DEBUG
56  */
57 
58 #ifdef HAVE_CONFIG_H
59 #include <config.h>
60 #endif /*HAVE_CONFIG_H*/
61 #include <vips/intl.h>
62 
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <errno.h>
67 #include <assert.h>
68 
69 #include <sys/types.h>
70 #ifdef HAVE_SYS_MMAN_H
71 #include <sys/mman.h>
72 #endif /*HAVE_SYS_MMAN_H*/
73 #ifdef HAVE_SYS_FILE_H
74 #include <sys/file.h>
75 #endif /*HAVE_SYS_FILE_H*/
76 #include <sys/stat.h>
77 #ifdef HAVE_UNISTD_H
78 #include <unistd.h>
79 #endif /*HAVE_UNISTD_H*/
80 
81 #include <vips/vips.h>
82 
83 #ifdef G_OS_WIN32
84 #ifndef S_ISREG
85 #define S_ISREG(m) (!!(m & _S_IFREG))
86 #endif
87 #include <windows.h>
88 #include <io.h>
89 #endif /*G_OS_WIN32*/
90 
91 /* Does this fd support mmap. Pipes won't, for example.
92  */
93 gboolean
vips__mmap_supported(int fd)94 vips__mmap_supported( int fd )
95 {
96 	void *baseaddr;
97 	size_t length = 4096;
98 	off_t offset = 0;
99 
100 #ifdef G_OS_WIN32
101 {
102 	HANDLE hFile = (HANDLE) _get_osfhandle( fd );
103 
104 	DWORD flProtect;
105         HANDLE hMMFile;
106 
107 	DWORD dwDesiredAccess;
108 	ULARGE_INTEGER quad;
109 	DWORD dwFileOffsetHigh;
110 	DWORD dwFileOffsetLow;
111 
112 	flProtect = PAGE_READONLY;
113         if( !(hMMFile = CreateFileMapping( hFile,
114 		NULL, flProtect, 0, 0, NULL )) )
115                 return( FALSE );
116 
117 	dwDesiredAccess = FILE_MAP_READ;
118 	quad.QuadPart = offset;
119 	dwFileOffsetLow = quad.LowPart;
120 	dwFileOffsetHigh = quad.HighPart;
121         if( !(baseaddr = (char *)MapViewOfFile( hMMFile, dwDesiredAccess,
122 		dwFileOffsetHigh, dwFileOffsetLow, length )) ) {
123 		CloseHandle( hMMFile );
124                 return( FALSE );
125         }
126 	CloseHandle( hMMFile );
127 	UnmapViewOfFile( baseaddr );
128 }
129 #else /*!G_OS_WIN32*/
130 {
131 	int prot = PROT_READ;
132 	int flags = MAP_SHARED;
133 
134 	baseaddr = mmap( 0, length, prot, flags, fd, (off_t) offset );
135 	if( baseaddr == MAP_FAILED )
136 		return( FALSE );
137 	munmap( baseaddr, length );
138 }
139 #endif /*G_OS_WIN32*/
140 
141 	return( TRUE );
142 }
143 
144 void *
vips__mmap(int fd,int writeable,size_t length,gint64 offset)145 vips__mmap( int fd, int writeable, size_t length, gint64 offset )
146 {
147 	void *baseaddr;
148 
149 #ifdef DEBUG
150 	printf( "vips__mmap: length = 0x%zx, offset = 0x%lx\n",
151 		length, offset );
152 #endif /*DEBUG*/
153 
154 #ifdef G_OS_WIN32
155 {
156 	HANDLE hFile = (HANDLE) _get_osfhandle( fd );
157 
158 	DWORD flProtect;
159 	DWORD dwDesiredAccess;
160 
161         HANDLE hMMFile;
162 
163 	ULARGE_INTEGER quad;
164 	DWORD dwFileOffsetHigh;
165 	DWORD dwFileOffsetLow;
166 
167 	if( writeable ) {
168 		flProtect = PAGE_READWRITE;
169 		dwDesiredAccess = FILE_MAP_WRITE;
170 	}
171 	else {
172 		flProtect = PAGE_READONLY;
173 		dwDesiredAccess = FILE_MAP_READ;
174 	}
175 
176 	quad.QuadPart = offset;
177 	dwFileOffsetLow = quad.LowPart;
178 	dwFileOffsetHigh = quad.HighPart;
179 
180         if( !(hMMFile = CreateFileMapping( hFile,
181 		NULL, flProtect, 0, 0, NULL )) ) {
182                 vips_error_system( GetLastError(), "vips_mapfile",
183 			"%s", _( "unable to CreateFileMapping" ) );
184 		printf( "CreateFileMapping failed: %s\n", vips_error_buffer() );
185                 return( NULL );
186         }
187 
188         if( !(baseaddr = (char *)MapViewOfFile( hMMFile, dwDesiredAccess,
189 		dwFileOffsetHigh, dwFileOffsetLow, length )) ) {
190                 vips_error_system( GetLastError(), "vips_mapfile",
191 			"%s", _( "unable to MapViewOfFile" ) );
192 		printf( "MapViewOfFile failed: %s\n", vips_error_buffer() );
193 		CloseHandle( hMMFile );
194                 return( NULL );
195         }
196 
197 	/* Can close mapping now ... view stays until UnmapViewOfFile().
198 
199 		FIXME ... is this a performance problem?
200 
201 	 */
202 	CloseHandle( hMMFile );
203 }
204 #else /*!G_OS_WIN32*/
205 {
206 	int prot;
207 	int flags;
208 
209 	if( writeable )
210 		prot = PROT_WRITE;
211 	else
212 		prot = PROT_READ;
213 
214 	flags = MAP_SHARED;
215 
216 	/* OS X caches mmapped files very aggressively if this flags is not
217 	 * set. Scanning a large file without this flag will cause every other
218 	 * process to get swapped out and kill performance.
219 	 */
220 #ifdef MAP_NOCACHE
221 	flags |= MAP_NOCACHE;
222 #endif /*MAP_NOCACHE*/
223 
224 	/* Casting gint64 to off_t should be safe, even on *nixes without
225 	 * LARGEFILE.
226 	 */
227 
228 	baseaddr = mmap( 0, length, prot, flags, fd, (off_t) offset );
229 	if( baseaddr == MAP_FAILED ) {
230 		vips_error_system( errno, "vips_mapfile",
231 			"%s", _( "unable to mmap" ) );
232 		g_warning( _( "map failed (%s), "
233 			"running very low on system resources, "
234 			"expect a crash soon" ), strerror( errno ) );
235 		return( NULL );
236 	}
237 }
238 #endif /*G_OS_WIN32*/
239 
240 	return( baseaddr );
241 }
242 
243 int
vips__munmap(const void * start,size_t length)244 vips__munmap( const void *start, size_t length )
245 {
246 #ifdef G_OS_WIN32
247 	if( !UnmapViewOfFile( (void *) start ) ) {
248 		vips_error_system( GetLastError(), "vips_mapfile",
249 			"%s", _( "unable to UnmapViewOfFile" ) );
250 		return( -1 );
251 	}
252 #else /*!G_OS_WIN32*/
253 	if( munmap( (void *) start, length ) < 0 ) {
254 		vips_error_system( errno, "vips_mapfile",
255 			"%s", _( "unable to munmap file" ) );
256 		return( -1 );
257 	}
258 #endif /*G_OS_WIN32*/
259 
260 	return( 0 );
261 }
262 
263 int
vips_mapfile(VipsImage * im)264 vips_mapfile( VipsImage *im )
265 {
266 	struct stat st;
267 	mode_t m;
268 
269 	assert( !im->baseaddr );
270 
271 	/* Check the size of the file; if it is less than 64 bytes, then flag
272 	 * an error, we won't be able to read the vips header without a segv.
273 	 */
274 	g_assert( im->file_length > 0 );
275 	if( im->file_length < 64 ) {
276 		vips_error( "vips_mapfile",
277 			"%s", _( "file is less than 64 bytes" ) );
278 		return( -1 );
279 	}
280 	if( fstat( im->fd, &st ) == -1 ) {
281 		vips_error( "vips_mapfile",
282 			"%s", _( "unable to get file status" ) );
283 		return( -1 );
284 	}
285 	m = (mode_t) st.st_mode;
286 	if( !S_ISREG( m ) ) {
287 		vips_error( "vips_mapfile",
288 			"%s", _( "not a regular file" ) );
289 		return( -1 );
290 	}
291 
292 	if( !(im->baseaddr = vips__mmap( im->fd, 0, im->file_length, 0 )) )
293 		return( -1 );
294 
295 	im->length = im->file_length;
296 
297 	return( 0 );
298 }
299 
300 /* As above, but map read/write.
301  */
302 int
vips_mapfilerw(VipsImage * im)303 vips_mapfilerw( VipsImage *im )
304 {
305 	struct stat st;
306 	mode_t m;
307 
308 	assert( !im->baseaddr );
309 
310 	/* Check the size of the file if it is less than 64 bytes return
311 	 * make also sure that it is a regular file
312 	 */
313 	g_assert( im->file_length > 0 );
314 	if( fstat( im->fd, &st ) == -1 ) {
315 		vips_error( "vips_mapfilerw",
316 			"%s", _( "unable to get file status" ) );
317 		return( -1 );
318 	}
319 	m = (mode_t) st.st_mode;
320 	if( im->file_length < 64 || !S_ISREG( m ) ) {
321 		vips_error( "vips_mapfile",
322 			"%s", _( "unable to read data" ) );
323 		return( -1 );
324 	}
325 
326 	if( !(im->baseaddr = vips__mmap( im->fd, 1, im->file_length, 0 )) )
327 		return( -1 );
328 
329 	im->length = im->file_length;
330 
331 	return( 0 );
332 }
333 
334 /* From im_rwcheck() ... image needs to be a completely mapped read-only file,
335  * we try to remap it read-write.
336  */
337 int
vips_remapfilerw(VipsImage * image)338 vips_remapfilerw( VipsImage *image )
339 {
340 	void *baseaddr;
341 
342 #ifdef DEBUG
343 	printf( "vips_remapfilerw:\n" );
344 #endif /*DEBUG*/
345 
346 #ifdef G_OS_WIN32
347 {
348 	HANDLE hFile = (HANDLE) _get_osfhandle( image->fd );
349         HANDLE hMMFile;
350 
351         if( !(hMMFile = CreateFileMapping( hFile,
352 		NULL, PAGE_READWRITE, 0, 0, NULL )) ) {
353                 vips_error_system( GetLastError(), "vips_mapfile",
354 			"%s", _( "unable to CreateFileMapping" ) );
355                 return( -1 );
356         }
357 
358 	if( !UnmapViewOfFile( image->baseaddr ) ) {
359 		vips_error_system( GetLastError(), "vips_mapfile",
360 			"%s", _( "unable to UnmapViewOfFile" ) );
361 		return( -1 );
362 	}
363         if( !(baseaddr = (char *)MapViewOfFileEx( hMMFile, FILE_MAP_WRITE,
364 		0, 0, 0, image->baseaddr )) ) {
365                 vips_error_system( GetLastError(), "vips_mapfile",
366 			"%s", _( "unable to MapViewOfFile" ) );
367 		CloseHandle( hMMFile );
368                 return( -1 );
369         }
370 
371 	/* Can close mapping now ... view stays until UnmapViewOfFile().
372 
373 		FIXME ... is this a performance problem?
374 
375 	 */
376 	CloseHandle( hMMFile );
377 }
378 #else /*!G_OS_WIN32*/
379 {
380 	assert( image->dtype == VIPS_IMAGE_MMAPIN );
381 
382 	baseaddr = mmap( image->baseaddr, image->length,
383 		PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
384 		image->fd, 0 );
385 	if( baseaddr == (void *)-1 ) {
386 		vips_error( "vips_mapfile", _( "unable to mmap: \"%s\" - %s" ),
387 			image->filename, strerror( errno ) );
388 		return( -1 );
389 	}
390 }
391 #endif /*G_OS_WIN32*/
392 
393 	image->dtype = VIPS_IMAGE_MMAPINRW;
394 
395 	if( baseaddr != image->baseaddr ) {
396 		vips_error( "vips_mapfile", _( "unable to mmap \"%s\" to same "
397 			"address" ), image->filename );
398 		image->baseaddr = baseaddr;
399 		return( -1 );
400 	}
401 
402 	return( 0 );
403 }
404 
405