>From 4a988c1f497514c2be4a627945eac78b9ac97742 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Thu, 29 Dec 2011 02:17:15 -0500
Subject: [PATCH 09/15] nsssrv: shared memory cache server initialization

---
 Makefile.am                           |    5 +
 configure.ac                          |    1 +
 contrib/sssd.spec.in                  |    3 +
 src/conf_macros.m4                    |   19 ++-
 src/responder/nss/nsssrv.c            |    9 +
 src/responder/nss/nsssrv.h            |    3 +
 src/responder/nss/nsssrv_mmap_cache.c |  300 +++++++++++++++++++++++++++++++++
 src/responder/nss/nsssrv_mmap_cache.h |   37 ++++
 8 files changed, 376 insertions(+), 1 deletions(-)
 create mode 100644 src/responder/nss/nsssrv_mmap_cache.c
 create mode 100644 src/responder/nss/nsssrv_mmap_cache.h

diff --git a/Makefile.am b/Makefile.am
index caebc820262ceaddc71f6db1fdc6676b416c1928..a9fc3e3cfbb5bdba5cb3bc71e28a4fc48fa6982f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -33,6 +33,7 @@ dbpath = @dbpath@
 pluginpath = @pluginpath@
 pidpath = @pidpath@
 pipepath = @pipepath@
+mcpath = @mcpath@
 initdir = @initdir@
 systemdunitdir = @systemdunitdir@
 logpath = @logpath@
@@ -209,6 +210,7 @@ AM_CPPFLAGS = \
     -DSSSD_LIBEXEC_PATH=\"$(sssdlibexecdir)\" \
     -DSSSD_INTROSPECT_PATH=\"$(dbusinstropectdir)\" \
     -DSSSD_CONF_DIR=\"$(sssdconfdir)\" \
+    -DSSS_NSS_MCACHE_DIR=\"$(mcpath)\" \
     -DSSS_NSS_SOCKET_NAME=\"$(pipepath)/nss\" \
     -DSSS_PAM_SOCKET_NAME=\"$(pipepath)/pam\" \
     -DSSS_PAM_PRIV_SOCKET_NAME=\"$(pipepath)/private/pam\" \
@@ -315,6 +317,7 @@ dist_noinst_HEADERS = \
     src/responder/nss/nsssrv.h \
     src/responder/nss/nsssrv_private.h \
     src/responder/nss/nsssrv_netgroup.h \
+    src/responder/nss/nsssrv_mmap_cache.h \
     src/responder/common/negcache.h \
     src/responder/sudo/sudosrv_private.h \
     src/sbus/sbus_client.h \
@@ -438,6 +441,7 @@ sssd_nss_SOURCES = \
     src/responder/nss/nsssrv.c \
     src/responder/nss/nsssrv_cmd.c \
     src/responder/nss/nsssrv_netgroup.c \
+    src/responder/nss/nsssrv_mmap_cache.c \
     $(SSSD_RESPONDER_OBJ)
 sssd_nss_LDADD = \
     $(TDB_LIBS) \
@@ -1188,6 +1192,7 @@ installsssddirs::
     $(DESTDIR)$(sssdlibdir) \
     $(DESTDIR)$(sssdconfdir) \
     $(DESTDIR)$(dbpath) \
+    $(DESTDIR)$(mcpath) \
     $(DESTDIR)$(pidpath) \
     $(DESTDIR)$(logpath) \
     $(DESTDIR)$(pubconfpath)
diff --git a/configure.ac b/configure.ac
index f4d65ee0e56eedd7bf1b9a493acd1b2a89c36bc2..1d10f65d404ef592591be3daa358e031833ceec5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,6 +81,7 @@ WITH_PID_PATH
 WITH_LOG_PATH
 WITH_PUBCONF_PATH
 WITH_PIPE_PATH
+WITH_MCACHE_PATH
 WITH_INIT_DIR
 WITH_TEST_DIR
 WITH_MANPAGES
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index 8bc51148f5de360fa47f2c6705e9ef318cde61ea..543d16e691282c97ed8e8b745a82e1054b884c08 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -45,6 +45,7 @@ Requires(postun): initscripts chkconfig /sbin/ldconfig
 %global sssdstatedir %{_localstatedir}/lib/sss
 %global dbpath %{sssdstatedir}/db
 %global pipepath %{sssdstatedir}/pipes
+%global mcpath %{sssdstatedir}/mc
 %global pubconfpath %{sssdstatedir}/pubconf
 
 ### Build Dependencies ###
@@ -175,6 +176,7 @@ autoreconf -ivf
 
 %configure \
     --with-db-path=%{dbpath} \
+    --with-mcache-path=%(mcpath) \
     --with-pipe-path=%{pipepath} \
     --with-pubconf-path=%{pubconfpath} \
     --with-init-dir=%{_initrddir} \
@@ -267,6 +269,7 @@ rm -rf $RPM_BUILD_ROOT
 %dir %{sssdstatedir}
 %dir %{_localstatedir}/cache/krb5rcache
 %attr(700,root,root) %dir %{dbpath}
+%attr(755,root,root) %dir %{mcpath}
 %attr(755,root,root) %dir %{pipepath}
 %attr(755,root,root) %dir %{pubconfpath}
 %attr(700,root,root) %dir %{pipepath}/private
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index fc5dec67442b0e3be02cd6bf53592e7439fcbf75..b97e24c5a6d36b287ed05c748b8a3a3b63efb0df 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -111,6 +111,23 @@ AC_DEFUN([WITH_PIPE_PATH],
     AC_DEFINE_UNQUOTED(PIPE_PATH, "$config_pipepath", [Where to store pipe files for the SSSD interconnects])
   ])
 
+AC_DEFUN([WITH_MCACHE_PATH],
+  [ AC_ARG_WITH([mcache-path],
+                [AC_HELP_STRING([--with-mcache-path=PATH],
+                                [Where to store mmap cache files for the SSSD interconnects [/var/lib/sss/mc]]
+                               )
+                ]
+               )
+    config_mcpath="\"VARDIR\"/lib/sss/mc"
+    mcpath="${localstatedir}/lib/sss/mc"
+    if test x"$with_mcache_path" != x; then
+        config_mcpath=$with_mcache_path
+        mcpath=$with_mcache_path
+    fi
+    AC_SUBST(mcpath)
+    AC_DEFINE_UNQUOTED(MCACHE_PATH, "$config_mcpath", [Where to store mmap cache files for the SSSD interconnects])
+  ])
+
 AC_DEFUN([WITH_INITSCRIPT],
   [ AC_ARG_WITH([initscript],
                 [AC_HELP_STRING([--with-initscript=INITSCRIPT_TYPE],
@@ -383,4 +400,4 @@ AC_DEFUN([WITH_UNICODE_LIB],
     
     AM_CONDITIONAL([WITH_LIBUNISTRING], test x"$unicode_lib" = x"libunistring")
     AM_CONDITIONAL([WITH_GLIB], test x"$unicode_lib" = x"glib2")
-  ])
\ No newline at end of file
+  ])
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
index ab836938b79b4600a3885461d651695f86d37119..6b1130a533ca165d30fa6c9a43b7df5535ee543b 100644
--- a/src/responder/nss/nsssrv.c
+++ b/src/responder/nss/nsssrv.c
@@ -33,6 +33,7 @@
 #include "popt.h"
 #include "util/util.h"
 #include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
 #include "responder/common/negcache.h"
 #include "db/sysdb.h"
 #include "confdb/confdb.h"
@@ -319,6 +320,14 @@ int nss_process_init(TALLOC_CTX *mem_ctx,
         return EIO;
     }
 
+    /* create mmap caches */
+    /* TODO: read cache sizes from configuration */
+    ret = sss_mmap_cache_init(nctx, "passwd", SSS_MC_PASSWD,
+                              50000,
+                              &nctx->pwd_mc_ctx);
+    if (ret) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("mmap cache is DISABLED"));
+    }
     DEBUG(1, ("NSS Initialization complete\n"));
 
     return EOK;
diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h
index 5e365d7b3d3fa7f6c76665a10fefdc6dfd5cb265..46b1cc7ff4293fea9ae63042ea25782192133c7d 100644
--- a/src/responder/nss/nsssrv.h
+++ b/src/responder/nss/nsssrv.h
@@ -39,6 +39,7 @@
 #define NSS_PACKET_MAX_RECV_SIZE 1024
 
 struct getent_ctx;
+struct sss_mc_ctx;
 
 struct nss_ctx {
     struct resp_ctx *rctx;
@@ -63,6 +64,8 @@ struct nss_ctx {
     char **vetoed_shells;
     char **etc_shells;
     char *shell_fallback;
+
+    struct sss_mc_ctx *pwd_mc_ctx;
 };
 
 struct nss_packet;
diff --git a/src/responder/nss/nsssrv_mmap_cache.c b/src/responder/nss/nsssrv_mmap_cache.c
new file mode 100644
index 0000000000000000000000000000000000000000..00c7a89d8b9dca9b1f92bae86ab20f7415b16457
--- /dev/null
+++ b/src/responder/nss/nsssrv_mmap_cache.c
@@ -0,0 +1,300 @@
+/*
+   SSSD
+
+   NSS Responder - Mmap Cache
+
+   Copyright (C) Simo Sorce <ssorce@redhat.com>	2011
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include <sys/mman.h>
+#include <fcntl.h>
+#include "util/mmap_cache.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
+
+/* arbitrary (avg of my /etc/passwd) */
+#define SSS_AVG_PASSWD_PAYLOAD (MC_SLOT_SIZE * 4)
+/* short group name and no gids (private user group */
+#define SSS_AVG_GROUP_PAYLOAD (MC_SLOT_SIZE * 3)
+
+#define MC_NEXT_BARRIER(val) ((((val) + 1) & 0x00ffffff) | 0xf0000000)
+
+#define MC_RAISE_BARRIER(m) do { \
+    m->b2 = MC_NEXT_BARRIER(m->b1); \
+    __sync_synchronize(); \
+} while (0)
+
+#define MC_LOWER_BARRIER(m) do { \
+    __sync_synchronize(); \
+    m->b1 = m->b2; \
+} while (0)
+
+struct sss_mc_ctx {
+    char *name;             /* mmap cache name */
+    enum sss_mc_type type;  /* mmap cache type */
+    char *file;             /* mmap cache file name */
+    int fd;                 /* file descriptor */
+
+    uint32_t seed;          /* pseudo-random seed to avoid collision attacks */
+    time_t valid_time_slot; /* maximum time the entry is valid in seconds */
+
+    void *mmap_base;        /* base address of mmap */
+    size_t mmap_size;       /* total size of mmap */
+
+    uint32_t *hash_table;   /* hash table address (in mmap) */
+    uint32_t ht_size;       /* size of hash table */
+
+    uint8_t *free_table;    /* free list bitmaps */
+    uint32_t ft_size;       /* size of free table */
+    uint32_t next_slot;     /* the next slot after last allocation */
+
+    uint8_t *data_table;    /* data table address (in mmap) */
+    uint32_t dt_size;       /* size of data table */
+};
+
+
+/***************************************************************************
+ * initialization
+ ***************************************************************************/
+
+static errno_t sss_mc_set_recycled(int fd)
+{
+    uint32_t w = SSS_MC_HEADER_RECYCLED;
+    struct sss_mc_header h;
+    off_t offset;
+    off_t pos;
+    int ret;
+
+    offset = MC_PTR_DIFF(&h.status, &h);
+
+    pos = lseek(fd, offset, SEEK_SET);
+    if (pos == -1) {
+        /* What do we do now ? */
+        return errno;
+    }
+    pos = 0;
+    while (pos < sizeof(uint32_t)) {
+        ret = write(fd, ((uint8_t *)&w) + pos, sizeof(uint32_t) - pos);
+        if (ret == -1) {
+            if (errno != EINTR) {
+                return errno;
+            }
+            continue;
+        }
+        pos += ret;
+    }
+
+    return EOK;
+}
+
+/*
+ * When we (re)create a new file we must mark the current file as recycled
+ * so active clients will abandon its use asap.
+ * We unlink the current file and make a new one
+ */
+static errno_t sss_mc_create_file(struct sss_mc_ctx *mc_ctx)
+{
+    mode_t old_mask;
+    int ofd;
+    int ret;
+
+    ofd = open(mc_ctx->file, O_RDWR);
+    if (ofd != -1) {
+        ret = sss_mc_set_recycled(ofd);
+        if (ret) {
+            DEBUG(SSSDBG_TRACE_FUNC, ("Failed to mark mmap file %s as"
+                                      " recycled: %d(%s)\n",
+                                      mc_ctx->file, ret, strerror(ret)));
+        }
+
+        close(ofd);
+    }
+
+    ret = unlink(mc_ctx->file);
+    if (ret == -1) {
+        DEBUG(SSSDBG_TRACE_FUNC, ("Failed to rm mmap file %s: %d(%s)\n",
+                                  mc_ctx->file, ret, strerror(ret)));
+    }
+
+    /* temporarily relax umask as we need the file to be readable
+     * by everyone for now */
+    old_mask = umask(0022);
+
+    ret = 0;
+    mc_ctx->fd = open(mc_ctx->file, O_CREAT | O_EXCL | O_RDWR, 0644);
+    if (mc_ctx->fd == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to open mmap file %s: %d(%s)\n",
+                                    mc_ctx->file, ret, strerror(ret)));
+    }
+
+    /* reset mask back */
+    umask(old_mask);
+
+    return ret;
+}
+
+static void sss_mc_header_update(struct sss_mc_ctx *mc_ctx, int status)
+{
+    struct sss_mc_header *h;
+
+    /* update header using barriers */
+    h = (struct sss_mc_header *)mc_ctx->mmap_base;
+    MC_RAISE_BARRIER(h);
+    if (status != SSS_MC_HEADER_RECYCLED) {
+        /* no reason to update anything else if the file is recycled */
+        h->hash_table = MC_PTR_DIFF(mc_ctx->hash_table, mc_ctx->mmap_base);
+        h->free_table = MC_PTR_DIFF(mc_ctx->free_table, mc_ctx->mmap_base);
+        h->data_table = MC_PTR_DIFF(mc_ctx->data_table, mc_ctx->mmap_base);
+        h->ht_size = mc_ctx->ht_size;
+        h->ft_size = mc_ctx->ft_size;
+        h->dt_size = mc_ctx->dt_size;
+        h->major_vno = SSS_MC_MAJOR_VNO;
+        h->minor_vno = SSS_MC_MINOR_VNO;
+        h->seed = mc_ctx->seed;
+        h->reserved = 0;
+    }
+    h->status = status;
+    MC_LOWER_BARRIER(h);
+}
+
+errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name,
+                            enum sss_mc_type type, size_t n_elem,
+                            struct sss_mc_ctx **mcc)
+{
+    struct sss_mc_ctx *mc_ctx = NULL;
+    unsigned int rseed;
+    int payload;
+    int ret;
+
+    switch (type) {
+    case SSS_MC_PASSWD:
+        payload = SSS_AVG_PASSWD_PAYLOAD;
+        break;
+    case SSS_MC_GROUP:
+        payload = SSS_AVG_GROUP_PAYLOAD;
+        break;
+    default:
+        return EINVAL;
+    }
+
+    mc_ctx = talloc_zero(mem_ctx, struct sss_mc_ctx);
+    if (!mc_ctx) {
+        return ENOMEM;
+    }
+    mc_ctx->fd = -1;
+
+    mc_ctx->name = talloc_strdup(mem_ctx, name);
+    if (!mc_ctx->name) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    mc_ctx->type = type;
+
+    mc_ctx->valid_time_slot = 300; /* 5 min. FIXME: parametrize */
+
+    mc_ctx->file = talloc_asprintf(mc_ctx, "%s/%s",
+                                   SSS_NSS_MCACHE_DIR, name);
+    if (!mc_ctx->file) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* elements must always be multiple of 8 to make things easier to handle,
+     * so we increase by the necessary amount if they are not a multiple */
+    /* We can use MC_ALIGN64 for this */
+    n_elem = MC_ALIGN64(n_elem);
+
+    /* hash table is double the size because it will store both forward and
+     * reverse keys (name/uid, name/gid, ..) */
+    mc_ctx->ht_size = MC_HT_SIZE(n_elem * 2);
+    mc_ctx->dt_size = MC_DT_SIZE(n_elem, payload);
+    mc_ctx->ft_size = MC_FT_SIZE(n_elem);
+    mc_ctx->mmap_size = MC_HEADER_SIZE +
+                        MC_ALIGN64(mc_ctx->dt_size) +
+                        MC_ALIGN64(mc_ctx->ft_size) +
+                        MC_ALIGN64(mc_ctx->ht_size);
+
+
+    /* for now ALWAYS create a new file on restart */
+
+    ret = sss_mc_create_file(mc_ctx);
+    if (ret) {
+        goto done;
+    }
+
+    ret = ftruncate(mc_ctx->fd, mc_ctx->mmap_size);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to resize file %s: %d(%s)\n",
+                                    mc_ctx->file, ret, strerror(ret)));
+        goto done;
+    }
+
+    mc_ctx->mmap_base = mmap(NULL, mc_ctx->mmap_size,
+                             PROT_READ | PROT_WRITE,
+                             MAP_SHARED, mc_ctx->fd, 0);
+    if (mc_ctx->mmap_base == MAP_FAILED) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to mmap file %s(%ld): %d(%s)\n",
+                                    mc_ctx->file, mc_ctx->mmap_size,
+                                    ret, strerror(ret)));
+        goto done;
+    }
+
+    mc_ctx->data_table = MC_PTR_ADD(mc_ctx->mmap_base, MC_HEADER_SIZE);
+    mc_ctx->free_table = MC_PTR_ADD(mc_ctx->data_table,
+                                    MC_ALIGN64(mc_ctx->dt_size));
+    mc_ctx->hash_table = MC_PTR_ADD(mc_ctx->free_table,
+                                    MC_ALIGN64(mc_ctx->ft_size));
+
+    memset(mc_ctx->data_table, 0x00, mc_ctx->dt_size);
+    memset(mc_ctx->free_table, 0x00, mc_ctx->ft_size);
+    memset(mc_ctx->hash_table, 0xff, mc_ctx->ht_size);
+
+    /* generate a pseudo-random seed.
+     * Needed to fend off dictionary based collision attacks */
+    rseed = time(NULL) * getpid();
+    mc_ctx->seed = rand_r(&rseed);
+
+    sss_mc_header_update(mc_ctx, SSS_MC_HEADER_ALIVE);
+
+    ret = EOK;
+
+done:
+    if (ret) {
+        if (mc_ctx && mc_ctx->fd != -1) {
+            close(mc_ctx->fd);
+            ret = unlink(mc_ctx->file);
+            if (ret == -1) {
+                DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to rm mmap file %s: %d(%s)\n",
+                                            mc_ctx->file, ret, strerror(ret)));
+            }
+        }
+
+        if (mc_ctx && mc_ctx->mmap_base) {
+            munmap(mc_ctx->mmap_base, mc_ctx->mmap_size);
+        }
+        talloc_free(mc_ctx);
+    } else {
+        *mcc = mc_ctx;
+    }
+    return ret;
+}
+
diff --git a/src/responder/nss/nsssrv_mmap_cache.h b/src/responder/nss/nsssrv_mmap_cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..6f49b8b76a576b86706da27784a01caf8b8b1beb
--- /dev/null
+++ b/src/responder/nss/nsssrv_mmap_cache.h
@@ -0,0 +1,37 @@
+/*
+   SSSD
+
+   NSS Responder - Mmap Cache
+
+   Copyright (C) Simo Sorce <ssorce@redhat.com>	2011
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NSSSRV_MMAP_CACHE_H_
+#define _NSSSRV_MMAP_CACHE_H_
+
+struct sss_mc_ctx;
+
+enum sss_mc_type {
+    SSS_MC_NONE = 0,
+    SSS_MC_PASSWD,
+    SSS_MC_GROUP,
+};
+
+errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name,
+                            enum sss_mc_type type, size_t n_elem,
+                            struct sss_mc_ctx **mcc);
+
+#endif /* _NSSSRV_MMAP_CACHE_H_ */
-- 
1.7.7.5

