1 /* Manage sets of mmap buffers on an image.
2  *
3  * 30/10/06
4  *	- from region.c
5  * 19/3/09
6  *	- block mmaps of nodata images
7  */
8 
9 /*
10 
11     This file is part of VIPS.
12 
13     VIPS is free software; you can redistribute it and/or modify
14     it under the terms of the GNU Lesser General Public License as published by
15     the Free Software Foundation; either version 2 of the License, or
16     (at your option) any later version.
17 
18     This program is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21     GNU Lesser General Public License for more details.
22 
23     You should have received a copy of the GNU Lesser General Public License
24     along with this program; if not, write to the Free Software
25     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26     02110-1301  USA
27 
28  */
29 
30 /*
31 
32     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
33 
34  */
35 
36 /*
37 #define DEBUG_TOTAL
38 #define DEBUG
39  */
40 
41 #ifdef HAVE_CONFIG_H
42 #include <config.h>
43 #endif /*HAVE_CONFIG_H*/
44 #include <vips/intl.h>
45 
46 #include <stdio.h>
47 #include <stdlib.h>
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h>
50 #endif /*HAVE_UNISTD_H*/
51 #include <errno.h>
52 #include <string.h>
53 #ifdef HAVE_SYS_MMAN_H
54 #include <sys/mman.h>
55 #endif
56 
57 #include <vips/vips.h>
58 #include <vips/internal.h>
59 #include <vips/thread.h>
60 
61 #ifdef G_OS_WIN32
62 #include <windows.h>
63 #endif /*G_OS_WIN32*/
64 
65 /* Sanity checking ... write to this during read tests to make sure we don't
66  * get optimized out.
67  */
68 int vips__read_test;
69 
70 /* Add this many lines above and below the mmap() window.
71  */
72 int vips__window_margin_pixels = VIPS__WINDOW_MARGIN_PIXELS;
73 
74 /* Always map at least this many bytes. There's no point making tiny windows
75  * on small files.
76  */
77 int vips__window_margin_bytes = VIPS__WINDOW_MARGIN_BYTES;
78 
79 /* Track global mmap usage.
80  */
81 #ifdef DEBUG_TOTAL
82 static int total_mmap_usage = 0;
83 static int max_mmap_usage = 0;
84 #endif /*DEBUG_TOTAL*/
85 
86 static int
vips_window_unmap(VipsWindow * window)87 vips_window_unmap( VipsWindow *window )
88 {
89 	/* unmap the old window
90 	 */
91 	if( window->baseaddr ) {
92 		if( vips__munmap( window->baseaddr, window->length ) )
93 			return( -1 );
94 
95 #ifdef DEBUG_TOTAL
96 		g_mutex_lock( vips__global_lock );
97 		total_mmap_usage -= window->length;
98 		g_assert( total_mmap_usage >= 0 );
99 		g_mutex_unlock( vips__global_lock );
100 #endif /*DEBUG_TOTAL*/
101 
102 		window->data = NULL;
103 		window->baseaddr = NULL;
104 		window->length = 0;
105 	}
106 
107 	return( 0 );
108 }
109 
110 static int
vips_window_free(VipsWindow * window)111 vips_window_free( VipsWindow *window )
112 {
113 	VipsImage *im = window->im;
114 
115 	g_assert( window->ref_count == 0 );
116 
117 #ifdef DEBUG
118 	printf( "** vips_window_free: window top = %d, height = %d (%p)\n",
119 		window->top, window->height, window );
120 	printf( "vips_window_unref: %d windows left\n",
121 		g_slist_length( im->windows ) );
122 #endif /*DEBUG*/
123 
124 	g_assert( g_slist_find( im->windows, window ) );
125 	im->windows = g_slist_remove( im->windows, window );
126 
127 	if( vips_window_unmap( window ) )
128 		return( -1 );
129 
130 	window->im = NULL;
131 
132 	g_free( window );
133 
134 	return( 0 );
135 }
136 
137 int
vips_window_unref(VipsWindow * window)138 vips_window_unref( VipsWindow *window )
139 {
140 	VipsImage *im = window->im;
141 
142 	g_mutex_lock( im->sslock );
143 
144 #ifdef DEBUG
145 	printf( "vips_window_unref: window top = %d, height = %d, count = %d\n",
146 		window->top, window->height, window->ref_count );
147 #endif /*DEBUG*/
148 
149 	g_assert( window->ref_count > 0 );
150 
151 	window->ref_count -= 1;
152 
153 	if( window->ref_count == 0 ) {
154 		if( vips_window_free( window ) ) {
155 			g_mutex_unlock( im->sslock );
156 			return( -1 );
157 		}
158 	}
159 
160 	g_mutex_unlock( im->sslock );
161 
162 	return( 0 );
163 }
164 
165 #ifdef DEBUG_TOTAL
166 static void
trace_mmap_usage(void)167 trace_mmap_usage( void )
168 {
169 	g_mutex_lock( vips__global_lock );
170 	{
171 		static int last_total = 0;
172 		int total = total_mmap_usage / (1024 * 1024);
173 		int max = max_mmap_usage / (1024 * 1024);
174 
175 		if( total != last_total ) {
176 			printf( "vips_window_set: current mmap "
177 				"usage of ~%dMB (high water mark %dMB)\n",
178 				total, max );
179 			last_total = total;
180 		}
181 	}
182 	g_mutex_unlock( vips__global_lock );
183 }
184 #endif /*DEBUG_TOTAL*/
185 
186 static int
vips_getpagesize(void)187 vips_getpagesize( void )
188 {
189 	static int pagesize = 0;
190 
191 	if( !pagesize ) {
192 #ifdef G_OS_WIN32
193 		SYSTEM_INFO si;
194 
195 		GetSystemInfo( &si );
196 
197 		pagesize = si.dwAllocationGranularity;
198 #else /*!G_OS_WIN32*/
199 		pagesize = getpagesize();
200 #endif /*G_OS_WIN32*/
201 
202 #ifdef DEBUG_TOTAL
203 		printf( "vips_getpagesize: 0x%x\n", pagesize );
204 #endif /*DEBUG_TOTAL*/
205 	}
206 
207 	return( pagesize );
208 }
209 
210 /* Map a window into a file.
211  */
212 static int
vips_window_set(VipsWindow * window,int top,int height)213 vips_window_set( VipsWindow *window, int top, int height )
214 {
215 	int pagesize = vips_getpagesize();
216 
217 	void *baseaddr;
218 	gint64 start, end, pagestart;
219 	size_t length, pagelength;
220 
221 	/* Calculate start and length for our window.
222 	 */
223 	start = window->im->sizeof_header +
224 		VIPS_IMAGE_SIZEOF_LINE( window->im ) * top;
225 	length = VIPS_IMAGE_SIZEOF_LINE( window->im ) * height;
226 
227 	pagestart = start - start % pagesize;
228 	end = start + length;
229 	pagelength = end - pagestart;
230 
231 	/* Make sure we have enough file.
232 	 */
233 	if( end > window->im->file_length ) {
234 		vips_error( "vips_window_set",
235 			_( "unable to read data for \"%s\", %s" ),
236 			window->im->filename, _( "file has been truncated" ) );
237 		return( -1 );
238 	}
239 
240 	if( vips_window_unmap( window ) )
241 		return( -1 );
242 
243 	if( !(baseaddr = vips__mmap( window->im->fd,
244 		0, pagelength, pagestart )) )
245 		return( -1 );
246 
247 	window->baseaddr = baseaddr;
248 	window->length = pagelength;
249 
250 	window->data = (VipsPel *) baseaddr + (start - pagestart);
251 	window->top = top;
252 	window->height = height;
253 
254 	/* Sanity check ... make sure the data pointer is readable.
255 	 */
256 	vips__read_test &= window->data[0];
257 
258 #ifdef DEBUG_TOTAL
259 	g_mutex_lock( vips__global_lock );
260 	total_mmap_usage += window->length;
261 	if( total_mmap_usage > max_mmap_usage )
262 		max_mmap_usage = total_mmap_usage;
263 	g_mutex_unlock( vips__global_lock );
264 	trace_mmap_usage();
265 #endif /*DEBUG_TOTAL*/
266 
267 	return( 0 );
268 }
269 
270 /* Make a new window.
271  */
272 static VipsWindow *
vips_window_new(VipsImage * im,int top,int height)273 vips_window_new( VipsImage *im, int top, int height )
274 {
275 	VipsWindow *window;
276 
277 	if( !(window = VIPS_NEW( NULL, VipsWindow )) )
278 		return( NULL );
279 
280 	window->ref_count = 1;
281 	window->im = im;
282 	window->top = 0;
283 	window->height = 0;
284 	window->data = NULL;
285 	window->baseaddr = NULL;
286 	window->length = 0;
287 	im->windows = g_slist_prepend( im->windows, window );
288 
289 	if( vips_window_set( window, top, height ) ) {
290 		vips_window_free( window );
291 		return( NULL );
292 	}
293 
294 #ifdef DEBUG
295 	printf( "** vips_window_new: window top = %d, height = %d (%p)\n",
296 		window->top, window->height, window );
297 #endif /*DEBUG*/
298 
299 	return( window );
300 }
301 
302 /* A request for an area of pixels.
303  */
304 typedef struct {
305 	int top;
306 	int height;
307 } request_t;
308 
309 static void *
vips_window_fits(VipsWindow * window,request_t * req,void * b)310 vips_window_fits( VipsWindow *window, request_t *req, void *b )
311 {
312 	if( window->top <= req->top &&
313 		window->top + window->height >= req->top + req->height )
314 		return( window );
315 
316 	return( NULL );
317 }
318 
319 /* Find an existing window that fits within top/height and return a ref.
320  */
321 static VipsWindow *
vips_window_find(VipsImage * im,int top,int height)322 vips_window_find( VipsImage *im, int top, int height )
323 {
324 	request_t req;
325 	VipsWindow *window;
326 
327 	req.top = top;
328 	req.height = height;
329 	window = vips_slist_map2( im->windows,
330 		(VipsSListMap2Fn) vips_window_fits, &req, NULL );
331 
332 	if( window ) {
333 		window->ref_count += 1;
334 
335 #ifdef DEBUG
336 		printf( "vips_window_find: ref window top = %d, height = %d, "
337 			"count = %d\n",
338 			top, height, window->ref_count );
339 #endif /*DEBUG*/
340 	}
341 
342 	return( window );
343 }
344 
345 /* Old API. Just a compat stub now.
346  */
347 VipsWindow *
vips_window_ref(VipsImage * im,int top,int height)348 vips_window_ref( VipsImage *im, int top, int height )
349 {
350 	return( NULL );
351 }
352 
353 /* Update a window to make it enclose top/height.
354  */
355 VipsWindow *
vips_window_take(VipsWindow * window,VipsImage * im,int top,int height)356 vips_window_take( VipsWindow *window, VipsImage *im, int top, int height )
357 {
358 	int margin;
359 
360 	/* We have a window and it has the pixels we need.
361 	 */
362 	if( window &&
363 		window->top <= top &&
364 		window->top + window->height >= top + height )
365 		return( window );
366 
367 	g_mutex_lock( im->sslock );
368 
369 	/* We have a window and we are the only ref to it ... scroll.
370 	 */
371 	if( window &&
372 		window->ref_count == 1 ) {
373 		if( vips_window_set( window, top, height ) ) {
374 			g_mutex_unlock( im->sslock );
375 			vips_window_unref( window );
376 
377 			return( NULL );
378 		}
379 
380 		g_mutex_unlock( im->sslock );
381 
382 		return( window );
383 	}
384 
385 	/* There's more than one ref to the window. We can just decrement.
386 	 * Don't call _unref, since we've inside the lock.
387 	 */
388 	if( window )
389 		window->ref_count -= 1;
390 
391 	/* Is there an existing window we can reuse?
392 	 */
393 	if( (window = vips_window_find( im, top, height )) ) {
394 		g_mutex_unlock( im->sslock );
395 
396 		return( window );
397 	}
398 
399 	/* We have to make a new window. Make it a bit bigger than strictly
400 	 * necessary.
401 	 */
402 	margin = VIPS_MIN( vips__window_margin_pixels,
403 		vips__window_margin_bytes / VIPS_IMAGE_SIZEOF_LINE( im ) );
404 	top -= margin;
405 	height += margin * 2;
406 	top = VIPS_CLIP( 0, top, im->Ysize - 1 );
407 	height = VIPS_CLIP( 0, height, im->Ysize - top );
408 
409 	if( !(window = vips_window_new( im, top, height )) ) {
410 		g_mutex_unlock( im->sslock );
411 		return( NULL );
412 	}
413 
414 	g_mutex_unlock( im->sslock );
415 
416 	return( window );
417 }
418 
419 void
vips_window_print(VipsWindow * window)420 vips_window_print( VipsWindow *window )
421 {
422 	printf( "VipsWindow: %p ref_count = %d, ", window, window->ref_count );
423 	printf( "im = %p, ", window->im );
424 	printf( "top = %d, ", window->top );
425 	printf( "height = %d, ", window->height );
426 	printf( "data = %p, ", window->data );
427 	printf( "baseaddr = %p, ", window->baseaddr );
428 	printf( "length = %zd\n", window->length );
429 }
430