summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2012-07-08 19:02:35 -0400
committerRyan Lortie <desrt@desrt.ca>2012-07-08 19:02:35 -0400
commitf0637da31d404b0710b6e6911b9ee6d7a3593ca2 (patch)
tree4b9c9d3cea24a0f858919a45bc8e7f26ff851029
parentdd9c1a0cad35082724bba59235af031246c06ce5 (diff)
downloaddconf-f0637da31d404b0710b6e6911b9ee6d7a3593ca2.tar.gz
shm/: make some adjustments after research
After doing some research it has been discovered that ftruncate() isn't sufficient for allocating space for a file. It can create a sparse file and you don't find out that the space doesn't exist until you get a SIGBUS later upon trying to write to the mmap region. posix_fallocate() isn't doing quite what we want either (due to a flaky glibc emulation of it when the native syscall is unavailable -- which is the case for tmpfs). Use a workaround based on pwrite() instead. Also: mmap() really can't fail here except in the cases where we would also abort due to g_malloc() failing, so we should just abort too.
-rw-r--r--shm/dconf-shm.c59
1 files changed, 36 insertions, 23 deletions
diff --git a/shm/dconf-shm.c b/shm/dconf-shm.c
index 62439a0..10bfec9 100644
--- a/shm/dconf-shm.c
+++ b/shm/dconf-shm.c
@@ -71,22 +71,27 @@ dconf_shm_open (const gchar *name)
goto out;
}
- if (ftruncate (fd, 1) != 0)
+ /* fruncate(fd, 1) is not sufficient because it does not actually
+ * ensure that the space is available (which could give a SIGBUS
+ * later).
+ *
+ * posix_fallocate() is also problematic because it is implemented in
+ * a racy way in the libc if unavailable for a particular filesystem
+ * (as is the case for tmpfs, which is where we probably are).
+ *
+ * By writing to the second byte in the file we ensure we don't
+ * overwrite the first byte (which is the one we care about).
+ */
+ if (pwrite (fd, "", 1, 1) != 1)
{
g_critical ("failed to allocate file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
goto out;
}
memory = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+ g_assert (memory != MAP_FAILED);
g_assert (memory != NULL);
- if (memory == MAP_FAILED)
- {
- g_critical ("failed to mmap file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
- memory = NULL;
- goto out;
- }
-
out:
g_free (filename);
close (fd);
@@ -104,35 +109,43 @@ dconf_shm_flag (const gchar *name)
shmdir = dconf_shm_get_shmdir ();
filename = g_build_filename (shmdir, name, NULL);
- fd = open (filename, O_WRONLY);
+ /* We need O_RDWR for PROT_WRITE.
+ *
+ * This is probably due to the fact that some architectures can't make
+ * write-only mappings (so they end up being readable as well).
+ */
+ fd = open (filename, O_RDWR);
if (fd >= 0)
{
- guint8 *shm;
-
- /* Easiest thing to do here would be write(fd, "\1", 1); but this
- * causes problems on kernels (ie: OpenBSD) that don't sync up
- * their filesystem cache with mmap()ed regions.
+ /* In theory we could have opened the file after a client created
+ * it but before they called pwrite(). Do the pwrite() ourselves
+ * to make sure (so we don't get SIGBUS in a moment).
*
- * Using mmap() works everywhere.
+ * If this fails then it will probably fail for the client too.
+ * If it doesn't then there's not really much we can do...
*/
- shm = mmap (NULL, 1, PROT_WRITE, MAP_SHARED, fd, 0);
-
- if (shm != MAP_FAILED)
+ if (pwrite (fd, "", 1, 1) == 1)
{
+ guint8 *shm;
+
+ /* It would ahve been easier for us to do write(fd, "\1", 1);
+ * but this causes problems on kernels (ie: OpenBSD) that
+ * don't sync up their filesystem cache with mmap()ed regions.
+ *
+ * Using mmap() works everywhere.
+ */
+ shm = mmap (NULL, 1, PROT_WRITE, MAP_SHARED, fd, 0);
+ g_assert (shm != MAP_FAILED);
+
*shm = 1;
munmap (shm, 1);
}
- else
- g_warning ("failed to invalidate mmap file '%s': %s.", filename, g_strerror (errno));
close (fd);
unlink (filename);
}
- else if (errno != ENOENT)
- unlink (filename);
-
g_free (filename);
}