Coverage Report

Created: 2026-10-13 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/glib/glib/gmappedfile.c
Line
Count
Source
1
/* GLIB - Library of useful routines for C programming
2
 * gmappedfile.c: Simplified wrapper around the mmap() function.
3
 *
4
 * Copyright 2005 Matthias Clasen
5
 *
6
 * SPDX-License-Identifier: LGPL-2.1-or-later
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include "config.h"
23
24
#include <errno.h>
25
#include <sys/types.h> 
26
#include <sys/stat.h> 
27
#include <fcntl.h>
28
#ifdef HAVE_MMAP
29
#include <sys/mman.h>
30
#endif
31
32
#include "glibconfig.h"
33
34
#ifdef G_OS_UNIX
35
#include <unistd.h>
36
#endif
37
38
#ifdef G_OS_WIN32
39
#include <windows.h>
40
#include <io.h>
41
42
#undef fstat
43
#define fstat(a,b) _fstati64(a,b)
44
#undef stat
45
#define stat _stati64
46
47
#ifndef S_ISREG
48
#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
49
#endif
50
51
#endif
52
53
#ifndef O_CLOEXEC
54
#define O_CLOEXEC 0
55
#endif
56
57
#include "gconvert.h"
58
#include "gerror.h"
59
#include "gfileutils.h"
60
#include "gmappedfile.h"
61
#include "gmem.h"
62
#include "gmessages.h"
63
#include "gstdio.h"
64
#include "gstrfuncs.h"
65
#include "gatomic.h"
66
67
#include "glibintl.h"
68
69
70
#ifndef _O_BINARY
71
0
#define _O_BINARY 0
72
#endif
73
74
#ifndef MAP_FAILED
75
#define MAP_FAILED ((void *) -1)
76
#endif
77
78
/**
79
 * GMappedFile:
80
 *
81
 * The #GMappedFile represents a file mapping created with
82
 * g_mapped_file_new(). It has only private members and should
83
 * not be accessed directly.
84
 */
85
86
struct _GMappedFile
87
{
88
  gchar *contents;
89
  gsize  length;
90
  gpointer free_func;
91
  int    ref_count;
92
#ifdef G_OS_WIN32
93
  HANDLE mapping;
94
#endif
95
};
96
97
static void
98
g_mapped_file_destroy (GMappedFile *file)
99
0
{
100
0
  if (file->length)
101
0
    {
102
0
#ifdef HAVE_MMAP
103
0
      munmap (file->contents, file->length);
104
0
#endif
105
#ifdef G_OS_WIN32
106
      UnmapViewOfFile (file->contents);
107
      CloseHandle (file->mapping);
108
#endif
109
0
    }
110
111
0
  g_slice_free (GMappedFile, file);
112
0
}
113
114
static GMappedFile*
115
mapped_file_new_from_fd (int           fd,
116
       gboolean      writable,
117
                         const gchar  *filename,
118
                         GError      **error)
119
0
{
120
0
  GMappedFile *file;
121
0
  struct stat st;
122
123
0
  file = g_slice_new0 (GMappedFile);
124
0
  file->ref_count = 1;
125
0
  file->free_func = g_mapped_file_destroy;
126
127
0
  if (fstat (fd, &st) == -1)
128
0
    {
129
0
      if (error != NULL)
130
0
        {
131
0
          int save_errno = errno;
132
0
          gchar *display_filename = filename ? g_filename_display_name (filename) : NULL;
133
134
0
          g_set_error (error,
135
0
                       G_FILE_ERROR,
136
0
                       g_file_error_from_errno (save_errno),
137
0
                       _("Failed to get attributes of file ā€œ%s%s%s%sā€: fstat() failed: %s"),
138
0
                       display_filename ? display_filename : "fd",
139
0
                       display_filename ? "' " : "",
140
0
                       display_filename ? display_filename : "",
141
0
                       display_filename ? "'" : "",
142
0
                       g_strerror (save_errno));
143
0
          g_free (display_filename);
144
0
        }
145
0
      goto out;
146
0
    }
147
148
  /* mmap() on size 0 will fail with EINVAL, so we avoid calling mmap()
149
   * in that case -- but only if we have a regular file; we still want
150
   * attempts to mmap a character device to fail, for example.
151
   */
152
0
  if (st.st_size == 0 && S_ISREG (st.st_mode))
153
0
    {
154
0
      file->length = 0;
155
0
      file->contents = NULL;
156
0
      return file;
157
0
    }
158
0
  else if (st.st_size == 0)
159
0
    {
160
0
      errno = EINVAL;
161
0
      file->length = 0;
162
0
      file->contents = MAP_FAILED;
163
0
      goto error;
164
0
    }
165
166
0
  file->contents = MAP_FAILED;
167
168
0
#ifdef HAVE_MMAP
169
0
  if (sizeof (st.st_size) > sizeof (gsize) && st.st_size > (off_t) G_MAXSIZE)
170
0
    {
171
0
      errno = EINVAL;
172
0
    }
173
0
  else
174
0
    {      
175
0
      file->length = (gsize) st.st_size;
176
0
      file->contents = (gchar *) mmap (NULL,  file->length,
177
0
               writable ? PROT_READ|PROT_WRITE : PROT_READ,
178
0
               MAP_PRIVATE, fd, 0);
179
0
    }
180
0
#endif
181
#ifdef G_OS_WIN32
182
  file->length = st.st_size;
183
  file->mapping = CreateFileMapping ((HANDLE) _get_osfhandle (fd), NULL,
184
             writable ? PAGE_WRITECOPY : PAGE_READONLY,
185
             0, 0,
186
             NULL);
187
  if (file->mapping != NULL)
188
    {
189
      file->contents = MapViewOfFile (file->mapping,
190
              writable ? FILE_MAP_COPY : FILE_MAP_READ,
191
              0, 0,
192
              0);
193
      if (file->contents == NULL)
194
  {
195
    file->contents = MAP_FAILED;
196
    CloseHandle (file->mapping);
197
    file->mapping = NULL;
198
  }
199
    }
200
#endif
201
202
0
error:
203
0
  if (file->contents == MAP_FAILED)
204
0
    {
205
0
      if (error != NULL)
206
0
        {
207
0
          int save_errno = errno;
208
0
          gchar *display_filename = filename ? g_filename_display_name (filename) : NULL;
209
210
0
          g_set_error (error,
211
0
                       G_FILE_ERROR,
212
0
                       g_file_error_from_errno (save_errno),
213
0
                       _("Failed to map %s%s%s%s: mmap() failed: %s"),
214
0
                       display_filename ? display_filename : "fd",
215
0
                       display_filename ? "' " : "",
216
0
                       display_filename ? display_filename : "",
217
0
                       display_filename ? "'" : "",
218
0
                       g_strerror (save_errno));
219
0
          g_free (display_filename);
220
0
        }
221
0
      goto out;
222
0
    }
223
224
0
  return file;
225
226
0
 out:
227
0
  g_slice_free (GMappedFile, file);
228
229
0
  return NULL;
230
0
}
231
232
/**
233
 * g_mapped_file_new:
234
 * @filename: (type filename): The path of the file to load, in the GLib
235
 *     filename encoding
236
 * @writable: whether the mapping should be writable
237
 * @error: return location for a #GError, or %NULL
238
 *
239
 * Maps a file into memory. On UNIX, this is using the mmap() function.
240
 *
241
 * If @writable is %TRUE, the mapped buffer may be modified, otherwise
242
 * it is an error to modify the mapped buffer. Modifications to the buffer
243
 * are not visible to other processes mapping the same file, and are not
244
 * written back to the file.
245
 *
246
 * Note that modifications of the underlying file might affect the contents
247
 * of the #GMappedFile. Therefore, mapping should only be used if the file
248
 * will not be modified, or if all modifications of the file are done
249
 * atomically (e.g. using g_file_set_contents()).
250
 *
251
 * If @filename is the name of an empty, regular file, the function
252
 * will successfully return an empty #GMappedFile. In other cases of
253
 * size 0 (e.g. device files such as /dev/null), @error will be set
254
 * to the #GFileError value %G_FILE_ERROR_INVAL.
255
 *
256
 * Returns: a newly allocated #GMappedFile which must be unref'd
257
 *    with g_mapped_file_unref(), or %NULL if the mapping failed.
258
 *
259
 * Since: 2.8
260
 */
261
GMappedFile *
262
g_mapped_file_new (const gchar  *filename,
263
       gboolean      writable,
264
       GError      **error)
265
0
{
266
0
  GMappedFile *file;
267
0
  int fd;
268
269
0
  g_return_val_if_fail (filename != NULL, NULL);
270
0
  g_return_val_if_fail (!error || *error == NULL, NULL);
271
272
0
  fd = g_open (filename, (writable ? O_RDWR : O_RDONLY) | _O_BINARY | O_CLOEXEC, 0);
273
0
  if (fd == -1)
274
0
    {
275
0
      if (error != NULL)
276
0
        {
277
0
          int save_errno = errno;
278
0
          gchar *display_filename = g_filename_display_name (filename);
279
280
0
          g_set_error (error,
281
0
                       G_FILE_ERROR,
282
0
                       g_file_error_from_errno (save_errno),
283
0
                       _("Failed to open file ā€œ%sā€: open() failed: %s"),
284
0
                       display_filename,
285
0
                       g_strerror (save_errno));
286
0
          g_free (display_filename);
287
0
        }
288
289
0
      return NULL;
290
0
    }
291
292
0
  file = mapped_file_new_from_fd (fd, writable, filename, error);
293
294
0
  close (fd);
295
296
0
  return file;
297
0
}
298
299
300
/**
301
 * g_mapped_file_new_from_fd:
302
 * @fd: The file descriptor of the file to load
303
 * @writable: whether the mapping should be writable
304
 * @error: return location for a #GError, or %NULL
305
 *
306
 * Maps a file into memory. On UNIX, this is using the mmap() function.
307
 *
308
 * If @writable is %TRUE, the mapped buffer may be modified, otherwise
309
 * it is an error to modify the mapped buffer. Modifications to the buffer
310
 * are not visible to other processes mapping the same file, and are not
311
 * written back to the file.
312
 *
313
 * Note that modifications of the underlying file might affect the contents
314
 * of the #GMappedFile. Therefore, mapping should only be used if the file
315
 * will not be modified, or if all modifications of the file are done
316
 * atomically (e.g. using g_file_set_contents()).
317
 *
318
 * Returns: a newly allocated #GMappedFile which must be unref'd
319
 *    with g_mapped_file_unref(), or %NULL if the mapping failed.
320
 *
321
 * Since: 2.32
322
 */
323
GMappedFile *
324
g_mapped_file_new_from_fd (gint          fd,
325
         gboolean      writable,
326
         GError      **error)
327
0
{
328
0
  return mapped_file_new_from_fd (fd, writable, NULL, error);
329
0
}
330
331
/**
332
 * g_mapped_file_get_length:
333
 * @file: a #GMappedFile
334
 *
335
 * Returns the length of the contents of a #GMappedFile.
336
 *
337
 * Returns: the length of the contents of @file.
338
 *
339
 * Since: 2.8
340
 */
341
gsize
342
g_mapped_file_get_length (GMappedFile *file)
343
0
{
344
0
  g_return_val_if_fail (file != NULL, 0);
345
346
0
  return file->length;
347
0
}
348
349
/**
350
 * g_mapped_file_get_contents:
351
 * @file: a #GMappedFile
352
 *
353
 * Returns the contents of a #GMappedFile. 
354
 *
355
 * Note that the contents may not be zero-terminated,
356
 * even if the #GMappedFile is backed by a text file.
357
 *
358
 * If the file is empty then %NULL is returned.
359
 *
360
 * Returns: (transfer none) (nullable): the contents of @file, or %NULL.
361
 *
362
 * Since: 2.8
363
 */
364
gchar *
365
g_mapped_file_get_contents (GMappedFile *file)
366
0
{
367
0
  g_return_val_if_fail (file != NULL, NULL);
368
369
0
  return file->contents;
370
0
}
371
372
/**
373
 * g_mapped_file_free:
374
 * @file: a #GMappedFile
375
 *
376
 * This call existed before #GMappedFile had refcounting and is currently
377
 * exactly the same as g_mapped_file_unref().
378
 *
379
 * Since: 2.8
380
 * Deprecated:2.22: Use g_mapped_file_unref() instead.
381
 */
382
void
383
g_mapped_file_free (GMappedFile *file)
384
0
{
385
0
  g_mapped_file_unref (file);
386
0
}
387
388
/**
389
 * g_mapped_file_ref:
390
 * @file: a #GMappedFile
391
 *
392
 * Increments the reference count of @file by one.  It is safe to call
393
 * this function from any thread.
394
 *
395
 * Returns: the passed in #GMappedFile.
396
 *
397
 * Since: 2.22
398
 **/
399
GMappedFile *
400
g_mapped_file_ref (GMappedFile *file)
401
0
{
402
0
  g_return_val_if_fail (file != NULL, NULL);
403
404
0
  g_atomic_int_inc (&file->ref_count);
405
406
0
  return file;
407
0
}
408
409
/**
410
 * g_mapped_file_unref:
411
 * @file: a #GMappedFile
412
 *
413
 * Decrements the reference count of @file by one.  If the reference count
414
 * drops to 0, unmaps the buffer of @file and frees it.
415
 *
416
 * It is safe to call this function from any thread.
417
 *
418
 * Since 2.22
419
 **/
420
void
421
g_mapped_file_unref (GMappedFile *file)
422
0
{
423
0
  g_return_if_fail (file != NULL);
424
425
0
  if (g_atomic_int_dec_and_test (&file->ref_count))
426
0
    g_mapped_file_destroy (file);
427
0
}
428
429
/**
430
 * g_mapped_file_get_bytes:
431
 * @file: a #GMappedFile
432
 *
433
 * Creates a new #GBytes which references the data mapped from @file.
434
 * The mapped contents of the file must not be modified after creating this
435
 * bytes object, because a #GBytes should be immutable.
436
 *
437
 * Returns: (transfer full): A newly allocated #GBytes referencing data
438
 *     from @file
439
 *
440
 * Since: 2.34
441
 **/
442
GBytes *
443
g_mapped_file_get_bytes (GMappedFile *file)
444
0
{
445
0
  g_return_val_if_fail (file != NULL, NULL);
446
447
0
  return g_bytes_new_with_free_func (file->contents,
448
0
             file->length,
449
0
             (GDestroyNotify) g_mapped_file_unref,
450
0
             g_mapped_file_ref (file));
451
0
}