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