1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2020 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20 
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #ifdef HAVE_SYS_MMAN_H
30 #include <sys/mman.h>
31 #endif
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <errno.h>
35 
36 #include "gmime-stream-mmap.h"
37 
38 
39 /**
40  * SECTION: gmime-stream-mmap
41  * @title: GMimeStreamMmap
42  * @short_description: A memory-mapped file stream
43  * @see_also: #GMimeStream
44  *
45  * A #GMimeStream implementation using a memory-mapped file backing
46  * store. This may be faster than #GMimeStreamFs or #GMimeStreamFile
47  * but you'll have to do your own performance checking to be sure for
48  * your particular application/platform.
49  **/
50 
51 
52 static void g_mime_stream_mmap_class_init (GMimeStreamMmapClass *klass);
53 static void g_mime_stream_mmap_init (GMimeStreamMmap *stream, GMimeStreamMmapClass *klass);
54 static void g_mime_stream_mmap_finalize (GObject *object);
55 
56 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
57 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
58 static int stream_flush (GMimeStream *stream);
59 static int stream_close (GMimeStream *stream);
60 static gboolean stream_eos (GMimeStream *stream);
61 static int stream_reset (GMimeStream *stream);
62 static gint64 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence);
63 static gint64 stream_tell (GMimeStream *stream);
64 static gint64 stream_length (GMimeStream *stream);
65 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
66 
67 
68 static GMimeStreamClass *parent_class = NULL;
69 
70 
71 GType
g_mime_stream_mmap_get_type(void)72 g_mime_stream_mmap_get_type (void)
73 {
74 	static GType type = 0;
75 
76 	if (!type) {
77 		static const GTypeInfo info = {
78 			sizeof (GMimeStreamMmapClass),
79 			NULL, /* base_class_init */
80 			NULL, /* base_class_finalize */
81 			(GClassInitFunc) g_mime_stream_mmap_class_init,
82 			NULL, /* class_finalize */
83 			NULL, /* class_data */
84 			sizeof (GMimeStreamMmap),
85 			0,    /* n_preallocs */
86 			(GInstanceInitFunc) g_mime_stream_mmap_init,
87 		};
88 
89 		type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamMmap", &info, 0);
90 	}
91 
92 	return type;
93 }
94 
95 
96 static void
g_mime_stream_mmap_class_init(GMimeStreamMmapClass * klass)97 g_mime_stream_mmap_class_init (GMimeStreamMmapClass *klass)
98 {
99 	GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
100 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
101 
102 	parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
103 
104 	object_class->finalize = g_mime_stream_mmap_finalize;
105 
106 	stream_class->read = stream_read;
107 	stream_class->write = stream_write;
108 	stream_class->flush = stream_flush;
109 	stream_class->close = stream_close;
110 	stream_class->eos = stream_eos;
111 	stream_class->reset = stream_reset;
112 	stream_class->seek = stream_seek;
113 	stream_class->tell = stream_tell;
114 	stream_class->length = stream_length;
115 	stream_class->substream = stream_substream;
116 }
117 
118 static void
g_mime_stream_mmap_init(GMimeStreamMmap * stream,GMimeStreamMmapClass * klass)119 g_mime_stream_mmap_init (GMimeStreamMmap *stream, GMimeStreamMmapClass *klass)
120 {
121 	stream->owner = TRUE;
122 	stream->eos = FALSE;
123 	stream->fd = -1;
124 	stream->map = NULL;
125 	stream->maplen = 0;
126 }
127 
128 static void
g_mime_stream_mmap_finalize(GObject * object)129 g_mime_stream_mmap_finalize (GObject *object)
130 {
131 	GMimeStream *stream = (GMimeStream *) object;
132 
133 	stream_close (stream);
134 
135 	G_OBJECT_CLASS (parent_class)->finalize (object);
136 }
137 
138 
139 static ssize_t
stream_read(GMimeStream * stream,char * buf,size_t len)140 stream_read (GMimeStream *stream, char *buf, size_t len)
141 {
142 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
143 	register char *mapptr;
144 	ssize_t nread;
145 
146 	if (mm->fd == -1) {
147 		errno = EBADF;
148 		return -1;
149 	}
150 
151 	if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
152 		errno = EINVAL;
153 		return -1;
154 	}
155 
156 	/* make sure we are at the right position */
157 	mapptr = mm->map + stream->position;
158 
159 	if (stream->bound_end == -1)
160 		nread = MIN ((gint64) ((mm->map + mm->maplen) - mapptr), (gint64) len);
161 	else
162 		nread = MIN (stream->bound_end - stream->position, (gint64) len);
163 
164 	if (nread > 0) {
165 		memcpy (buf, mapptr, nread);
166 		stream->position += nread;
167 	} else
168 		mm->eos = TRUE;
169 
170 	return nread;
171 }
172 
173 static ssize_t
stream_write(GMimeStream * stream,const char * buf,size_t len)174 stream_write (GMimeStream *stream, const char *buf, size_t len)
175 {
176 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
177 	register char *mapptr;
178 	ssize_t nwritten;
179 
180 	if (mm->fd == -1) {
181 		errno = EBADF;
182 		return -1;
183 	}
184 
185 	if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
186 		errno = EINVAL;
187 		return -1;
188 	}
189 
190 	/* make sure we are at the right position */
191 	mapptr = mm->map + stream->position;
192 
193 	if (stream->bound_end == -1)
194 		nwritten = MIN ((gint64) ((mm->map + mm->maplen) - mapptr), (gint64) len);
195 	else
196 		nwritten = MIN (stream->bound_end - stream->position, (gint64) len);
197 
198 	if (nwritten > 0) {
199 		memcpy (mapptr, buf, nwritten);
200 		stream->position += nwritten;
201 	}
202 
203 	return nwritten;
204 }
205 
206 static int
stream_flush(GMimeStream * stream)207 stream_flush (GMimeStream *stream)
208 {
209 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
210 
211 	if (mm->fd == -1) {
212 		errno = EBADF;
213 		return -1;
214 	}
215 
216 #ifdef HAVE_MSYNC
217 	return msync (mm->map, mm->maplen, MS_SYNC /* | MS_INVALIDATE */);
218 #else
219 	return 0;
220 #endif
221 }
222 
223 static int
stream_close(GMimeStream * stream)224 stream_close (GMimeStream *stream)
225 {
226 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
227 	int rv = 0;
228 
229 	if (mm->fd == -1)
230 		return 0;
231 
232 	if (mm->owner) {
233 #ifdef HAVE_MUNMAP
234 		munmap (mm->map, mm->maplen);
235 #endif
236 
237 		do {
238 			rv = close (mm->fd);
239 		} while (rv == -1 && errno == EINTR);
240 	}
241 
242 	mm->map = NULL;
243 	mm->fd = -1;
244 
245 	return rv;
246 }
247 
248 static gboolean
stream_eos(GMimeStream * stream)249 stream_eos (GMimeStream *stream)
250 {
251 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
252 
253 	if (mm->fd == -1)
254 		return TRUE;
255 
256 	return mm->eos;
257 }
258 
259 static int
stream_reset(GMimeStream * stream)260 stream_reset (GMimeStream *stream)
261 {
262 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
263 
264 	if (mm->fd == -1) {
265 		errno = EBADF;
266 		return -1;
267 	}
268 
269 	mm->eos = FALSE;
270 
271 	return 0;
272 }
273 
274 static gint64
stream_seek(GMimeStream * stream,gint64 offset,GMimeSeekWhence whence)275 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
276 {
277 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
278 	gint64 real = stream->position;
279 
280 	if (mm->fd == -1) {
281 		errno = EBADF;
282 		return -1;
283 	}
284 
285 	switch (whence) {
286 	case GMIME_STREAM_SEEK_SET:
287 		real = offset;
288 		break;
289 	case GMIME_STREAM_SEEK_CUR:
290 		real = stream->position + offset;
291 		break;
292 	case GMIME_STREAM_SEEK_END:
293 		if (stream->bound_end == -1) {
294 			real = offset <= 0 ? stream->bound_start + (gint64) mm->maplen + offset : -1;
295 			if (real != -1) {
296 				if (real < stream->bound_start)
297 					real = stream->bound_start;
298 				stream->position = real;
299 			}
300 
301 			return real;
302 		}
303 		real = stream->bound_end + offset;
304 		break;
305 	}
306 
307 	/* sanity check the resultant offset */
308 	if (real < stream->bound_start) {
309 		errno = EINVAL;
310 		return -1;
311 	}
312 
313 	if (stream->bound_end != -1 && real > stream->bound_end) {
314 		errno = EINVAL;
315 		return -1;
316 	}
317 
318 	/* reset eos if appropriate */
319 	if ((stream->bound_end != -1 && real < stream->bound_end) ||
320 	    (mm->eos && real < stream->position))
321 		mm->eos = FALSE;
322 
323 	stream->position = real;
324 
325 	return real;
326 }
327 
328 static gint64
stream_tell(GMimeStream * stream)329 stream_tell (GMimeStream *stream)
330 {
331 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
332 
333 	if (mm->fd == -1) {
334 		errno = EBADF;
335 		return -1;
336 	}
337 
338 	return stream->position;
339 }
340 
341 static gint64
stream_length(GMimeStream * stream)342 stream_length (GMimeStream *stream)
343 {
344 	GMimeStreamMmap *mm = (GMimeStreamMmap *) stream;
345 
346 	if (mm->fd == -1) {
347 		errno = EBADF;
348 		return -1;
349 	}
350 
351 	if (stream->bound_start != -1 && stream->bound_end != -1)
352 		return stream->bound_end - stream->bound_start;
353 
354 	return mm->maplen - stream->bound_start;
355 }
356 
357 static GMimeStream *
stream_substream(GMimeStream * stream,gint64 start,gint64 end)358 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
359 {
360 	GMimeStreamMmap *mm;
361 
362 	mm = g_object_new (GMIME_TYPE_STREAM_MMAP, NULL);
363 	g_mime_stream_construct ((GMimeStream *) mm, start, end);
364 	mm->maplen = ((GMimeStreamMmap *) stream)->maplen;
365 	mm->map = ((GMimeStreamMmap *) stream)->map;
366 	mm->fd = ((GMimeStreamMmap *) stream)->fd;
367 	mm->owner = FALSE;
368 
369 	return (GMimeStream *) mm;
370 }
371 
372 
373 /**
374  * g_mime_stream_mmap_new:
375  * @fd: file descriptor
376  * @prot: protection flags
377  * @flags: map flags
378  *
379  * Creates a new #GMimeStreamMmap object around @fd.
380  *
381  * Returns: a stream using @fd.
382  **/
383 GMimeStream *
g_mime_stream_mmap_new(int fd,int prot,int flags)384 g_mime_stream_mmap_new (int fd, int prot, int flags)
385 {
386 #ifdef HAVE_MMAP
387 	gint64 start;
388 
389 	if ((start = lseek (fd, 0, SEEK_CUR)) == -1)
390 		return NULL;
391 
392 	return g_mime_stream_mmap_new_with_bounds (fd, prot, flags, start, -1);
393 #else
394 	return NULL;
395 #endif /* HAVE_MMAP */
396 }
397 
398 
399 /**
400  * g_mime_stream_mmap_new_with_bounds:
401  * @fd: file descriptor
402  * @prot: protection flags
403  * @flags: map flags
404  * @start: start boundary
405  * @end: end boundary
406  *
407  * Creates a new #GMimeStreamMmap object around @fd with bounds @start
408  * and @end.
409  *
410  * Returns: a stream using @fd with bounds @start and @end.
411  **/
412 GMimeStream *
g_mime_stream_mmap_new_with_bounds(int fd,int prot,int flags,gint64 start,gint64 end)413 g_mime_stream_mmap_new_with_bounds (int fd, int prot, int flags, gint64 start, gint64 end)
414 {
415 #ifdef HAVE_MMAP
416 	GMimeStreamMmap *mm;
417 	struct stat st;
418 	size_t len;
419 	char *map;
420 
421 	if (end == -1) {
422 		if (fstat (fd, &st) == -1)
423 			return NULL;
424 
425 		len = st.st_size;
426 	} else
427 		len = (size_t) end;
428 
429 	if ((map = mmap (NULL, len, prot, flags, fd, 0)) == MAP_FAILED)
430 		return NULL;
431 
432 	mm = g_object_new (GMIME_TYPE_STREAM_MMAP, NULL);
433 	g_mime_stream_construct ((GMimeStream *) mm, start, end);
434 	mm->owner = TRUE;
435 	mm->eos = FALSE;
436 	mm->fd = fd;
437 	mm->map = map;
438 	mm->maplen = len;
439 
440 	return (GMimeStream *) mm;
441 #else
442 	return NULL;
443 #endif /* HAVE_MMAP */
444 }
445 
446 
447 /**
448  * g_mime_stream_mmap_get_owner:
449  * @stream: a #GMimeStreamFs
450  *
451  * Gets whether or not @stream owns the backend file descriptor.
452  *
453  * Returns: %TRUE if @stream owns the backend file descriptor or %FALSE
454  * otherwise.
455  *
456  * Since: 3.2
457  **/
458 gboolean
g_mime_stream_mmap_get_owner(GMimeStreamMmap * stream)459 g_mime_stream_mmap_get_owner (GMimeStreamMmap *stream)
460 {
461 	g_return_val_if_fail (GMIME_IS_STREAM_MMAP (stream), FALSE);
462 
463 	return stream->owner;
464 }
465 
466 
467 /**
468  * g_mime_stream_mmap_set_owner:
469  * @stream: a #GMimeStreamMmap
470  * @owner: %TRUE if this stream should own the file descriptor or %FALSE otherwise
471  *
472  * Sets whether or not @stream owns the backend file descriptor.
473  *
474  * Note: @owner should be %TRUE if the stream should close() the
475  * backend file descriptor when destroyed or %FALSE otherwise.
476  *
477  * Since: 3.2
478  **/
479 void
g_mime_stream_mmap_set_owner(GMimeStreamMmap * stream,gboolean owner)480 g_mime_stream_mmap_set_owner (GMimeStreamMmap *stream, gboolean owner)
481 {
482 	g_return_if_fail (GMIME_IS_STREAM_MMAP (stream));
483 
484 	stream->owner = owner;
485 }
486