diff --git a/Makefile.am b/Makefile.am index fb380f8..105ae72 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,7 +3,7 @@ bin_PROGRAMS = nbd-server nbd-trdump sbin_PROGRAMS = @NBD_CLIENT_NAME@ EXTRA_PROGRAMS = nbd-client make-integrityhuge TESTS_ENVIRONMENT=$(srcdir)/simple_test -TESTS = cmd cfg1 cfgmulti cfgnew cfgsize write flush integrity dirconfig #integrityhuge +TESTS = cmd cfg1 cfgmulti cfgnew cfgsize write flush integrity dirconfig list #integrityhuge check_PROGRAMS = nbd-tester-client nbd_client_SOURCES = nbd-client.c cliserv.h nbd_server_SOURCES = nbd-server.c cliserv.h lfs.h nbd.h @@ -28,3 +28,4 @@ flush: integrity: integrityhuge: dirconfig: +list: diff --git a/configure.ac b/configure.ac index 0a867df..e11f4fd 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Configure script for NBD system dnl (c) 1998 Martin Mares , (c) 2000 Pavel Machek , dnl (c) 2003-2006 Wouter Verhelst -AC_INIT([nbd],[3.1.1],[wouter@debian.org]) +AC_INIT([nbd],[3.2],[wouter@debian.org]) AM_INIT_AUTOMAKE(foreign dist-bzip2) AM_MAINTAINER_MODE diff --git a/man/nbd-client.8.in.sgml b/man/nbd-client.8.in.sgml index b375dd0..8c862a1 100644 --- a/man/nbd-client.8.in.sgml +++ b/man/nbd-client.8.in.sgml @@ -201,7 +201,7 @@ manpage.1: manpage.sgml Connect to the server using the Socket Direct Protocol - (SDP), rather than IP. See nbd-server(1) for details. + (SDP), rather than IP. See nbd-server(5) for details. @@ -211,8 +211,9 @@ manpage.1: manpage.sgml Specifies that this NBD device will be used as swapspace. This option attempts to prevent deadlocks by - performing mlockall() at an appropriate time. It does not - however guarantee that such deadlocks can be avoided. + performing mlockall() and adjusting the oom-killer score + at an appropriate time. It does not however guarantee + that such deadlocks can be avoided. diff --git a/nbd-client.c b/nbd-client.c index 7a526b9..d20f32c 100644 --- a/nbd-client.c +++ b/nbd-client.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -94,7 +95,7 @@ int opennet(char *name, char* portstr, int sdp) { if(e != 0) { fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(e)); freeaddrinfo(ai); - exit(EXIT_FAILURE); + return -1; } if(sdp) { @@ -118,8 +119,10 @@ int opennet(char *name, char* portstr, int sdp) { break; /* success */ } - if (rp == NULL) - err("Socket failed: %m"); + if (rp == NULL) { + err_nonfatal("Socket failed: %m"); + return -1; + } setmysockopt(sock); @@ -371,6 +374,21 @@ void finish_sock(int sock, int nbd, int swap) { mlockall(MCL_CURRENT | MCL_FUTURE); } +static int +oom_adjust(const char *file, const char *value) +{ + int fd, rc; + size_t len; + + fd = open(file, O_WRONLY); + if (fd < 0) + return -1; + len = strlen(value); + rc = write(fd, value, len) != (ssize_t) len; + close(fd); + return rc ? -1 : 0; +} + void usage(char* errmsg, ...) { if(errmsg) { char tmp[256]; @@ -550,6 +568,8 @@ int main(int argc, char *argv[]) { } sock = opennet(hostname, port, sdp); + if (sock < 0) + exit(EXIT_FAILURE); negotiate(sock, &size64, &flags, name, needed_flags, cflags, opts); @@ -560,6 +580,13 @@ int main(int argc, char *argv[]) { setsizes(nbd, size64, blocksize, flags); set_timeout(nbd, timeout); finish_sock(sock, nbd, swap); + if (swap) { + /* try linux >= 2.6.36 interface first */ + if (oom_adjust("/proc/self/oom_score_adj", "-1000")) { + /* fall back to linux <= 2.6.35 interface */ + oom_adjust("/proc/self/oom_adj", "-17"); + } + } /* Go daemon */ @@ -571,6 +598,16 @@ int main(int argc, char *argv[]) { #endif do { #ifndef NOFORK +#ifdef SA_NOCLDWAIT + struct sigaction sa; + + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDWAIT; + if (sigaction(SIGCHLD, &sa, NULL) < 0) + err("sigaction: %m"); +#endif + if (!fork()) { /* Due to a race, the kernel NBD driver cannot * call for a reread of the partition table @@ -603,10 +640,17 @@ int main(int argc, char *argv[]) { u64 new_size; u32 new_flags; - fprintf(stderr, " Reconnecting\n"); close(sock); close(nbd); - sock = opennet(hostname, port, sdp); + for (;;) { + fprintf(stderr, " Reconnecting\n"); + sock = opennet(hostname, port, sdp); + if (sock >= 0) + break; + sleep (1); + } nbd = open(nbddev, O_RDWR); + if (nbd < 0) + err("Cannot open NBD: %m"); negotiate(sock, &new_size, &new_flags, name, needed_flags, cflags, opts); if (size64 != new_size) { err("Size of the device changed. Bye"); diff --git a/nbd-server.c b/nbd-server.c index a8f4f40..49822a7 100644 --- a/nbd-server.c +++ b/nbd-server.c @@ -185,7 +185,7 @@ char default_authname[] = SYSCONFDIR "/nbd-server/allow"; /**< default name of a #define NEG_OLD (1 << 1) #define NEG_MODERN (1 << 2) -int modernsock=0; /**< Socket for the modern handler. Not used +int modernsock=-1; /**< Socket for the modern handler. Not used if a client was only specified on the command line; only port used if oldstyle is set to false (and then the @@ -803,7 +803,7 @@ GArray* do_cfile_dir(gchar* dir, GError** e) { case DT_REG: /* Skip unless the name ends with '.conf' */ if(strcmp((de->d_name + strlen(de->d_name) - 5), ".conf")) { - continue; + goto next; } tmp = parse_cfile(fname, FALSE, e); errno=saved_errno; @@ -1087,10 +1087,8 @@ void sigchld_handler(int s) { **/ void killchild(gpointer key, gpointer value, gpointer user_data) { pid_t *pid=value; - int *parent=user_data; kill(*pid, SIGTERM); - *parent=1; } /** @@ -1099,13 +1097,8 @@ void killchild(gpointer key, gpointer value, gpointer user_data) { * is severely wrong). **/ void sigterm_handler(int s) { - int parent=0; - - g_hash_table_foreach(children, killchild, &parent); - - if(parent) { - unlink(pidfname); - } + g_hash_table_foreach(children, killchild, NULL); + unlink(pidfname); exit(EXIT_SUCCESS); } @@ -2062,8 +2055,9 @@ void serveconnection(CLIENT *client) { * @param client information about the client. The IP address in human-readable * format will be written to a new char* buffer, the address of which will be * stored in client->clientname. + * @return: 0 - OK, -1 - failed. **/ -void set_peername(int net, CLIENT *client) { +int set_peername(int net, CLIENT *client) { struct sockaddr_storage addrin; struct sockaddr_storage netaddr; struct sockaddr_in *netaddr4 = NULL; @@ -2078,13 +2072,15 @@ void set_peername(int net, CLIENT *client) { int e; int shift; - if (getpeername(net, (struct sockaddr *) &addrin, &addrinlen) < 0) - err("getpeername failed: %m"); + if (getpeername(net, (struct sockaddr *) &addrin, &addrinlen) < 0) { + msg2(LOG_INFO, "getpeername failed: %m"); + return -1; + } if((e = getnameinfo((struct sockaddr *)&addrin, addrinlen, peername, sizeof (peername), NULL, 0, NI_NUMERICHOST))) { msg3(LOG_INFO, "getnameinfo failed: %s", gai_strerror(e)); - freeaddrinfo(ai); + return -1; } memset(&hints, '\0', sizeof (hints)); @@ -2094,23 +2090,27 @@ void set_peername(int net, CLIENT *client) { if(e != 0) { msg3(LOG_INFO, "getaddrinfo failed: %s", gai_strerror(e)); freeaddrinfo(ai); - return; + return -1; } switch(client->server->virtstyle) { case VIRT_NONE: + msg2(LOG_DEBUG, "virtualization is off"); client->exportname=g_strdup(client->server->exportname); break; case VIRT_IPHASH: + msg2(LOG_DEBUG, "virtstyle iphash"); for(i=0;iexportname=g_strdup_printf(client->server->exportname, peername); break; case VIRT_CIDR: + msg3(LOG_DEBUG, "virtstyle cidr %d", client->server->cidrlen); memcpy(&netaddr, &addrin, addrinlen); if(ai->ai_family == AF_INET) { netaddr4 = (struct sockaddr_in *)&netaddr; @@ -2148,6 +2148,7 @@ void set_peername(int net, CLIENT *client) { msg4(LOG_INFO, "connect from %s, assigned file is %s", peername, client->exportname); client->clientname=g_strdup(peername); + return 0; } /** @@ -2158,6 +2159,95 @@ void destroy_pid_t(gpointer data) { g_free(data); } +static void +handle_connection(GArray *servers, int net, SERVER *serve, CLIENT *client) +{ + int sock_flags_old; + int sock_flags_new; + + if(serve->max_connections > 0 && + g_hash_table_size(children) >= serve->max_connections) { + msg2(LOG_INFO, "Max connections reached"); + goto handle_connection_out; + } + if((sock_flags_old = fcntl(net, F_GETFL, 0)) == -1) { + err("fcntl F_GETFL"); + } + sock_flags_new = sock_flags_old & ~O_NONBLOCK; + if (sock_flags_new != sock_flags_old && + fcntl(net, F_SETFL, sock_flags_new) == -1) { + err("fcntl F_SETFL ~O_NONBLOCK"); + } + if(!client) { + client = g_new0(CLIENT, 1); + client->server=serve; + client->exportsize=OFFT_MAX; + client->net=net; + client->transactionlogfd = -1; + } + if (set_peername(net, client)) { + goto handle_connection_out; + } + if (!authorized_client(client)) { + msg2(LOG_INFO,"Unauthorized client") ; + goto handle_connection_out; + } + msg2(LOG_INFO,"Authorized client") ; + + if (!dontfork) { + pid_t pid; + int i; + sigset_t newset; + sigset_t oldset; + + sigemptyset(&newset); + sigaddset(&newset, SIGCHLD); + sigaddset(&newset, SIGTERM); + sigprocmask(SIG_BLOCK, &newset, &oldset); + if ((pid = fork()) < 0) { + msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ; + sigprocmask(SIG_SETMASK, &oldset, NULL); + goto handle_connection_out; + } + if (pid > 0) { /* parent */ + pid_t *pidp; + + pidp = g_malloc(sizeof(pid_t)); + *pidp = pid; + g_hash_table_insert(children, pidp, pidp); + sigprocmask(SIG_SETMASK, &oldset, NULL); + goto handle_connection_out; + } + /* child */ + signal(SIGCHLD, SIG_DFL); + signal(SIGTERM, SIG_DFL); + sigprocmask(SIG_SETMASK, &oldset, NULL); + + g_hash_table_destroy(children); + children = NULL; + for(i=0;ilen;i++) { + serve=&g_array_index(servers, SERVER, i); + close(serve->socket); + } + /* FALSE does not free the + actual data. This is required, + because the client has a + direct reference into that + data, and otherwise we get a + segfault... */ + g_array_free(servers, FALSE); + close(modernsock); + } + + msg2(LOG_INFO,"Starting to serve"); + serveconnection(client); + exit(EXIT_SUCCESS); + +handle_connection_out: + g_free(client); + close(net); +} + /** * Loop through the available servers, and serve them. Never returns. **/ @@ -2185,99 +2275,42 @@ int serveloop(GArray* servers) { max=sock>max?sock:max; } } - if(modernsock) { + if(modernsock >= 0) { FD_SET(modernsock, &mset); max=modernsock>max?modernsock:max; } for(;;) { - CLIENT *client = NULL; - pid_t *pid; - memcpy(&rset, &mset, sizeof(fd_set)); if(select(max+1, &rset, NULL, NULL, NULL)>0) { - int net = 0; - SERVER* serve=NULL; + int net; DEBUG("accept, "); - if(FD_ISSET(modernsock, &rset)) { - if((net=accept(modernsock, (struct sockaddr *) &addrin, &addrinlen)) < 0) - err("accept: %m"); + if(modernsock >= 0 && FD_ISSET(modernsock, &rset)) { + CLIENT *client; + + if((net=accept(modernsock, (struct sockaddr *) &addrin, &addrinlen)) < 0) { + err_nonfatal("accept: %m"); + continue; + } client = negotiate(net, NULL, servers, NEG_INIT | NEG_MODERN); if(!client) { err_nonfatal("negotiation failed"); close(net); - net=0; continue; } - serve = client->server; + handle_connection(servers, net, client->server, client); } - for(i=0;ilen && !net;i++) { + for(i=0; i < servers->len; i++) { + SERVER *serve; + serve=&(g_array_index(servers, SERVER, i)); if(FD_ISSET(serve->socket, &rset)) { - if ((net=accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0) - err("accept: %m"); - } - } - if(net) { - int sock_flags; - - if(serve->max_connections > 0 && - g_hash_table_size(children) >= serve->max_connections) { - msg2(LOG_INFO, "Max connections reached"); - close(net); - continue; - } - if((sock_flags = fcntl(net, F_GETFL, 0))==-1) { - err("fcntl F_GETFL"); - } - if(fcntl(net, F_SETFL, sock_flags &~O_NONBLOCK)==-1) { - err("fcntl F_SETFL ~O_NONBLOCK"); - } - if(!client) { - client = g_new0(CLIENT, 1); - client->server=serve; - client->exportsize=OFFT_MAX; - client->net=net; - client->transactionlogfd = -1; - } - set_peername(net, client); - if (!authorized_client(client)) { - msg2(LOG_INFO,"Unauthorized client") ; - close(net); - continue; - } - msg2(LOG_INFO,"Authorized client") ; - pid=g_malloc(sizeof(pid_t)); - - if (!dontfork) { - if ((*pid=fork())<0) { - msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ; - close(net); + if ((net=accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0) { + err_nonfatal("accept: %m"); continue; } - if (*pid>0) { /* parent */ - close(net); - g_hash_table_insert(children, pid, pid); - continue; - } - /* child */ - g_hash_table_destroy(children); - for(i=0;ilen;i++) { - serve=&g_array_index(servers, SERVER, i); - close(serve->socket); - } - /* FALSE does not free the - actual data. This is required, - because the client has a - direct reference into that - data, and otherwise we get a - segfault... */ - g_array_free(servers, FALSE); + handle_connection(servers, net, serve, NULL); } - - msg2(LOG_INFO,"Starting to serve"); - serveconnection(client); - exit(EXIT_SUCCESS); } } } @@ -2432,11 +2465,14 @@ void setup_servers(GArray* servers) { sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGTERM); sa.sa_flags = SA_RESTART; if(sigaction(SIGCHLD, &sa, NULL) == -1) err("sigaction: %m"); + sa.sa_handler = sigterm_handler; sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGCHLD); sa.sa_flags = SA_RESTART; if(sigaction(SIGTERM, &sa, NULL) == -1) err("sigaction: %m"); @@ -2594,9 +2630,10 @@ int main(int argc, char *argv[]) { #endif client=g_malloc(sizeof(CLIENT)); client->server=serve; - client->net=0; + client->net=-1; client->exportsize=OFFT_MAX; - set_peername(0,client); + if (set_peername(0, client)) + exit(EXIT_FAILURE); serveconnection(client); return 0; } diff --git a/simple_test b/simple_test index fcaed0f..26572a6 100755 --- a/simple_test +++ b/simple_test @@ -7,6 +7,8 @@ pidfile=${tmpdir}/nbd.pid tmpnam=${tmpdir}/nbd.dd mydir=$(dirname "`readlink -f $0`") +set -e + ulimit -c unlimited # Create a one-meg device @@ -184,6 +186,30 @@ EOF ./nbd-tester-client localhost -N export1 -i -t ${mydir}/integrityhuge-test.tr retval=$? ;; + */list) + # List exports + # This only works if we built nbd-client, which only exists on + # Linux. But hey, testing nbd-client itself isn't a bad idea, + # so here goes. + if [ `uname -s` != "Linux" ] + then + retval=77 + else + cat >${conffile} <