]> git.donarmstrong.com Git - unscd.git/blobdiff - nscd.c
Migrate to salsa (with donarmstrong.com as a backup)
[unscd.git] / nscd.c
diff --git a/nscd.c b/nscd.c
index 29494ff89771f365b8c6dd76fe5971ab44c4cf50..c4e4ecba4b48a217a79e0eeb54e9f439a47cfb95 100644 (file)
--- a/nscd.c
+++ b/nscd.c
@@ -1,5 +1,6 @@
 /* This file is part of unscd, a complete nscd replacement.
- * Copyright (C) 2007 Denys Vlasenko. Licensed under the GPL version 2. */
+ * Copyright (C) 2007-2012 Denys Vlasenko. Licensed under the GPL version 2.
+ */
 
 /* unscd is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -139,8 +140,15 @@ vda.linux@googlemail.com
  *        thanks to Sebastian Krahmer (krahmer AT suse.de)
  * 0.46   fix a case when we forgot to remove a future entry on worker failure
  * 0.47   fix nscd without -d to not bump debug level
+ * 0.48   fix for changes in __nss_disable_nscd API in glibc-2.15
+ * 0.49   minor tweaks to messages
+ * 0.50   add more files to watch for changes
+ * 0.51   fix a case where we forget to refcount-- the cached entry
+ * 0.52   make free_refcounted_ureq() tolerant to pointers to NULLs
+ * 0.53   fix INVALIDATE and SHUTDOWN requests being ignored
+ * 0.54   clang warning fix for "str" + OFFSET trick and variable struct field
  */
-#define PROGRAM_VERSION "0.47"
+#define PROGRAM_VERSION "0.54"
 
 #define DEBUG_BUILD 1
 
@@ -263,9 +271,11 @@ static void dump(const void *ptr, int len)
        buf = ptr;
        while (len > 0) {
                int chunk = ((len >= 16) ? 16 : len);
-               fprintf(stderr,
+               const char *fmt =
                        "%02x %02x %02x %02x %02x %02x %02x %02x "
-                       "%02x %02x %02x %02x %02x %02x %02x %02x " + (16-chunk) * 5,
+                       "%02x %02x %02x %02x %02x %02x %02x %02x ";
+               fmt += (16-chunk) * 5;
+               fprintf(stderr, fmt,
                        buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
                        buf[8], buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
                );
@@ -662,6 +672,10 @@ static int num_clients = 2; /* two listening sockets are "clients" too */
 /* We read up to max_reqnum requests in parallel */
 static unsigned max_reqnum = 14;
 static int next_buf;
+/* To be allocated at init to become client_buf[max_reqnum][MAX_USER_REQ_SIZE].
+ * Note: it is a pointer to [MAX_USER_REQ_SIZE] arrays,
+ * not [MAX_USER_REQ_SIZE] array of pointers.
+ */
 static char          (*client_buf)[MAX_USER_REQ_SIZE];
 static char          *busy_cbuf;
 static struct pollfd *pfd;
@@ -694,12 +708,17 @@ static client_info   *cinfo;
  *          wait for another client's worker.
  *          Otherwise, it's 0 and client's fd is in pfd[i].fd
  *      .bufidx: index in client_buf[] we store client's request in
+ *      .ureq: = client_buf[bufidx]
  *      .bytecnt: size of the request
  *      .started_ms: used to time out unresponsive clients
- *      .respos:
- *      .resptr:
- *      .cache_pp: &cache[x][y] where the response is, or will be stored.
- *      .ureq:
+ *      .resptr: initially NULL. Later, same as cache[x][y] pointer to a cached
+ *          response, or (a rare case) a "fake cache" entry:
+ *          all cache[hash(request)][0..7] blocks were found busy,
+ *          the result won't be cached.
+ *      .respos: "write-out to client" offset
+ *      .cache_pp: initially NULL. Later, &cache[x][y] where the response is,
+ *          or will be stored. Remains NULL if "fake cache" entry is in use
+ *
  * When a client has received its reply (or otherwise closed (timeout etc)),
  * corresponding pfd[i] and cinfo[i] are removed by shifting [i+1], [i+2] etc
  * elements down, so that both arrays never have free holes.
@@ -763,17 +782,36 @@ static inline void *bufno2buf(int i)
        return client_buf[i];
 }
 
+static void free_refcounted_ureq(user_req **ureqp);
+
 static void close_client(unsigned i)
 {
        log(L_DEBUG, "closing client %u (fd %u,%u)", i, pfd[i].fd, cinfo[i].client_fd);
        /* Paranoia. We had nasty bugs where client was closed twice. */
-       if (pfd[i].fd == 0) ////
+       if (pfd[i].fd == 0)
                return;
+
        close(pfd[i].fd);
        if (cinfo[i].client_fd && cinfo[i].client_fd != pfd[i].fd)
                close(cinfo[i].client_fd);
        pfd[i].fd = 0; /* flag as unused (coalescing needs this) */
        busy_cbuf[cinfo[i].bufidx] = 0;
+
+       if (cinfo[i].cache_pp == NULL) {
+               user_req *resptr = cinfo[i].resptr;
+               if (resptr) {
+                       log(L_DEBUG, "client %u: freeing fake cache entry %p", i, resptr);
+                       free(resptr);
+               }
+       } else {
+               /* Most of the time, it is not freed here,
+                * only refcounted--. Freeing happens
+                * if it was deleted from cache[] but retained
+                * for writeout.
+                */
+               free_refcounted_ureq(&cinfo[i].resptr);
+       }
+
        cnt_closed++;
        if (i < min_closed)
                min_closed = i;
@@ -1180,13 +1218,18 @@ static void free_refcounted_ureq(user_req **ureqp)
 {
        user_req *ureq = *ureqp;
 
+       /* (when exactly can this happen?) */
+       if (ureq == NULL)
+               return;
+
        if (!CACHED_ENTRY(ureq))
                return;
 
        if (ureq->refcount) {
                ureq->refcount--;
+               log(L_DEBUG2, "--%p.refcount=%u", ureq, ureq->refcount);
        } else {
-               log(L_DEBUG2, "refcount == 0, free(%p)", ureq);
+               log(L_DEBUG2, "%p.refcount=0, freeing", ureq);
                free(ureq);
        }
        *ureqp = NULL;
@@ -1481,10 +1524,14 @@ static void worker(const char *param)
 ** Main loop
 */
 
-static const char checked_filenames[][sizeof("/etc/passwd")] = {
-       [SRV_PASSWD] = "/etc/passwd", /*  "/etc/shadow"? */
-       [SRV_GROUP]  = "/etc/group",
-       [SRV_HOSTS]  = "/etc/hosts", /* "/etc/resolv.conf" "/etc/nsswitch.conf"? */
+static const char *const checked_filenames[] = {
+       /* Note: compiler adds another \0 byte at the end of each array element,
+        * so there are TWO \0's there.
+        */
+       [SRV_PASSWD] = "/etc/passwd\0" "/etc/passwd.cache\0" "/etc/shadow\0",
+       [SRV_GROUP]  = "/etc/group\0"  "/etc/group.cache\0",
+       [SRV_HOSTS]  = "/etc/hosts\0"  "/etc/hosts.cache\0"  "/etc/resolv.conf\0"  "/etc/nsswitch.conf\0",
+       /* ("foo.cache" files are maintained by libnss-cache) */
 };
 
 static long checked_status[ARRAY_SIZE(checked_filenames)];
@@ -1495,15 +1542,20 @@ static void check_files(int srv)
        const char *file = checked_filenames[srv];
        long v;
 
-       memset(&tsb, 0, sizeof(tsb));
-       stat(file, &tsb); /* ignore errors */
-       /* Comparing struct stat's was giving false positives.
-        * Extracting only those fields which are interesting: */
-       v = (long)tsb.st_mtime ^ (long)tsb.st_size ^ (long)tsb.st_ino; /* ^ (long)tsb.st_dev ? */
+       v = 0;
+       do {
+               memset(&tsb, 0, sizeof(tsb));
+               stat(file, &tsb); /* ignore errors */
+               /* Comparing struct stat's was giving false positives.
+                * Extracting only those fields which are interesting:
+                */
+               v ^= (long)tsb.st_mtime ^ (long)tsb.st_size ^ (long)tsb.st_ino; /* ^ (long)tsb.st_dev ? */
+               file += strlen(file) + 1;
+       } while (file[0]);
 
        if (v != checked_status[srv]) {
                checked_status[srv] = v;
-               log(L_INFO, "detected change in %s", file);
+               log(L_INFO, "detected change in files related to service %d", srv);
                age_cache(/*free_all:*/ 1, srv);
        }
 }
@@ -1519,7 +1571,7 @@ static int handle_client(int i)
 #if DEBUG_BUILD
        log(L_DEBUG, "version:%d type:%d(%s) key_len:%d '%s'",
                        ureq->version, ureq->type,
-                       ureq->type < ARRAY_SIZE(typestr) ? typestr[ureq->type] : "BAD",
+                       ureq->type < ARRAY_SIZE(typestr) ? typestr[ureq->type] : "?",
                        ureq->key_len, req_str(ureq->type, ureq->reqbuf));
 #endif
 
@@ -1539,7 +1591,8 @@ static int handle_client(int i)
                return 0; /* more to read */
        }
        if (cinfo[i].bytecnt > USER_HDR_SIZE + ureq->key_len) {
-               log(L_INFO, "read overflow");
+               log(L_INFO, "read overflow: %u > %u",
+                       (int)cinfo[i].bytecnt, (int)(USER_HDR_SIZE + ureq->key_len));
                close_client(i);
                return 0;
        }
@@ -1551,12 +1604,6 @@ static int handle_client(int i)
                close_client(i);
                return 0;
        }
-       srv = type_to_srv[ureq->type];
-       if (!config.srv_enable[srv]) {
-               log(L_INFO, "service %d is disabled, dropping", srv);
-               close_client(i);
-               return 0;
-       }
 
        hex_dump(cinfo[i].ureq, cinfo[i].bytecnt);
 
@@ -1593,6 +1640,13 @@ static int handle_client(int i)
                return 0;
        }
 
+       srv = type_to_srv[ureq->type];
+       if (!config.srv_enable[srv]) {
+               log(L_INFO, "service %d is disabled, dropping", srv);
+               close_client(i);
+               return 0;
+       }
+
        if (ureq->type != GETHOSTBYADDR
         && ureq->type != GETHOSTBYADDRv6
        ) {
@@ -1618,7 +1672,11 @@ static int handle_client(int i)
 
                        log(L_DEBUG, "sz:%u", sz);
                        hex_dump(resp, sz);
-                       ureq_and_resp->refcount++; /* cache shouldn't free it under us! */
+                       /* cache shouldn't free it under us! */
+                       if (++ureq_and_resp->refcount == 0) {
+                               error_and_die("BUG! ++%p.refcount rolled over to 0, exiting", ureq_and_resp);
+                       }
+                       log(L_DEBUG2, "++%p.refcount=%u", ureq_and_resp, ureq_and_resp->refcount);
                        pfd[i].events = POLLOUT; /* we want to write out */
                        cinfo[i].resptr = ureq_and_resp;
                        /*cinfo[i].respos = 0; - already is */
@@ -1699,14 +1757,18 @@ static void handle_worker_response(int i)
        }
 
        resp_sz = sz_and_found.version_or_size;
-       if (resp_sz < sz || resp_sz > 0xfffffff) { /* 256 mb */
+       if (resp_sz < sz || resp_sz > 0x0fffffff) { /* 256 mb */
                error("BUG: bad size from worker:%u", resp_sz);
                goto err;
        }
 
        /* Create new block of cached info */
        cached = xzalloc(ureq_sz_aligned + resp_sz);
-       log(L_DEBUG2, "xzalloc(%u):%p", ureq_sz_aligned + resp_sz, cached);
+       log(L_DEBUG2, "xzalloc(%u):%p sz:%u resp_sz:%u found:%u",
+                       ureq_sz_aligned + resp_sz, cached,
+                       sz, resp_sz,
+                       (int)sz_and_found.found
+                       );
        resp = (void*) (((char*) cached) + ureq_sz_aligned);
        memcpy(cached, ureq, ureq_size(ureq));
        memcpy(resp, &sz_and_found, sz);
@@ -1765,8 +1827,10 @@ static void handle_worker_response(int i)
        prepare_for_writeout(i, cached);
 ret:
        /* cache shouldn't free it under us! */
-       if (cached)
+       if (cached) {
                cached->refcount = ref;
+               log(L_DEBUG2, "%p.refcount=%u", cached, ref);
+       }
        aging_interval_ms = min_aging_interval_ms;
 }
 
@@ -1877,24 +1941,17 @@ static void main_loop(void)
                                resp = ureq_response(cinfo[i].resptr);
                                resp_sz = resp->version_or_size;
                                resp->version_or_size = NSCD_VERSION;
+                               errno = 0;
                                r = safe_write(pfd[i].fd, ((char*) resp) + cinfo[i].respos, resp_sz - cinfo[i].respos);
                                resp->version_or_size = resp_sz;
 
-                               if (r < 0 && errno == EAGAIN)
+                               if (r < 0 && errno == EAGAIN) {
+                                       log(L_DEBUG, "client %u: EAGAIN on write", i);
                                        continue;
+                               }
                                if (r <= 0) { /* client isn't there anymore */
-                                       log(L_DEBUG, "client %d is gone (write returned %d)", i, r);
- write_out_is_done:
-                                       if (cinfo[i].cache_pp == NULL) {
-                                               log(L_DEBUG, "client %d: freeing fake cache entry %p", i, cinfo[i].resptr);
-                                               free(cinfo[i].resptr);
-                                       } else {
-                                               /* Most of the time, it is not freed here,
-                                                * only refcounted--. Freeing happens
-                                                * if it was deleted from cache[] but retained
-                                                * for writeout. */
-                                               free_refcounted_ureq(&cinfo[i].resptr);
-                                       }
+                                       log(L_DEBUG, "client %u is gone (write returned:%d err:%s)",
+                                                       i, r, errno ? strerror(errno) : "-");
                                        close_client(i);
                                        continue;
                                }
@@ -1908,13 +1965,17 @@ static void main_loop(void)
                                         * read(3, "www.google.com\0\0", 16) = 16
                                         * close(3) = 0
                                         */
-                                       log(L_DEBUG, "client %u: sent answer %u bytes", i, cinfo[i].respos);
-                                       goto write_out_is_done;
+                                       log(L_DEBUG, "client %u: sent answer %u/%u/%u bytes", i, r, cinfo[i].respos, resp_sz);
+                                       close_client(i);
+                                       continue;
                                }
+                               log(L_DEBUG, "client %u: sent partial answer %u/%u/%u bytes", i, r, cinfo[i].respos, resp_sz);
+                               continue;
                        }
 
                        /* "Read reply from worker" case. Worker may be
-                        * already dead, revents may contain other bits too */
+                        * already dead, revents may contain other bits too
+                        */
                        if ((pfd[i].revents & POLLIN) && cinfo[i].client_fd) {
                                log(L_DEBUG, "reading response for client %u", i);
                                handle_worker_response(i);
@@ -1924,9 +1985,12 @@ static void main_loop(void)
                        }
 
                        /* POLLHUP means pfd[i].fd is closed by peer.
-                        * POLLHUP+POLLOUT is seen when we switch for writeout
-                        * and see that pfd[i].fd is closed by peer. */
-                       if ((pfd[i].revents & ~POLLOUT) == POLLHUP) {
+                        * POLLHUP+POLLOUT[+POLLERR] is seen when we writing out
+                        * and see that pfd[i].fd is closed by peer (for example,
+                        * it happens when client's result buffer is too small
+                        * to receive a huge GETGRBYNAME result).
+                        */
+                       if ((pfd[i].revents & ~(POLLOUT+POLLERR)) == POLLHUP) {
                                int is_client = (cinfo[i].client_fd == 0 || cinfo[i].client_fd == pfd[i].fd);
                                log(L_INFO, "%s %u disappeared (got POLLHUP on fd %d)",
                                        is_client ? "client" : "worker",
@@ -1948,7 +2012,7 @@ static void main_loop(void)
                        /* All strange and unexpected cases */
                        if (pfd[i].revents != POLLIN) {
                                /* Not just "can read", but some other bits are there */
-                               log(L_INFO, "client %u revents is strange:%x", i, pfd[i].revents);
+                               log(L_INFO, "client %u revents is strange:0x%x", i, pfd[i].revents);
                                close_client(i);
                                continue;
                        }
@@ -1985,7 +2049,7 @@ static void main_loop(void)
 
                /* Close timed out client connections */
                for (i = 2; i < num_clients; i++) {
-                       if (pfd[i].fd != 0 /* not closed yet? */ ////
+                       if (pfd[i].fd != 0 /* not closed yet? */
                         && cinfo[i].client_fd == 0 /* do we still wait for client, not worker? */
                         && (g_now_ms - cinfo[i].started_ms) > CLIENT_TIMEOUT_MS
                        ) {
@@ -2097,7 +2161,7 @@ static const struct option longopt[] = {
 };
 
 static const char *const help[] = {
-       "Do not daemonize; log to stderr",
+       "Do not daemonize; log to stderr (-dd: more verbosity)",
        "File to read configuration from",
        "Invalidate cache",
        "Shut the server down",
@@ -2347,6 +2411,7 @@ static char* user_to_env_U(const char *user)
 
 
 /* not static - don't inline me, compiler! */
+void readlink_self_exe(void);
 void readlink_self_exe(void)
 {
        char buf[PATH_MAX + 1];
@@ -2380,24 +2445,38 @@ static void special_op(const char *arg)
                printf("sent shutdown request, exiting\n");
        } else { /* invalidate */
                size_t arg_len = strlen(arg) + 1;
-               struct {
-                       user_req_header req;
-                       char arg[arg_len];
-               } reqdata;
-               reqdata.req.version = NSCD_VERSION;
-               reqdata.req.type = INVALIDATE;
-               reqdata.req.key_len = arg_len;
-               memcpy(reqdata.arg, arg, arg_len);
-               xfull_write(sock, &reqdata, arg_len + sizeof(ureq));
+               char buf[sizeof(user_req_header) + arg_len];
+               user_req_header *req = (void*) buf;
+
+               req->version = NSCD_VERSION;
+               req->type = INVALIDATE;
+               req->key_len = arg_len;
+               memcpy(req + 1, arg, arg_len);
+               xfull_write(sock, req, sizeof(*req) + arg_len);
                printf("sent invalidate(%s) request, exiting\n", arg);
        }
        exit(0);
 }
 
 
+/* Callback for glibc-2.15 */
+struct traced_file;
+static void do_nothing(size_t dbidx, struct traced_file *finfo)
+{
+       /* nscd from glibc-2.15 does something like this:
+       if (!dbs[dbidx].enabled || !dbs[dbidx].check_file)
+               return;
+       add_file_to_watch_list(finfo->fname);
+       */
+}
+
 /* This internal glibc function is called to disable trying to contact nscd.
- * We _are_ nscd, so we need to do the lookups, and not recurse. */
-void __nss_disable_nscd(void);
+ * We _are_ nscd, so we need to do the lookups, and not recurse.
+ * Until 2.14, this function was taking no parameters.
+ * In 2.15, it takes a function pointer from hell.
+ */
+void __nss_disable_nscd(void (*hell)(size_t, struct traced_file*));
+
 
 int main(int argc, char **argv)
 {
@@ -2407,7 +2486,7 @@ int main(int argc, char **argv)
        const char *conffile;
 
        /* make sure we don't get recursive calls */
-       __nss_disable_nscd();
+       __nss_disable_nscd(do_nothing);
 
        if (argv[0][0] == 'w') /* "worker_nscd" */
                worker(argv[1]);
@@ -2552,7 +2631,7 @@ int main(int argc, char **argv)
                signal(SIGTSTP, SIG_IGN);
        }
 
-       log(L_ALL, "nscd v" PROGRAM_VERSION ", debug level 0x%x", debug & L_ALL);
+       log(L_ALL, "unscd v" PROGRAM_VERSION ", debug level 0x%x", debug & L_ALL);
        log(L_DEBUG, "max %u requests in parallel", max_reqnum - 2);
        log(L_DEBUG, "cache size %u x 8 entries", cache_size);