/* Includes LFS defines, which defines behaviours of some of the following
* headers, so must come before those */
-#include "config.h"
#include "lfs.h"
#include <sys/types.h>
*
* Actually, do we need this at all? Can't we just say '0 is autodetect', and
* live with it? Or better yet, use an extra flag, or so?
+ * Answer: yes, we need it, as the hunksize is defined to this when the
+ * multiple file thingy isn't used.
*/
#define OFFT_MAX (((((off_t)1)<<((sizeof(off_t)-1)*8))-1)<<7)+127
#define LINELEN 256 /**< Size of static buffer used to read the
authorization file (yuck) */
#define BUFSIZE (1024*1024) /**< Size of buffer that can hold requests */
#define GIGA (1*1024*1024*1024) /**< 1 Gigabyte. Used as hunksize when doing
- the multiple file thingy */
+ the multiple file thingy. @todo: make this a
+ configuration option. */
#define DIFFPAGESIZE 4096 /**< diff file uses those chunks */
#define F_READONLY 1 /**< flag to tell us a file is readonly */
#define F_MULTIFILE 2 /**< flag to tell us a file is exported using -m */
-#define F_COPYONWRITE 4 /**< flag to tell us a file is exported using copyonwrite */
+#define F_COPYONWRITE 4 /**< flag to tell us a file is exported using
+ copyonwrite */
#define F_AUTOREADONLY 8 /**< flag to tell us a file is set to autoreadonly */
-//char difffilename[1024]; /**< filename of the copy-on-write file. Doesn't belong here! */
-//unsigned int timeout = 0; /**< disconnect timeout */
-//int autoreadonly = 0; /**< 1 = switch to readonly if opening readwrite isn't
-// possible */
-//char *auth_file="nbd_server.allow"; /**< authorization file */
-//char exportname2[1024]; /**< File I'm exporting, with virtualhost resolved */
-//off_t lastpoint = (off_t)-1; /**< keep track of where we are in the file, to
-// avoid an lseek if possible */
-//char pagebuf[DIFFPAGESIZE]; /**< when doing copyonwrite, this is
-// used as a temporary buffer to store
-// the exported block in. @todo this is
-// a great example of namespace
-// pollution. Throw it out. */
-//unsigned int port; /**< Port I'm listening at */
-//char *exportname; /**< File I'm exporting */
-//off_t exportsize = OFFT_MAX; /**< length of file I'm exporting */
-//off_t hunksize = OFFT_MAX; /**< size of each exported file in case of -m */
-//int flags = 0; /**< flags associated with this exported file */
-//int export[1024];/**< array of filedescriptors of exported files; only first is
-// used unless -m option is activated */
-//int difffile=-1; /**< filedescriptor for copyonwrite file */
-//u32 difffilelen=0 ; /**< number of pages in difffile */
-//u32 *difmap=NULL ; /**< Determine whether a block is in the original file
-// (difmap[block]==-1) or in the copyonwrite file (in which
-// case it contains the offset where it is to be found in the
-// copyonwrite file). @todo the kernel knows about sparse
-// files, we should use those instead. Should also be off_t
-// instead of u32; copyonwrite is probably broken wrt LFS */
-char clientname[256] ;
-int child_arraysize=DEFAULT_CHILD_ARRAY; /**< number of available slots for
- child array */
-pid_t *children; /**< child array */
+GHashTable *children;
char pidfname[256]; /**< name of our PID file */
+char default_authname[] = "/etc/nbd_server.allow"; /**< default name of allow file */
/**
* Variables associated with a server.
int flags; /**< flags associated with this exported file */
unsigned int timeout;/**< how long a connection may be idle
(0=forever) */
+ int socket; /**< The socket of this server. */
} SERVER;
/**
int export[1024]; /**< array of filedescriptors of exported files;
only the first is actually used unless we're
doing the multiple file option */
- int lastpoint; /**< For keeping track of where we are in a file.
- This code is BUGGY currently, at least in
- combination with the multiple file option. */
int net; /**< The actual client socket */
SERVER *server; /**< The server this client is getting data from */
char* difffilename; /**< filename of the copy-on-write file, if any */
int difffile; /**< filedescriptor of copyonwrite file. @todo
- shouldn't this be an array too? (cfr
- nbd_server_opts::export) Or make -m and -c
- mutually exclusive */
+ shouldn't this be an array too? (cfr export) Or
+ make -m and -c mutually exclusive */
u32 difffilelen; /**< number of pages in difffile */
u32 *difmap; /**< see comment on the global difmap for this one */
} CLIENT;
* @param buf a buffer
* @param len the number of bytes to be read
**/
-inline void readit(int f, void *buf, size_t len)
-{
+inline void readit(int f, void *buf, size_t len) {
ssize_t res;
while (len > 0) {
DEBUG("*");
* @param buf a buffer containing data
* @param len the number of bytes to be written
**/
-inline void writeit(int f, void *buf, size_t len)
-{
+inline void writeit(int f, void *buf, size_t len) {
ssize_t res;
while (len > 0) {
DEBUG("+");
* function so that we can call it from multiple places
*/
void usage() {
- printf("This is nbd-server version " VERSION "\n");
- printf("Usage: port file_to_export [size][kKmM] [-r] [-m] [-c] [-a timeout_sec]\n"
- " -r read only\n"
- " -m multiple file\n"
- " -c copy on write\n"
- " -l file with list of hosts that are allowed to connect.\n"
- " -a maximum idle seconds, terminates when idle time exceeded\n"
- " if port is set to 0, stdin is used (for running from inetd)\n"
- " if file_to_export contains '%%s', it is substituted with IP\n"
- " address of machine trying to connect\n" );
+ printf("This is nbd-server version " VERSION "\n");
+ printf("Usage: port file_to_export [size][kKmM] [-l authorize_file] [-r] [-m] [-c] [-a timeout_sec]\n"
+ "\t-r|--read-only\t\tread only\n"
+ "\t-m|--multi-file\t\tmultiple file\n"
+ "\t-c|--copy-on-write\tcopy on write\n"
+ "\t-l|--authorize-file\tfile with list of hosts that are allowed to\n\t\t\t\tconnect.\n"
+ "\t-a|--idle-time\t\tmaximum idle seconds; server terminates when\n\t\t\t\tidle time exceeded\n\n"
+ "\tif port is set to 0, stdin is used (for running from inetd)\n"
+ "\tif file_to_export contains '%%s', it is substituted with the IP\n"
+ "\t\taddress of the machine trying to connect\n" );
}
/**
case 'm':
serve->flags |= F_MULTIFILE;
serve->hunksize = 1*GIGA;
+ serve->authname = default_authname;
break;
case 'c':
serve->flags |=F_COPYONWRITE;
* @param s the signal we're handling (must be SIGCHLD, or something
* is severely wrong)
**/
-void sigchld_handler(int s)
-{
+void sigchld_handler(int s) {
int* status=NULL;
- int i;
- char buf[80];
+ int* i;
pid_t pid;
while((pid=wait(status)) > 0) {
if(WIFEXITED(status)) {
msg3(LOG_INFO, "Child exited with %d", WEXITSTATUS(status));
}
- for(i=0;children[i]!=pid&&i<child_arraysize;i++);
- if(i>=child_arraysize) {
+ i=g_hash_table_lookup(children, &pid);
+ if(!i) {
msg3(LOG_INFO, "SIGCHLD received for an unknown child with PID %ld", (long)pid);
} else {
- children[i]=(pid_t)0;
DEBUG2("Removing %d from the list of children", pid);
+ g_hash_table_remove(children, &pid);
}
}
}
/**
+ * Kill a child. Called from sigterm_handler::g_hash_table_foreach.
+ *
+ * @param key the key
+ * @param value the value corresponding to the above key
+ * @param user_data a pointer which we always set to 1, so that we know what
+ * will happen next.
+ **/
+void killchild(gpointer key, gpointer value, gpointer user_data) {
+ pid_t *pid=value;
+ int *parent=user_data;
+
+ kill(*pid, SIGTERM);
+ *parent=1;
+}
+
+/**
* Handle SIGTERM and dispatch it to our children
* @param s the signal we're handling (must be SIGTERM, or something
* is severely wrong).
**/
void sigterm_handler(int s) {
- int i;
int parent=0;
- for(i=0;i<child_arraysize;i++) {
- if(children[i]) {
- kill(children[i], s);
- parent=1;
- }
- }
+ g_hash_table_foreach(children, killchild, &parent);
if(parent) {
unlink(pidfname);
* @return the size of the file, or OFFT_MAX if detection was
* impossible.
**/
-off_t size_autodetect(int export)
-{
+off_t size_autodetect(int export) {
off_t es;
u32 es32;
struct stat stat_buf;
}
/**
- * Seek to a position in a file, unless we're already there.
+ * seek to a position in a file, with error handling.
* @param handle a filedescriptor
* @param a position to seek to
- * @param client the client we're working for
+ * @todo get rid of this; lastpoint is a global variable right now, but it
+ * shouldn't be. If we pass it on as a parameter, that makes things a *lot*
+ * easier.
**/
-void maybeseek(int handle, off_t a, CLIENT* client) {
- if (a < 0 || a > client->exportsize) {
- err("Can not happen\n");
- }
- if (client->lastpoint != a) {
- if (lseek(handle, a, SEEK_SET) < 0) {
- err("Can not seek locally!\n");
- }
- client->lastpoint = a;
- } else {
- DEBUG("S");
+void myseek(int handle,off_t a) {
+ if (lseek(handle, a, SEEK_SET) < 0) {
+ err("Can not seek locally!\n");
}
}
* @param len The length of buf
* @return The number of bytes actually written, or -1 in case of an error
**/
-int rawexpwrite(off_t a, char *buf, size_t len, CLIENT *client)
-{
+int rawexpwrite(off_t a, char *buf, size_t len, CLIENT *client) {
ssize_t res;
- maybeseek(client->export[a/client->server->hunksize],
- a%client->server->hunksize, client);
- res = write(client->export[a/client->server->hunksize], buf, len);
+ myseek(client->export[(int)a/client->server->hunksize],
+ a%client->server->hunksize);
+ res = write(client->export[(int)((off_t)a/(off_t)(client->server->hunksize))], buf, len);
return (res < 0 || (size_t)res != len);
}
/**
- * seek to a position in a file, no matter what. Used when using maybeseek is a
- * bad idea (for instance, because we're reading the copyonwrite file instead
- * of the exported file).
- * @param handle a filedescriptor
- * @param a position to seek to
- * @todo get rid of this; lastpoint is a global variable right now, but it
- * shouldn't be. If we pass it on as a parameter, that makes things a *lot*
- * easier.
- **/
-void myseek(int handle,off_t a) {
- if (lseek(handle, a, SEEK_SET) < 0) {
- err("Can not seek locally!\n");
- }
-}
-
-/**
* Read an amount of bytes at a given offset from the right file. This
* abstracts the read-side of the multiple files option.
*
* @return The number of bytes actually read, or -1 in case of an
* error.
**/
-int rawexpread(off_t a, char *buf, size_t len, CLIENT *client)
-{
+int rawexpread(off_t a, char *buf, size_t len, CLIENT *client) {
ssize_t res;
- maybeseek(client->export[a/client->server->hunksize],
- a%client->server->hunksize, client);
- res = read(client->export[a/client->server->hunksize], buf, len);
+ myseek(client->export[(int)a/client->server->hunksize],
+ a%client->server->hunksize);
+ res = read(client->export[(int)a/client->server->hunksize], buf, len);
return (res < 0 || (size_t)res != len);
}
* @param len The size of buf
* @return The number of bytes actually read, or -1 in case of an error
**/
-int expread(off_t a, char *buf, size_t len, CLIENT *client)
-{
+int expread(off_t a, char *buf, size_t len, CLIENT *client) {
off_t rdlen, offset;
off_t mapcnt, mapl, maph, pagestart;
-
+
if (!(client->server->flags & F_COPYONWRITE))
return rawexpread(a, buf, len, client);
DEBUG3("Asked to read %d bytes at %Lu.\n", len, (unsigned long long)a);
/** sending macro. */
#define SEND(net,reply) writeit( net, &reply, sizeof( reply ));
/** error macro. */
-#define ERROR(client,reply) { reply.error = htonl(-1); SEND(client->net,reply); reply.error = 0; client->lastpoint = -1; }
+#define ERROR(client,reply) { reply.error = htonl(-1); SEND(client->net,reply); reply.error = 0; }
/**
* Serve a file to a single client.
*
* @param net A network socket, connected to an nbd client
* @return never
**/
-int mainloop(CLIENT *client)
-{
+int mainloop(CLIENT *client) {
struct nbd_request request;
struct nbd_reply reply;
+ gboolean go_on=TRUE;
#ifdef DODBG
int i = 0;
#endif
DEBUG("Entering request loop!\n");
reply.magic = htonl(NBD_REPLY_MAGIC);
reply.error = 0;
- while (1) {
+ while (go_on) {
char buf[BUFSIZE];
size_t len;
#ifdef DODBG
request.from = ntohll(request.from);
request.type = ntohl(request.type);
- if (request.type==NBD_CMD_DISC) { /* Disconnect request */
- if (client->difmap) free(client->difmap) ;
- if (client->difffile>=0) {
- close(client->difffile) ; unlink(client->difffilename) ; }
- err("Disconnect request received.") ;
+ if (request.type==NBD_CMD_DISC) {
+ msg2(LOG_INFO, "Disconnect request received.");
+ if (client->difmap) g_free(client->difmap) ;
+ if (client->difffile>=0) {
+ close(client->difffile);
+ unlink(client->difffilename);
+ }
+ go_on=FALSE;
+ continue;
}
len = ntohl(request.len);
#endif
memcpy(reply.handle, request.handle, sizeof(reply.handle));
if ((request.from + len) > (OFFT_MAX)) {
- DEBUG("[Number too large!]");
- ERROR(client, reply);
- continue;
+ DEBUG("[Number too large!]");
+ ERROR(client, reply);
+ continue;
}
if (((ssize_t)((off_t)request.from + len) > client->exportsize) ||
continue;
}
- if (request.type==1) { /* WRITE */
+ if (request.type==NBD_CMD_WRITE) {
DEBUG("wr: net->buf, ");
readit(client->net, buf, len);
DEBUG("buf->exp, ");
ERROR(client, reply);
continue;
}
- client->lastpoint += len;
SEND(client->net, reply);
DEBUG("OK!\n");
continue;
DEBUG("exp->buf, ");
if (expread(request.from, buf + sizeof(struct nbd_reply), len, client)) {
- client->lastpoint = -1;
DEBUG("Read failed: %m");
ERROR(client, reply);
continue;
}
- client->lastpoint += len;
DEBUG("buf->net, ");
memcpy(buf, &reply, sizeof(struct nbd_reply));
writeit(client->net, buf, len + sizeof(struct nbd_reply));
DEBUG("OK!\n");
}
+ return 0;
}
/**
off_t i;
for (i=0; i<client->exportsize; i+=client->server->hunksize) {
- char tmpname[1024];
+ gchar *tmpname;
if(client->server->flags & F_MULTIFILE) {
- snprintf(tmpname, 1024, "%s.%d", client->exportname,
+ tmpname=g_strdup_printf("%s.%d", client->exportname,
(int)(i/client->server->hunksize));
} else {
- strncpy(tmpname, client->exportname, 1024);
+ tmpname=g_strdup(client->exportname);
}
- tmpname[1023]='\0';
DEBUG2( "Opening %s\n", tmpname );
if ((client->export[ i/ client->server->hunksize ] =
open(tmpname, (client->server->flags &
open(tmpname, O_RDONLY)) == -1)
err("Could not open exported file: %m");
}
+ g_free(tmpname);
}
if (client->server->flags & F_COPYONWRITE) {
/**
* Serve a connection.
*
- * @todo allow for multithreading, perhaps use libevent.
+ * @todo allow for multithreading, perhaps use libevent. Not just yet, though;
+ * follow the road map.
*
* @param net A network socket connected to an nbd client
**/
void serveconnection(CLIENT *client) {
- char buf[80];
splitexport(client);
+
if (!client->server->expected_size) {
client->exportsize = size_autodetect(client->export[0]);
} else {
}
/**
- * Find the name of the file we have to serve. This will use snprintf()
+ * Find the name of the file we have to serve. This will use g_strdup_printf
* to put the IP address of the client inside a filename containing
- * "%s". That name is then written to exportname2
+ * "%s". That name is then written to client->exportname.
*
* @param net A socket connected to an nbd client
* @param client information about the client. The IP address in human-readable
msg4(LOG_INFO, "connect from %s, assigned file is %s",
peername, client->exportname);
- strncpy(clientname,peername,255) ;
+ client->clientname=g_strdup(peername);
}
/**
- * Connect the socket, and start to serve. This function will fork()
- * if a connection from an authorized client is received, and will
- * start mainloop().
- *
- * @todo modularize this giant beast. Preferably with a chainsaw. Also,
- * it has no business starting mainloop(); it should connect, and be
- * done with it.
- *
- * @param port the port where we will listen
+ * Destroy a pid_t*
+ * @param data a pointer to pid_t which should be freed
**/
-void connectme(SERVER* serve) {
- struct sockaddr_in addrin;
- struct sigaction sa;
- int addrinlen = sizeof(addrin);
- int net, sock, newpid, i;
-#ifndef sun
- int yes=1;
-#else
- char yes='1';
-#endif /* sun */
-#ifndef NODAEMON
-#ifndef NOFORK
+void destroy_pid_t(gpointer data) {
+ g_free(data);
+}
+
+/**
+ * Go daemon (unless we specified at compile time that we didn't want this)
+ * @param serve the first server of our configuration. If its port is zero,
+ * then do not daemonize, because we're doing inetd then.
+ **/
+#if !defined(NODAEMON) && !defined(NOFORK)
+void daemonize(SERVER* serve) {
FILE*pidf;
if((serve->port)) {
fprintf(stderr, "Not fatal; continuing");
}
}
-#endif /* NOFORK */
-#endif /* NODAEMON */
+}
+#else
+#define daemonize(serve)
+#endif /* !defined(NODAEMON) && !defined(NOFORK) */
+
+/**
+ * Connect a server's socket.
+ *
+ * @todo modularize this giant beast. Preferably with a chainsaw. Also,
+ * it has no business starting mainloop(), through serveconnection(); it
+ * should connect, and be done with it.
+ *
+ * @param serve the server we want to connect.
+ **/
+void setup_serve(SERVER* serve) {
+ struct sockaddr_in addrin;
+ struct sigaction sa;
+ int addrinlen = sizeof(addrin);
+#ifndef sun
+ int yes=1;
+#else
+ char yes='1';
+#endif /* sun */
- if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ if ((serve->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
err("socket: %m");
/* lose the pesky "Address already in use" error message */
- if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
+ if (setsockopt(serve->socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
err("setsockopt SO_REUSEADDR");
}
- if (setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
+ if (setsockopt(serve->socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) {
err("setsockopt SO_KEEPALIVE");
}
addrin.sin_family = AF_INET;
addrin.sin_port = htons(serve->port);
addrin.sin_addr.s_addr = 0;
- if (bind(sock, (struct sockaddr *) &addrin, addrinlen) < 0)
+ if (bind(serve->socket, (struct sockaddr *) &addrin, addrinlen) < 0)
err("bind: %m");
DEBUG("listen, ");
- if (listen(sock, 1) < 0)
+ if (listen(serve->socket, 1) < 0)
err("listen: %m");
- DEBUG("accept, ");
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_flags = SA_RESTART;
if(sigaction(SIGTERM, &sa, NULL) == -1)
err("sigaction: %m");
- children=g_malloc(sizeof(pid_t)*child_arraysize);
- memset(children, 0, sizeof(pid_t)*DEFAULT_CHILD_ARRAY);
- for(;;) { /* infinite loop */
+ children=g_hash_table_new_full(g_int_hash, g_int_equal, NULL, destroy_pid_t);
+}
+
+/**
+ * Loop through the available servers, and serve them.
+ *
+ * Actually, right now we only handle one server. Will change that for
+ * 2.9.
+ **/
+int serveloop(SERVER* serve) {
+ struct sockaddr_in addrin;
+ socklen_t addrinlen=sizeof(addrin);
+ for(;;) {
CLIENT *client;
- if ((net = accept(sock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
+ int net;
+ pid_t *pid;
+
+ DEBUG("accept, ");
+ if ((net = accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0)
err("accept: %m");
client = g_malloc(sizeof(CLIENT));
continue ;
}
msg2(LOG_INFO,"Authorized client") ;
- for(i=0;children[i]&&i<child_arraysize;i++);
- if(i>=child_arraysize) {
- pid_t*ptr;
-
- ptr=realloc(children, sizeof(pid_t)*child_arraysize);
- if(ptr) {
- children=ptr;
- memset(children+child_arraysize, 0, sizeof(pid_t)*DEFAULT_CHILD_ARRAY);
- i=child_arraysize+1;
- child_arraysize+=DEFAULT_CHILD_ARRAY;
- } else {
- msg2(LOG_INFO,"Not enough memory to store child PID");
- close(net);
- continue;
- }
- }
+ pid=g_malloc(sizeof(pid_t));
#ifndef NOFORK
- if ((children[i]=fork())<0) {
+ if ((*pid=fork())<0) {
msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
close(net) ;
continue ;
}
- if (children[i]>0) { /* parent */
- close(net) ; continue ; }
+ if (*pid>0) { /* parent */
+ close(net);
+ g_hash_table_insert(children, pid, pid);
+ continue;
+ }
/* child */
- realloc(children,0);
- child_arraysize=0;
- close(sock) ;
+ g_hash_table_destroy(children);
+ close(serve->socket) ;
#endif // NOFORK
msg2(LOG_INFO,"Starting to serve") ;
serveconnection(client);
**/
int main(int argc, char *argv[]) {
SERVER* serve;
+ GArray* servers;
+
if (sizeof( struct nbd_request )!=28) {
fprintf(stderr,"Bad size of structure. Alignment problems?\n");
exit(-1) ;
}
+
logging();
serve=cmdline(argc, argv);
-
+ servers=g_array_new(TRUE, FALSE, sizeof(SERVER*));
+
if (!(serve->port)) {
CLIENT *client;
#ifndef ISSERVER
- /* You really should define ISSERVER if you're going to use inetd
- * mode, but if you don't, closing stdout and stderr (which inetd
- * had connected to the client socket) will let it work. */
+ /* You really should define ISSERVER if you're going to use
+ * inetd mode, but if you don't, closing stdout and stderr
+ * (which inetd had connected to the client socket) will let it
+ * work. */
close(1);
close(2);
open("/dev/null", O_WRONLY);
client->net=0;
client->exportsize=OFFT_MAX;
set_peername(0,client);
- serveconnection(0);
+ serveconnection(client);
return 0;
}
- connectme(serve); /* serve infinitely */
+ daemonize(serve);
+ setup_serve(serve);
+ serveloop(serve);
return 0 ;
}
-