#include <sys/mman.h>
#include <errno.h>
#include <getopt.h>
+#include <stdarg.h>
#ifndef __GNUC__
#error I need GCC to work
return 0;
}
-int opennet(char *name, int port, int sdp) {
+int opennet(char *name, char* portstr, int sdp) {
int sock;
- char portstr[6];
struct addrinfo hints;
struct addrinfo *ai = NULL;
struct addrinfo *rp = NULL;
int e;
- snprintf(portstr, sizeof(portstr), "%d", port);
-
memset(&hints,'\0',sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
return sock;
}
-void negotiate(int sock, u64 *rsize64, u32 *flags) {
+void negotiate(int sock, u64 *rsize64, u32 *flags, char* name) {
u64 magic, size64;
+ uint16_t tmp;
char buf[256] = "\0\0\0\0\0\0\0\0\0";
printf("Negotiation: ");
if (magic != cliserv_magic)
err("Not enough cliserv_magic");
printf(".");
+ if(name) {
+ uint32_t opt;
+ uint64_t namesize;
+ uint64_t reserved = 0;
+
+ if(read(sock, &tmp, sizeof(uint16_t)) < 0) {
+ err("Failed reading flags: %m");
+ }
+ *flags = ((u32)ntohs(tmp)) << 16;
+
+ /* reserved for future use*/
+ write(sock, &reserved, sizeof(reserved));
+
+ /* Write the export name that we're after */
+ magic = ntohll(cliserv_magic);
+ write(sock, &magic, sizeof(magic));
+ opt = ntohl(NBD_OPT_EXPORT_NAME);
+ write(sock, &opt, sizeof(opt));
+ namesize = (u64)strlen(name);
+ namesize = ntohll(namesize);
+ write(sock, &namesize, sizeof(namesize));
+ write(sock, name, strlen(name));
+ }
if (read(sock, &size64, sizeof(size64)) < 0)
err("Failed/3: %m\n");
printf("size = %lu", (unsigned long)(size64));
#endif
- if (read(sock, flags, sizeof(*flags)) < 0)
- err("Failed/4: %m\n");
- *flags = ntohl(*flags);
+ if(!name) {
+ if (read(sock, flags, sizeof(*flags)) < 0)
+ err("Failed/4: %m\n");
+ *flags = ntohl(*flags);
+ } else {
+ if(read(sock, &tmp, sizeof(tmp)) < 0)
+ err("Failed/4: %m\n");
+ *flags |= (uint32_t)ntohs(tmp);
+ }
if (read(sock, &buf, 124) < 0)
err("Failed/5: %m\n");
mlockall(MCL_CURRENT | MCL_FUTURE);
}
-void usage(void) {
- fprintf(stderr, "nbd-client version %s\n", PACKAGE_VERSION);
+void usage(char* errmsg, ...) {
+ if(errmsg) {
+ char tmp[256];
+ va_list ap;
+ va_start(ap, errmsg);
+ snprintf(tmp, 256, "ERROR: %s\n\n", errmsg);
+ vfprintf(stderr, errmsg, ap);
+ va_end(ap);
+ } else {
+ fprintf(stderr, "nbd-client version %s\n", PACKAGE_VERSION);
+ }
fprintf(stderr, "Usage: nbd-client host port nbd_device [-block-size|-b block size] [-timeout|-t timeout] [-swap|-s] [-sdp|-S] [-persist|-p] [-nofork|-n]\n");
fprintf(stderr, "Or : nbd-client -d nbd_device\n");
fprintf(stderr, "Or : nbd-client -c nbd_device\n");
}
int main(int argc, char *argv[]) {
- int port=0;
+ char* port=NULL;
int sock, nbd;
int blocksize=1024;
char *hostname=NULL;
u32 flags;
int c;
int nonspecial=0;
+ char* name=NULL;
struct option long_options[] = {
{ "block-size", required_argument, NULL, 'b' },
{ "check", required_argument, NULL, 'c' },
{ "disconnect", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
+ { "name", required_argument, NULL, 'N' },
{ "nofork", no_argument, NULL, 'n' },
{ "persist", no_argument, NULL, 'p' },
{ "sdp", no_argument, NULL, 'S' },
logging();
- while((c=getopt_long_only(argc, argv, "-b:c:d:hnpSst:", long_options, NULL))>=0) {
+ while((c=getopt_long_only(argc, argv, "-b:c:d:hnN:pSst:", long_options, NULL))>=0) {
switch(c) {
case 1:
// non-option argument
optarg+=8;
goto timeout;
}
- fprintf(stderr, "ERROR: unknown option %s encountered\n", optarg);
+ usage("unknown option %s encountered", optarg);
exit(EXIT_FAILURE);
}
switch(nonspecial++) {
break;
case 1:
// port
- port = strtol(optarg, NULL, 0);
+ port = optarg;
+ if(!strtol(optarg, NULL, 0)) {
+ // not parseable as a number, assume it's the device and we have a name
+ nbddev = optarg;
+ nonspecial++;
+ } else {
+ if(name) {
+ usage("port and name specified at the same time. This is not supported.");
+ exit(EXIT_FAILURE);
+ }
+ }
break;
case 2:
// device
nbddev = optarg;
break;
default:
- fprintf(stderr, "ERROR: too many non-option arguments specified\n");
+ usage("too many non-option arguments specified");
exit(EXIT_FAILURE);
}
break;
disconnect(optarg);
exit(EXIT_SUCCESS);
case 'h':
- usage();
+ usage(NULL);
exit(EXIT_SUCCESS);
case 'n':
nofork=1;
break;
+ case 'N':
+ name=optarg;
+ if(port) {
+ usage("port and name specified at the same time. This is not supported.");
+ exit(EXIT_FAILURE);
+ }
+ port = NBD_DEFAULT_PORT;
+ break;
case 'p':
cont=1;
break;
}
}
- if(!port || !hostname || !nbddev) {
- usage();
+ if((!port && !name) || !hostname || !nbddev) {
+ usage("not enough information specified");
exit(EXIT_FAILURE);
}
sock = opennet(hostname, port, sdp);
- negotiate(sock, &size64, &flags);
+ negotiate(sock, &size64, &flags, name);
setsizes(nbd, size64, blocksize, flags);
set_timeout(nbd, timeout);
finish_sock(sock, nbd, swap);
close(sock); close(nbd);
sock = opennet(hostname, port, sdp);
nbd = open(nbddev, O_RDWR);
- negotiate(sock, &new_size, &new_flags);
+ negotiate(sock, &new_size, &new_flags, name);
if (size64 != new_size) {
err("Size of the device changed. Bye");
}
char pidftemplate[256]; /**< template to be used for the filename of the PID file */
char default_authname[] = SYSCONFDIR "/nbd-server/allow"; /**< default name of allow file */
+int modernsock=0; /**< 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
+ command-line client isn't used, gna gna) */
+char* modern_listen; /**< listenaddr value for modernsock */
+
/**
* Types of virtuatlization
**/
but before starting to serve */
gchar* postrun; /**< command that will be ran after the client
disconnects */
+ gchar* servename; /**< name of the export as selected by nbd-client */
} SERVER;
/**
make -m and -c mutually exclusive */
u32 difffilelen; /**< number of pages in difffile */
u32 *difmap; /**< see comment on the global difmap for this one */
+ gboolean modern; /**< client was negotiated using modern negotiation protocol */
} CLIENT;
/**
CFILE_VALUE_INVALID, /**< A value is syntactically invalid */
CFILE_VALUE_UNSUPPORTED,/**< A value is not supported in this build */
CFILE_PROGERR, /**< Programmer error */
- CFILE_NO_EXPORTS /**< A config file was specified that does not
+ CFILE_NO_EXPORTS, /**< A config file was specified that does not
define any exports */
+ CFILE_INCORRECT_PORT, /**< The reserved port was specified for an
+ old-style export. */
} CFILE_ERRORS;
/**
* @param a server array
* @return 0 success, -1 error
*/
-int append_serve(SERVER *s, GArray *a)
-{
+int append_serve(SERVER *s, GArray *a) {
SERVER *ns = NULL;
struct addrinfo hints;
struct addrinfo *ai = NULL;
{ "listenaddr", FALSE, PARAM_STRING, NULL, 0 },
};
const int lp_size=sizeof(lp)/sizeof(PARAM);
+ int do_oldstyle;
PARAM gp[] = {
{ "user", FALSE, PARAM_STRING, &runuser, 0 },
{ "group", FALSE, PARAM_STRING, &rungroup, 0 },
+ { "oldstyle", FALSE, PARAM_BOOL, &do_oldstyle, 1 },
+ { "listenaddr", FALSE, PARAM_STRING, &modern_listen, 0 },
};
PARAM* p=gp;
int p_size=sizeof(gp)/sizeof(PARAM);
}
break;
}
+ if(!strcmp(p[j].paramname, "port") && !strcmp(p[j].target, NBD_DEFAULT_PORT)) {
+ g_set_error(e, errdomain, CFILE_INCORRECT_PORT, "Config file specifies default port for oldstyle export");
+ g_key_file_free(cfile);
+ return NULL;
+ }
if(err) {
if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
if(!p[j].required) {
/* Don't append values for the [generic] group */
if(i>0) {
s.socket_family = AF_UNSPEC;
+ s.servename = groups[i];
append_serve(&s, retval);
+ } else {
+ if(!do_oldstyle) {
+ lp[1].required = 0;
+ }
}
#ifndef WITH_SDP
if(s.flags & F_SDP) {
*
* @param client The client we're negotiating with.
**/
-void negotiate(CLIENT *client) {
+CLIENT* negotiate(int net, CLIENT *client, GArray* servers) {
char zeros[128];
- u64 size_host;
- u32 flags = NBD_FLAG_HAS_FLAGS;
+ uint64_t size_host;
+ uint32_t flags = NBD_FLAG_HAS_FLAGS;
+ uint16_t smallflags = 0;
memset(zeros, '\0', sizeof(zeros));
- if (write(client->net, INIT_PASSWD, 8) < 0)
- err("Negotiation failed: %m");
- cliserv_magic = htonll(cliserv_magic);
- if (write(client->net, &cliserv_magic, sizeof(cliserv_magic)) < 0)
- err("Negotiation failed: %m");
+ if(!client || !client->modern) {
+ if (write(net, INIT_PASSWD, 8) < 0) {
+ err_nonfatal("Negotiation failed: %m");
+ if(client)
+ exit(EXIT_FAILURE);
+ }
+ cliserv_magic = htonll(cliserv_magic);
+ if (write(net, &cliserv_magic, sizeof(cliserv_magic)) < 0) {
+ err_nonfatal("Negotiation failed: %m");
+ if(client)
+ exit(EXIT_FAILURE);
+ }
+ }
+ if(!client) {
+ uint64_t reserved;
+ uint64_t magic;
+ uint32_t opt;
+ uint64_t namelen;
+ char* name;
+ int i;
+
+ if(!servers)
+ err("programmer error");
+ write(net, &smallflags, sizeof(uint16_t));
+ read(net, &reserved, sizeof(reserved));
+ read(net, &magic, sizeof(magic));
+ magic = ntohll(magic);
+ if(magic != cliserv_magic) {
+ close(net);
+ return NULL;
+ }
+ read(net, &opt, sizeof(opt));
+ opt = ntohl(opt);
+ if(opt != NBD_OPT_EXPORT_NAME) {
+ close(net);
+ return NULL;
+ }
+ read(net, &namelen, sizeof(namelen));
+ namelen = ntohll(namelen);
+ name = malloc(namelen+1);
+ name[namelen+1]=0;
+ read(net, &name, namelen);
+ for(i=0; i<servers->len; i++) {
+ SERVER* serve = &(g_array_index(servers, SERVER, i));
+ if(!strcmp(serve->servename, name)) {
+ CLIENT* client = g_new0(CLIENT, 1);
+ client->server = serve;
+ client->exportsize = OFFT_MAX;
+ client->net = net;
+ client->modern = TRUE;
+ return client;
+ }
+ }
+ }
size_host = htonll((u64)(client->exportsize));
- if (write(client->net, &size_host, 8) < 0)
+ if (write(net, &size_host, 8) < 0)
err("Negotiation failed: %m");
if (client->server->flags & F_READONLY)
flags |= NBD_FLAG_READ_ONLY;
err("Negotiation failed: %m");
if (write(client->net, zeros, 124) < 0)
err("Negotiation failed: %m");
+ return NULL;
}
/** sending macro. */
#ifdef DODBG
int i = 0;
#endif
- negotiate(client);
+ negotiate(client->net, client, NULL);
DEBUG("Entering request loop!\n");
reply.magic = htonl(NBD_REPLY_MAGIC);
reply.error = 0;
int serveloop(GArray* servers) {
struct sockaddr_storage addrin;
socklen_t addrinlen=sizeof(addrin);
- SERVER *serve;
int i;
int max;
int sock;
FD_SET(sock, &mset);
max=sock>max?sock:max;
}
+ if(modernsock) {
+ FD_SET(modernsock, &mset);
+ max=modernsock>max?sock:max;
+ }
for(;;) {
- CLIENT *client;
- int net;
+ 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;
+
DEBUG("accept, ");
- for(i=0;i<servers->len;i++) {
+ if(FD_ISSET(modernsock, &rset)) {
+ if((net=accept(modernsock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
+ err("accept: %m");
+ client = negotiate(net, NULL, servers);
+ if(!client) {
+ err_nonfatal("negotiation failed");
+ close(net);
+ }
+ }
+ for(i=0;i<servers->len && !net;i++) {
serve=&(g_array_index(servers, SERVER, i));
if(FD_ISSET(serve->socket, &rset)) {
- int sock_flags;
if ((net=accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0)
err("accept: %m");
+ }
+ }
+ if(net) {
+ int sock_flags;
- 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");
- }
- client = g_malloc(sizeof(CLIENT));
+ 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;
- 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));
+ }
+ 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));
#ifndef NOFORK
- if ((*pid=fork())<0) {
- msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
- close(net);
- continue;
- }
- if (*pid>0) { /* parent */
- close(net);
- g_hash_table_insert(children, pid, pid);
- continue;
- }
- /* child */
- g_hash_table_destroy(children);
- for(i=0;i<servers->len;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);
-#endif // NOFORK
- msg2(LOG_INFO,"Starting to serve");
- serveconnection(client);
- exit(EXIT_SUCCESS);
+ if ((*pid=fork())<0) {
+ msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
+ close(net);
+ continue;
}
+ if (*pid>0) { /* parent */
+ close(net);
+ g_hash_table_insert(children, pid, pid);
+ continue;
+ }
+ /* child */
+ g_hash_table_destroy(children);
+ for(i=0;i<servers->len;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);
+#endif // NOFORK
+ msg2(LOG_INFO,"Starting to serve");
+ serveconnection(client);
+ exit(EXIT_SUCCESS);
}
}
}
}
+void dosockopts(int socket) {
+#ifndef sun
+ int yes=1;
+#else
+ char yes='1';
+#endif /* sun */
+ int sock_flags;
+
+ /* lose the pesky "Address already in use" error message */
+ if (setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
+ err("setsockopt SO_REUSEADDR");
+ }
+ if (setsockopt(socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
+ err("setsockopt SO_KEEPALIVE");
+ }
+
+ /* make the listening socket non-blocking */
+ if ((sock_flags = fcntl(socket, F_GETFL, 0)) == -1) {
+ err("fcntl F_GETFL");
+ }
+ if (fcntl(socket, F_SETFL, sock_flags | O_NONBLOCK) == -1) {
+ err("fcntl F_SETFL O_NONBLOCK");
+ }
+}
+
/**
* Connect a server's socket.
*
* @param serve the server we want to connect.
**/
-void setup_serve(SERVER *serve) {
+int setup_serve(SERVER *serve) {
struct addrinfo hints;
struct addrinfo *ai = NULL;
- struct sigaction sa;
- int sock_flags;
-#ifndef sun
- int yes=1;
-#else
- char yes='1';
-#endif /* sun */
gchar *port = NULL;
int e;
port = g_strdup_printf ("%d", serve->port);
if (port == NULL)
- return;
+ return 0;
e = getaddrinfo(serve->listenaddr,port,&hints,&ai);
if ((serve->socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
err("socket: %m");
- /* lose the pesky "Address already in use" error message */
- if (setsockopt(serve->socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
- err("setsockopt SO_REUSEADDR");
- }
- if (setsockopt(serve->socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
- err("setsockopt SO_KEEPALIVE");
- }
-
- /* make the listening socket non-blocking */
- if ((sock_flags = fcntl(serve->socket, F_GETFL, 0)) == -1) {
- err("fcntl F_GETFL");
- }
- if (fcntl(serve->socket, F_SETFL, sock_flags | O_NONBLOCK) == -1) {
- err("fcntl F_SETFL O_NONBLOCK");
- }
+ dosockopts(serve->socket);
DEBUG("Waiting for connections... bind, ");
e = bind(serve->socket, ai->ai_addr, ai->ai_addrlen);
err("listen: %m");
freeaddrinfo (ai);
+ if(serve->servename) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
- sa.sa_handler = sigchld_handler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- if(sigaction(SIGCHLD, &sa, NULL) == -1)
- err("sigaction: %m");
- sa.sa_handler = sigterm_handler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- if(sigaction(SIGTERM, &sa, NULL) == -1)
- err("sigaction: %m");
+void open_modern(void) {
+ struct addrinfo hints;
+ struct addrinfo* ai = NULL;
+ struct sock_flags;
+ int e;
+
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_protocol = IPPROTO_TCP;
+ e = getaddrinfo(modern_listen, NBD_DEFAULT_PORT, &hints, &ai);
+ if(e != 0) {
+ fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(e));
+ exit(EXIT_FAILURE);
+ }
+ if((modernsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))<0) {
+ err("socket: %m");
+ }
+
+ dosockopts(modernsock);
+
+ if(bind(modernsock, ai->ai_addr, ai->ai_addrlen)) {
+ err("bind: %m");
+ }
+ if(listen(modernsock, 10) <0) {
+ err("listen: %m");
+ }
+
+ freeaddrinfo(ai);
}
/**
**/
void setup_servers(GArray* servers) {
int i;
+ struct sigaction sa;
+ int want_modern=0;
for(i=0;i<servers->len;i++) {
- setup_serve(&(g_array_index(servers, SERVER, i)));
+ want_modern |= setup_serve(&(g_array_index(servers, SERVER, i)));
+ }
+ if(want_modern) {
+ open_modern();
}
children=g_hash_table_new_full(g_int_hash, g_int_equal, NULL, destroy_pid_t);
+
+ sa.sa_handler = sigchld_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if(sigaction(SIGCHLD, &sa, NULL) == -1)
+ err("sigaction: %m");
+ sa.sa_handler = sigterm_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if(sigaction(SIGTERM, &sa, NULL) == -1)
+ err("sigaction: %m");
}
/**