X-Git-Url: https://git.donarmstrong.com/?p=unscd.git;a=blobdiff_plain;f=nscd.c;h=35502d7bedb175cae7bf863206f75c2594273382;hp=593be0bcabda67357baeaf8917a1b89c598e8803;hb=2866357cf8b7106e16b39c7547fbdc24d6ecc9ec;hpb=8855a92fa6ed39ef3f27ff856be4c6b710ecbc95 diff --git a/nscd.c b/nscd.c index 593be0b..35502d7 100644 --- 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 @@ -140,8 +141,13 @@ vda.linux@googlemail.com * 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 */ -#define PROGRAM_VERSION "0.48" +#define PROGRAM_VERSION "0.53" #define DEBUG_BUILD 1 @@ -663,6 +669,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; @@ -695,12 +705,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. @@ -764,17 +779,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; @@ -1181,13 +1215,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; @@ -1482,10 +1521,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)]; @@ -1496,15 +1539,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); } } @@ -1520,7 +1568,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 @@ -1540,7 +1588,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; } @@ -1552,12 +1601,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); @@ -1594,6 +1637,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 ) { @@ -1619,7 +1669,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 */ @@ -1700,14 +1754,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); @@ -1766,8 +1824,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; } @@ -1878,24 +1938,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; } @@ -1909,13 +1962,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); @@ -1925,9 +1982,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", @@ -1949,7 +2009,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; } @@ -1986,7 +2046,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 ) { @@ -2569,7 +2629,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);