r189: Fix crashes, thanks (again) to Phillip
[nbd.git] / nbd-server.c
index 004e266..77b3e0d 100644 (file)
@@ -58,6 +58,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/select.h>                /* select */
 #include <sys/wait.h>          /* wait */
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 /** Where our config file actually is */
 gchar* config_file_pos;
 
-/** how much space for child PIDs we have by default. Dynamically
-   allocated, and will be realloc()ed if out of space, so this should
-   probably be fair for most situations. */
-#define DEFAULT_CHILD_ARRAY 256
-
 /** Logging macros, now nothing goes to syslog unless you say ISSERVER */
 #ifdef ISSERVER
 #define msg2(a,b) syslog(a,b)
@@ -128,18 +124,10 @@ gchar* config_file_pos;
 #define PACKAGE_VERSION ""
 #endif
 /**
- * The highest value a variable of type off_t can reach.
+ * The highest value a variable of type off_t can reach. This is a signed
+ * integer, so set all bits except for the leftmost one.
  **/
-/* This is starting to get ugly. If someone knows a better way to find
- * the maximum value of a signed type *without* relying on overflow
- * (doing so breaks on 64bit architectures), that would be nice.
- *
- * 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 OFFT_MAX ~((off_t)1<<(sizeof(off_t)*8-1))
 #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 */
@@ -287,10 +275,11 @@ inline void writeit(int f, void *buf, size_t len) {
  */
 void usage() {
        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"
+       printf("Usage: port file_to_export [size][kKmM] [-l authorize_file] [-r] [-m] [-c] [-a timeout_sec] [-C configuration file]\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-C|--config-file\tspecify an alternat configuration file\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"
@@ -392,8 +381,8 @@ SERVER* cmdline(int argc, char *argv[]) {
        /* What's left: the port to export, the name of the to be exported
         * file, and, optionally, the size of the file, in that order. */
        if(nonspecial<2) {
-               usage();
-               exit(EXIT_FAILURE);
+               g_free(serve);
+               serve=NULL;
        }
        return serve;
 }
@@ -434,6 +423,8 @@ void remove_server(gpointer s) {
  *     e is set appropriately
  **/
 GArray* parse_cfile(gchar* f, GError** e) {
+       const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
+       const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
        SERVER s;
        PARAM p[] = {
                { "exportname", TRUE,   PARAM_STRING,   NULL, 0 },
@@ -445,14 +436,17 @@ GArray* parse_cfile(gchar* f, GError** e) {
                { "multifile",  FALSE,  PARAM_BOOL,     NULL, F_MULTIFILE },
                { "copyonwrite", FALSE, PARAM_BOOL,     NULL, F_COPYONWRITE },
        };
+       const int p_size=8;
        GKeyFile *cfile;
        GError *err = NULL;
+       const char *err_msg=NULL;
        GQuark errdomain;
        GArray *retval=NULL;
        gchar **groups;
        gboolean value;
        gint i,j;
 
+       memset(&s, '\0', sizeof(SERVER));
        errdomain = g_quark_from_string("parse_cfile");
        cfile = g_key_file_new();
        retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
@@ -468,50 +462,63 @@ GArray* parse_cfile(gchar* f, GError** e) {
                return NULL;
        }
        groups = g_key_file_get_groups(cfile, NULL);
-       for(i=0;groups[i];i++) {
+       for(i=1;groups[i];i++) {
                p[0].target=&(s.exportname);
                p[1].target=&(s.port);
                p[2].target=&(s.authname);
                p[3].target=&(s.timeout);
                p[4].target=&(s.expected_size);
-               p[5].target=p[6].target=p[7].target=p[8].target=&(s.flags);
-               for(j=0;j<9;j++) {
+               p[5].target=p[6].target=p[7].target=&(s.flags);
+               for(j=0;j<p_size;j++) {
                        g_assert(p[j].target != NULL);
                        g_assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL);
                        switch(p[j].ptype) {
                                case PARAM_INT:
-                                       *((gint*)p[j].target) = g_key_file_get_integer(cfile, groups[i], p[j].paramname, &err);
+                                       *((gint*)p[j].target) =
+                                               g_key_file_get_integer(cfile,
+                                                               groups[i],
+                                                               p[j].paramname,
+                                                               &err);
                                        break;
                                case PARAM_STRING:
-                                       *((gchar**)p[j].target) = g_key_file_get_string(cfile, groups[i], p[j].paramname, &err);
+                                       *((gchar**)p[j].target) =
+                                               g_key_file_get_string(cfile,
+                                                               groups[i],
+                                                               p[j].paramname,
+                                                               &err);
                                        break;
                                case PARAM_BOOL:
-                                       value = g_key_file_get_boolean(cfile, groups[i], p[j].paramname, &err);
-                                       if(!err) {
-                                               *((gint*)p[j].target) |= value;
+                                       value = g_key_file_get_boolean(cfile,
+                                                       groups[i],
+                                                       p[j].paramname, &err);
+                                       if(!err && value) {
+                                               *((gint*)p[j].target) |= p[j].flagval;
                                        }
                                        break;
                        }
                        if(err) {
                                if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
-                                       if(p[j].required) {
-                                               g_set_error(e, errdomain, CFILE_KEY_MISSING, "Could not find required value %s in group %s: %s", p[j].paramname, groups[i], err->message);
-                                               g_array_free(retval, TRUE);
-                                               g_error_free(err);
-                                               g_key_file_free(cfile);
-                                               return NULL;
-                                       } else {
-                                               g_error_free(err);
+                                       if(!p[j].required) {
+                                               /* Ignore not-found error for optional values */
+                                               g_clear_error(&err);
                                                continue;
+                                       } else {
+                                               err_msg = MISSING_REQUIRED_ERROR;
                                        }
-                                       g_set_error(e, errdomain, CFILE_VALUE_INVALID, "Could not parse %s in group %s: %s", p[j].paramname, groups[i], err->message);
-                                       g_array_free(retval, TRUE);
-                                       g_error_free(err);
-                                       g_key_file_free(cfile);
-                                       return NULL;
+                               } else {
+                                       err_msg = DEFAULT_ERROR;
                                }
+                               g_set_error(e, errdomain, CFILE_VALUE_INVALID, err_msg, p[j].paramname, groups[i], err->message);
+                               g_array_free(retval, TRUE);
+                               g_error_free(err);
+                               g_key_file_free(cfile);
+                               return NULL;
                        }
                }
+               if(s.flags & F_MULTIFILE)
+                       s.hunksize = 1*GIGA;
+               else
+                       s.hunksize = OFFT_MAX;
                g_array_append_val(retval, s);
        }
        return retval;
@@ -523,12 +530,12 @@ GArray* parse_cfile(gchar* f, GError** e) {
  * is severely wrong)
  **/
 void sigchld_handler(int s) {
-        int* status=NULL;
+        int status;
        int* i;
        pid_t pid;
 
-       while((pid=wait(status)) > 0) {
-               if(WIFEXITED(status)) {
+       while((pid=waitpid(-1, &status, WNOHANG)) > 0) {
+               if(WIFEXITED(&status)) {
                        msg3(LOG_INFO, "Child exited with %d", WEXITSTATUS(status));
                }
                i=g_hash_table_lookup(children, &pid);
@@ -902,39 +909,55 @@ int mainloop(CLIENT *client) {
 }
 
 /**
- * Split a single exportfile into multiple ones, if that was asked.
+ * Set up client export array, which is an array of file handles.
+ * Also, split a single exportfile into multiple ones, if that was asked.
  * @return 0 on success, -1 on failure
  * @param client information on the client which we want to split
  **/
-int splitexport(CLIENT* client) {
-       off_t i;
-       int fhandle;
+int setupexport(CLIENT* client) {
+       int i;
+       int multifile = (client->server->flags & F_MULTIFILE);
 
        client->export = g_array_new(TRUE, TRUE, sizeof(int));
-       for (i=0; i<client->exportsize; i+=client->server->hunksize) {
+
+       /* If multifile, open as many files as we can.
+        * For non-multifile, open exactly one file.
+        * (Either way, we must be able to open at least one file.) */
+       for(i=0; ; i++) {
+               int fhandle;
                gchar *tmpname;
+               mode_t mode = (client->server->flags & F_READONLY) ? O_RDONLY : O_RDWR;
 
-               if(client->server->flags & F_MULTIFILE) {
-                       tmpname=g_strdup_printf("%s.%d", client->exportname,
-                                       (int)(i/client->server->hunksize));
+               if(multifile) {
+                       tmpname=g_strdup_printf("%s.%d", client->exportname, i);
                } else {
                        tmpname=g_strdup(client->exportname);
                }
                DEBUG2( "Opening %s\n", tmpname );
-               if((fhandle = open(tmpname, (client->server->flags & F_READONLY) ? O_RDONLY : O_RDWR)) == -1) {
-                       /* Read WRITE ACCESS was requested by media is only read only */
-                       client->server->flags |= F_AUTOREADONLY;
-                       client->server->flags |= F_READONLY;
-                       if((fhandle = open(tmpname, O_RDONLY)) == -1)
-                               err("Could not open exported file: %m");
+               fhandle = open(tmpname, mode);
+               if(fhandle == -1 && mode == O_RDWR) {
+                       /* Try again because maybe media was read-only */
+                       fhandle = open(tmpname, O_RDONLY);
+                       if(fhandle != -1) {
+                               client->server->flags |= F_AUTOREADONLY;
+                               client->server->flags |= F_READONLY;
+                       }
                }
-               g_array_insert_val(client->export,i/client->server->hunksize,fhandle);
+               if(fhandle == -1) {
+                       if(multifile && i*(client->server->hunksize) >= client->exportsize)
+                               break;
+                       err("Could not open exported file: %m");
+               }
+               g_array_insert_val(client->export, i, fhandle);
                g_free(tmpname);
+
+               if(!multifile)
+                       break;
        }
        return 0;
 }
-int copyonwrite_prepare(CLIENT* client)
-{
+
+int copyonwrite_prepare(CLIENT* client) {
        off_t i;
        if ((client->difffilename = malloc(1024))==NULL)
                err("Failed to allocate string for diff file name");
@@ -960,7 +983,11 @@ int copyonwrite_prepare(CLIENT* client)
  * @param client a connected client
  **/
 void serveconnection(CLIENT *client) {
-       splitexport(client);
+       setupexport(client);
+
+       /* Sanity check. There must be at least one file. */
+       if(client->export->len == 0)
+               err("No file(s) to export\n");
 
        if (!client->server->expected_size) {
                client->exportsize = size_autodetect(g_array_index(client->export,int,0));
@@ -1060,6 +1087,7 @@ void setup_serve(SERVER *serve) {
        struct sockaddr_in addrin;
        struct sigaction sa;
        int addrinlen = sizeof(addrin);
+       int sock_flags;
 #ifndef sun
        int yes=1;
 #else
@@ -1076,6 +1104,14 @@ void setup_serve(SERVER *serve) {
                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");
+       }
+
        DEBUG("Waiting for connections... bind, ");
        addrin.sin_family = AF_INET;
        addrin.sin_port = htons(serve->port);
@@ -1116,8 +1152,11 @@ int serveloop(GArray* servers) {
        struct sockaddr_in addrin;
        socklen_t addrinlen=sizeof(addrin);
        SERVER *serve;
-       int i, max, sock;
-       fd_set mset, rset;
+       int i;
+       int max;
+       int sock;
+       fd_set mset;
+       fd_set rset;
        struct timeval tv;
 
        /* 
@@ -1211,7 +1250,7 @@ int main(int argc, char *argv[]) {
        config_file_pos = g_strdup(CFILE);
        serve=cmdline(argc, argv);
        servers = parse_cfile(config_file_pos, &err);
-       if(!servers->len) {
+       if(!servers || !servers->len) {
                g_warning("Could not parse config file: %s", err->message);
        }
        if(serve) {
@@ -1241,7 +1280,7 @@ int main(int argc, char *argv[]) {
                return 0;
         }
 #endif
-       if((!serve) && (!servers->len)) {
+       if((!serve) && (!servers||!servers->len)) {
                g_message("Nothing to do! Bye!");
                exit(EXIT_FAILURE);
        }