Deduplicate and fix up our use of mmap()

Buried in this large patch is a logical fix:

```
-  if (!map)
-    return glnx_throw_errno_prefix (error, "mmap");
+  if (map == (void*)-1)
+    return glnx_null_throw_errno_prefix (error, "mmap");
```

Which would have helped me debug another patch I was working
on.  But it turns out that actually correctly checking for
errors from `mmap()` triggers lots of other bugs - basically
because we sometimes handle zero-length variants (in detached
metadata).  When we start actually returning errors due to
this, things break.  (It wasn't a problem in practice before
because most things looked at the zero size, not the data).

Anyways there's a bigger picture issue here - a while ago
we made a fix to only use `mmap()` for reading metadata from disk
only if it was large enough (i.e. `>16k`).  But that didn't
help various other paths in the pull code and others that were
directly doing the `mmap()`.

Fix this by having a proper low level fs helper that does "read all data from
fd+offset into GBytes", which handles the size check. Then the `GVariant` bits
are just a clean layer on top of this. (At the small cost of an additional
allocation)

Side note: I had to remind myself, but the reason we can't just use
`GMappedFile` here is it doesn't support passing an offset into `mmap()`.

Closes: #1251
Approved by: jlebon
This commit is contained in:
Colin Walters authored and Atomic Bot committed 2017-10-04 20:42:39 +00:00
1 parent c511ca0fae
commit 5c7d2dd8be
10 files changed
+131 -172

No files matched your search

+48 -12
View File
@@ -22,6 +22,7 @@
#include "ot-fs-utils.h"
#include "libglnx.h"
#include <sys/xattr.h>
#include <sys/mman.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
@@ -144,22 +145,57 @@ ot_dfd_iter_init_allow_noent (int dfd,
return TRUE;
}
GBytes *
ot_file_mapat_bytes (int dfd,
const char *path,
GError **error)
typedef struct {
gpointer addr;
gsize len;
} MapData;
static void
map_data_destroy (gpointer data)
{
glnx_fd_close int fd = openat (dfd, path, O_RDONLY | O_CLOEXEC);
g_autoptr(GMappedFile) mfile = NULL;
MapData *mdata = data;
(void) munmap (mdata->addr, mdata->len);
g_free (mdata);
}
if (fd < 0)
return glnx_null_throw_errno_prefix (error, "openat(%s)", path);
mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
if (!mfile)
/* Return a newly-allocated GBytes that refers to the contents of the file
* starting at offset @start. If the file is large enough, mmap() may be used.
*/
GBytes *
ot_fd_readall_or_mmap (int fd,
goffset start,
GError **error)
{
struct stat stbuf;
if (!glnx_fstat (fd, &stbuf, error))
return FALSE;
return g_mapped_file_get_bytes (mfile);
/* http://stackoverflow.com/questions/258091/when-should-i-use-mmap-for-file-access */
if (start > stbuf.st_size)
return g_bytes_new_static (NULL, 0);
const gsize len = stbuf.st_size - start;
if (len > 16*1024)
{
/* The reason we don't use g_mapped_file_new_from_fd() here
* is it doesn't support passing an offset, which is actually
* used by the static delta code.
*/
gpointer map = mmap (NULL, len, PROT_READ, MAP_PRIVATE, fd, start);
if (map == (void*)-1)
return glnx_null_throw_errno_prefix (error, "mmap");
MapData *mdata = g_new (MapData, 1);
mdata->addr = map;
mdata->len = len;
return g_bytes_new_with_free_func (map, len, map_data_destroy, mdata);
}
/* Fall through to plain read into a malloc buffer */
if (lseek (fd, start, SEEK_SET) < 0)
return glnx_null_throw_errno_prefix (error, "lseek");
/* Not cancellable since this should be small */
return glnx_fd_readall_bytes (fd, NULL, error);
}
/* Given an input stream, splice it to an anonymous file (O_TMPFILE).
+2 -3
View File
@@ -85,8 +85,7 @@ ot_map_anonymous_tmpfile_from_content (GInputStream *instream,
GCancellable *cancellable,
GError **error);
GBytes *ot_file_mapat_bytes (int dfd,
const char *path,
GError **error);
GBytes *ot_fd_readall_or_mmap (int fd, goffset offset,
GError **error);
G_END_DECLS
+16 -78
View File
@@ -25,7 +25,6 @@
#include <gio/gfiledescriptorbased.h>
#include <string.h>
#include <sys/mman.h>
#include "otutil.h"
@@ -85,86 +84,25 @@ ot_util_variant_take_ref (GVariant *variant)
return g_variant_take_ref (variant);
}
/* Create a GVariant in @out_variant that is backed by
* the data from @fd, starting at @start. If the data is
* large enough, mmap() may be used. @trusted is used
* by the GVariant core; see g_variant_new_from_data().
*/
gboolean
ot_util_variant_map_at (int dfd,
const char *path,
const GVariantType *type,
OtVariantMapFlags flags,
GVariant **out_variant,
GError **error)
ot_variant_read_fd (int fd,
goffset start,
const GVariantType *type,
gboolean trusted,
GVariant **out_variant,
GError **error)
{
glnx_fd_close int fd = -1;
const gboolean trusted = (flags & OT_VARIANT_MAP_TRUSTED) > 0;
g_autoptr(GBytes) bytes = ot_fd_readall_or_mmap (fd, start, error);
if (!bytes)
return FALSE;
fd = openat (dfd, path, O_RDONLY | O_CLOEXEC);
if (fd < 0)
{
if (errno == ENOENT && (flags & OT_VARIANT_MAP_ALLOW_NOENT) > 0)
{
*out_variant = NULL;
return TRUE;
}
else
{
glnx_set_error_from_errno (error);
g_prefix_error (error, "Opening %s: ", path);
return FALSE;
}
}
return ot_util_variant_map_fd (fd, 0, type, trusted, out_variant, error);
}
typedef struct {
gpointer addr;
gsize len;
} VariantMapData;
static void
variant_map_data_destroy (gpointer data)
{
VariantMapData *mdata = data;
(void) munmap (mdata->addr, mdata->len);
g_free (mdata);
}
gboolean
ot_util_variant_map_fd (int fd,
goffset start,
const GVariantType *type,
gboolean trusted,
GVariant **out_variant,
GError **error)
{
gboolean ret = FALSE;
gpointer map;
struct stat stbuf;
VariantMapData *mdata = NULL;
gsize len;
if (fstat (fd, &stbuf) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
len = stbuf.st_size - start;
map = mmap (NULL, len, PROT_READ, MAP_PRIVATE, fd, start);
if (!map)
{
glnx_set_error_from_errno (error);
goto out;
}
mdata = g_new (VariantMapData, 1);
mdata->addr = map;
mdata->len = len;
ret = TRUE;
*out_variant = g_variant_ref_sink (g_variant_new_from_data (type, map, len, trusted,
variant_map_data_destroy, mdata));
out:
return ret;
*out_variant = g_variant_ref_sink (g_variant_new_from_bytes (type, bytes, trusted));
return TRUE;
}
GInputStream *
+6 -18
View File
@@ -36,24 +36,12 @@ GHashTable *ot_util_variant_asv_to_hash_table (GVariant *variant);
GVariant * ot_util_variant_take_ref (GVariant *variant);
typedef enum {
OT_VARIANT_MAP_TRUSTED = (1 << 0),
OT_VARIANT_MAP_ALLOW_NOENT = (1 << 1)
} OtVariantMapFlags;
gboolean ot_util_variant_map_at (int dfd,
const char *path,
const GVariantType *type,
OtVariantMapFlags flags,
GVariant **out_variant,
GError **error);
gboolean ot_util_variant_map_fd (int fd,
goffset offset,
const GVariantType *type,
gboolean trusted,
GVariant **out_variant,
GError **error);
gboolean ot_variant_read_fd (int fd,
goffset offset,
const GVariantType *type,
gboolean trusted,
GVariant **out_variant,
GError **error);
GInputStream *ot_variant_read (GVariant *variant);