add eina mmap safety handling.
authorraster <raster@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Mon, 4 Jul 2011 09:29:59 +0000 (09:29 +0000)
committerraster <raster@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Mon, 4 Jul 2011 09:29:59 +0000 (09:29 +0000)
git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/eina@60976 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33

ChangeLog
src/include/Makefile.am
src/include/eina_mmap.h [new file with mode: 0644]
src/lib/Makefile.am
src/lib/eina_file.c
src/lib/eina_mmap.c [new file with mode: 0644]

index d36ad6e..a049e6b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
 2011-06-23  Cedric Bail
 
        * Add Eina_LockRW.
+
+2011-07-04  Carsten Haitzler (The Rasterman)
+
+       * Add eina_mmap safety handling.
index 308d139..8fc4763 100644 (file)
@@ -57,7 +57,8 @@ eina_quadtree.h \
 eina_simple_xml_parser.h \
 eina_lock.h \
 eina_prefix.h \
-eina_refcount.h
+eina_refcount.h \
+eina_mmap.h
 
 # Will be back for developper after 1.1.
 # eina_object.h
diff --git a/src/include/eina_mmap.h b/src/include/eina_mmap.h
new file mode 100644 (file)
index 0000000..618b805
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef EINA_MMAP_H_
+#define EINA_MMAP_H_
+
+/**
+ * @addtogroup Eina_Mmap_Group Group
+ *
+ * @brief These functions provide helpers for safe mmap handling
+ *
+ * @{
+ *
+ * @since 1.1.0
+ */
+
+/**
+ * @brief Enable or disable safe mmaped IO handling
+ *
+ * @param enabled The enabled state (to enable, pass EINA_TRUE)
+ *
+ * This enables (if possible on your platform) a signal handler for
+ * SIGBUS, that replaces the "bad page" with a pzge of 0's (from /dev/zero)
+ * if a SIGBUS occurs. This allows for safe mmap() of files that may truncate
+ * or from files on devices with IO errors. Normally these cases will result
+ * in a SIGBUS being delivered (and termination of yyour process), but
+ * when "mmap safety" is enabled, this will not occur. Instead a page of
+ * bytes of the value 0 will replace the "bad page", allowing the process
+ * to continue and allow its own parsing error detection to safely abort
+ * the operation without the process falling apart.
+ * 
+ * If you disable mmap safety, the SIGBUS handler will be restored to its
+ * default handler. Note that eina_file_map_all() and eina_file_map_new()
+ * will automatically enable mmap safety as they provide an mmaped file IO
+ * layer, and rely on mmap to not fail for any part of the file.
+ * 
+ * If you set up your own SIGBUS handler, then this will effectively disable
+ * the safe mmap handling and make you liable to crashes on IO to or from
+ * such "damaged files" that would take down your process.
+ *
+ * @since 1.1.0
+ */
+EAPI Eina_Bool
+eina_mmap_safety_enabled_set(Eina_Bool enabled);
+
+/**
+ * @brief Get the enabled state of mmap safety.
+ *
+ * @return The safety state (EINA_TRUE if enabled)
+ *
+ * This returns the mmap safety state set by eina_mmap_safety_enabled_set().
+ * See eina_mmap_safety_enabled_set() for more information.
+ *
+ * @since 1.1.0
+ */
+EAPI Eina_Bool
+eina_mmap_safety_enabled_get(void);
+
+/**
+ * @}
+ */
+#endif
index 2e89bc7..0d1717c 100644 (file)
@@ -31,6 +31,7 @@ eina_magic.c \
 eina_main.c \
 eina_matrixsparse.c \
 eina_mempool.c \
+eina_mmap.c \
 eina_module.c \
 eina_prefix.c \
 eina_quadtree.c \
index 373065a..e3796e9 100644 (file)
@@ -65,6 +65,7 @@ void *alloca (size_t);
 #include "eina_hash.h"
 #include "eina_list.h"
 #include "eina_lock.h"
+#include "eina_mmap.h"
 
 /*============================================================================*
  *                                  Local                                     *
@@ -867,6 +868,7 @@ eina_file_map_all(Eina_File *file, Eina_File_Populate rule)
    if (file->length > EINA_HUGE_PAGE) flags |= MAP_HUGETLB;
 #endif
 
+   eina_mmap_safety_enabled_set(EINA_TRUE);
    eina_lock_take(&file->lock);
    if (file->global_map == MAP_FAILED)
      file->global_map = mmap(NULL, file->length, PROT_READ, flags, file->fd, 0);
@@ -900,6 +902,7 @@ eina_file_map_new(Eina_File *file, Eina_File_Populate rule,
    key[0] = offset;
    key[1] = length;
 
+   eina_mmap_safety_enabled_set(EINA_TRUE);
    eina_lock_take(&file->lock);
 
    map = eina_hash_find(file->map, &key);
diff --git a/src/lib/eina_mmap.c b/src/lib/eina_mmap.c
new file mode 100644 (file)
index 0000000..fe2a120
--- /dev/null
@@ -0,0 +1,198 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2011 Carsten Haitzler
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library;
+ * if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#elif defined __GNUC__
+# define alloca __builtin_alloca
+#elif defined _AIX
+# define alloca __alloca
+#elif defined _MSC_VER
+# include <malloc.h>
+# define alloca _alloca
+#else
+# ifndef HAVE_ALLOCA
+#  ifdef  __cplusplus
+extern "C"
+#  endif
+void *alloca (size_t);
+# endif
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <limits.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#include "eina_config.h"
+#include "eina_private.h"
+#include "eina_log.h"
+#include "eina_mmap.h"
+
+/*============================================================================*
+ *                                 Local                                      *
+ *============================================================================*/
+static Eina_Bool mmap_safe = EINA_FALSE;
+#ifndef _WIN32
+static int _eina_mmap_log_dom = -1;
+static int _eina_mmap_zero_fd = -1;
+static long _eina_mmap_pagesize = -1;
+
+#ifdef ERR
+#undef ERR
+#endif
+#define ERR(...) EINA_LOG_DOM_ERR(_eina_mmap_log_dom, __VA_ARGS__)
+
+#ifdef DBG
+#undef DBG
+#endif
+#define DBG(...) EINA_LOG_DOM_DBG(_eina_mmap_log_dom, __VA_ARGS__)
+
+static void
+_eina_mmap_safe_sigbus(int sig __UNUSED__,
+                       siginfo_t *siginfo,
+                       void *ptr __UNUSED__)
+{
+   unsigned char *addr = (unsigned char *)(siginfo->si_addr);
+   int perrno;
+
+   /* save previous errno */
+   perrno = errno;
+   /* if problems was an unaligned access - complain accordingly and abort */
+   if (siginfo->si_code == BUS_ADRALN)
+     {
+        ERR("Unaligned memory access. SIGBUS!!!");
+        errno = perrno; 
+        abort();
+     }
+   /* send this to stderr - not eina_log. Specifically want this on stderr */
+   fprintf(stderr,
+           "EINA: Data at address 0x%lx is invalid. Replacing with zero page.\n",
+           (unsigned long)addr);
+   /* align address to the lower page boundary */
+   addr = (unsigned char *)((long)addr & (~(_eina_mmap_pagesize - 1)));
+   /* mmap a pzge of zero's from /dev/zero in there */
+   if (mmap(addr, _eina_mmap_pagesize,
+            PROT_READ | PROT_WRITE | PROT_EXEC,
+            MAP_PRIVATE | MAP_FIXED, 
+            _eina_mmap_zero_fd, 0) == MAP_FAILED)
+     {
+        /* mmap of /dev/zero failed :( */
+        perror("mmap");
+        ERR("Failed to mmap() /dev/zero in place of page. SIGBUS!!!");
+        errno = perrno; 
+        abort();
+     }
+   /* restore previous errno */
+   errno = perrno; 
+}
+#endif
+
+/*============================================================================*
+ *                                   API                                      *
+ *============================================================================*/
+
+EAPI Eina_Bool
+eina_mmap_safety_enabled_set(Eina_Bool enabled)
+{
+#ifdef _WIN32
+   return EINA_FALSE;
+#else
+   if (_eina_mmap_log_dom < 0)
+     {
+        _eina_mmap_log_dom = eina_log_domain_register("eina_mmap",
+                                                         EINA_LOG_COLOR_DEFAULT);
+        if (_eina_mmap_log_dom < 0)
+          {
+             EINA_LOG_ERR("Could not register log domain: eina_mmap");
+             return EINA_FALSE;
+          }
+     }
+   
+   enabled = !!enabled;
+   
+   if (mmap_safe == enabled) return mmap_safe;
+   if (enabled)
+     {
+        struct sigaction  sa;
+
+        /* find out system page size the cleanest way we can */
+#ifdef _SC_PAGESIZE   
+        _eina_mmap_pagesize = sysconf(_SC_PAGESIZE);
+        if (_eina_mmap_pagesize <= 0) return EINA_FALSE;
+#else
+        _eina_mmap_pagesize = 4096;
+#endif
+        /* no zero page device - open it */
+        if (_eina_mmap_zero_fd < 0)
+          {
+             _eina_mmap_zero_fd = open("/dev/zero", O_RDWR);
+             /* if we don;'t have one - fail to set up mmap safety */
+             if (_eina_mmap_zero_fd < 0) return EINA_FALSE;
+          }
+        /* set up signal handler for SIGBUS */
+        sa.sa_sigaction = _eina_mmap_safe_sigbus;
+        sa.sa_flags = SA_RESTART | SA_SIGINFO;
+        sigemptyset(&sa.sa_mask);
+        if (sigaction(SIGBUS, &sa, NULL) == 0) return EINA_FALSE;
+        /* setup of SIGBUS handler failed, lets close zero page dev and fail */
+        close(_eina_mmap_zero_fd);
+        _eina_mmap_zero_fd = -1;
+        return EINA_FALSE;
+     }
+   else
+     {
+        /* reset signal handler to default for SIGBUS */
+        signal(SIGBUS, SIG_DFL);
+     }
+   mmap_safe = enabled;
+   return mmap_safe;
+#endif   
+}
+
+EAPI Eina_Bool
+eina_mmap_safety_enabled_get(void)
+{
+   return mmap_safe;
+}