r141: Commit this before I accidentally remove it again..
authoryoe <yoe>
Fri, 6 Jan 2006 17:03:10 +0000 (17:03 +0000)
committeryoe <yoe>
Fri, 6 Jan 2006 17:03:10 +0000 (17:03 +0000)
nbd-server.c

index 26c6bc0..dc5dbf9 100644 (file)
 #define MY_NAME "nbd_server"
 #include "cliserv.h"
 
+/** Default position of the config file */
+#ifndef SYSCONFDIR
+#define SYSCONFDIR "/etc"
+#endif
+#define CFILE SYSCONFDIR "/nbd-server/config"
+
+/** 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. */
@@ -151,7 +160,7 @@ char default_authname[] = "/etc/nbd_server.allow"; /**< default name of allow fi
  * Variables associated with a server.
  **/
 typedef struct {
-       char* exportname;    /**< (unprocessed) filename of the file we're exporting */
+       gchar* exportname;    /**< (unprocessed) filename of the file we're exporting */
        off_t hunksize;      /**< size of a hunk of an exported file */
        off_t expected_size; /**< size of the exported file as it was told to
                               us through configuration */
@@ -184,10 +193,35 @@ typedef struct {
 } CLIENT;
 
 /**
+ * Type of configuration file values
+ **/
+typedef enum {
+       PARAM_INT,              /**< This parameter is an integer */
+       PARAM_STRING,           /**< This parameter is a string */
+       PARAM_BOOL,             /**< This parameter is a boolean */
+} PARAM_TYPE;
+/**
+ * Configuration file values
+ **/
+typedef struct {
+       gchar *paramname;       /**< Name of the parameter, as it appears in
+                                 the config file */
+       gboolean required;      /**< Whether this is a required (as opposed to
+                                 optional) parameter */
+       PARAM_TYPE ptype;       /**< Type of the parameter. */
+       gpointer target;        /**< Pointer to where the data of this
+                                 parameter should be written. If ptype is
+                                 PARAM_BOOL, the data is or'ed rather than
+                                 overwritten. */
+       gint flagval;           /**< Flag mask for this parameter in case ptype
+                                 is PARAM_BOOL. */
+} PARAM;
+
+/**
  * Check whether a client is allowed to connect. Works with an authorization
  * file which contains one line per machine, no wildcards.
  *
- * @param name IP address of client trying to connect (in human-readable form)
+ * @param opts The client who's trying to connect.
  * @return 0 - authorization refused, 1 - OK
  **/
 int authorized_client(CLIENT *opts) {
@@ -262,20 +296,18 @@ void usage() {
               "\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" );
+       printf("Using configuration file %s\n", CFILE);
 }
 
 /**
  * Parse the command line.
  *
- * @todo getopt() is a great thing, and easy to use. Also, we want to
- * create a configuration file which nbd-server will read. Maybe do (as in,
- * parse) that here.
- *
  * @param argc the argc argument to main()
  * @param argv the argv argument to main()
  **/
 SERVER* cmdline(int argc, char *argv[]) {
        int i=0;
+       int nonspecial=0;
        int c;
        struct option long_options[] = {
                {"read-only", no_argument, NULL, 'r'},
@@ -283,71 +315,209 @@ SERVER* cmdline(int argc, char *argv[]) {
                {"copy-on-write", no_argument, NULL, 'c'},
                {"authorize-file", required_argument, NULL, 'l'},
                {"idle-time", required_argument, NULL, 'a'},
+               {"config-file", required_argument, NULL, 'C'},
                {0,0,0,0}
        };
        SERVER *serve;
+       off_t es;
+       size_t last;
+       char suffix;
 
-       serve=g_malloc(sizeof(SERVER));
+       if(argc==1) {
+               return NULL;
+       }
+       serve=g_new0(SERVER, 1);
        serve->hunksize=OFFT_MAX;
-       while((c=getopt_long(argc, argv, "a:cl:mr", long_options, &i))>=0) {
+       serve->authname = g_strdup(default_authname);
+       while((c=getopt_long(argc, argv, "-a:C:cl:mr", long_options, &i))>=0) {
                switch (c) {
+               case 1:
+                       /* non-option argument */
+                       switch(nonspecial++) {
+                       case 0:
+                               serve->port=strtol(optarg, NULL, 0);
+                               break;
+                       case 1:
+                               serve->exportname = g_strdup(optarg);
+                               if(serve->exportname[0] != '/') {
+                                       fprintf(stderr, "E: The to be exported file needs to be an absolute filename!\n");
+                                       exit(EXIT_FAILURE);
+                               }
+                               break;
+                       case 2:
+                               last=strlen(optarg)-1;
+                               suffix=optarg[last];
+                               if (suffix == 'k' || suffix == 'K' ||
+                                   suffix == 'm' || suffix == 'M')
+                                       optarg[last] = '\0';
+                               es = (off_t)atol(optarg);
+                               switch (suffix) {
+                                       case 'm':
+                                       case 'M':  es <<= 10;
+                                       case 'k':
+                                       case 'K':  es <<= 10;
+                                       default :  break;
+                               }
+                               serve->expected_size = es;
+                               break;
+                       }
+                       break;
                case 'r':
                        serve->flags |= F_READONLY;
                        break;
                case 'm':
                        serve->flags |= F_MULTIFILE;
                        serve->hunksize = 1*GIGA;
-                       serve->authname = default_authname;
                        break;
                case 'c': 
                        serve->flags |=F_COPYONWRITE;
                        break;
+               case 'C':
+                       g_free(config_file_pos);
+                       config_file_pos=g_strdup(optarg);
+                       break;
                case 'l':
-                       serve->authname=optarg;
+                       g_free(serve->authname);
+                       serve->authname=g_strdup(optarg);
                        break;
                case 'a': 
                        serve->timeout=strtol(optarg, NULL, 0);
                        break;
                default:
                        usage();
-                       exit(0);
+                       exit(EXIT_FAILURE);
                        break;
                }
        }
        /* 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(++i>=argc) {
-               usage();
-               exit(0);
-       } 
-       serve->port=strtol(argv[i], NULL, 0);
-       if(++i>=argc) {
+       if(nonspecial<2) {
                usage();
-               exit(0);
-       }
-       serve->exportname = argv[i];
-       if(serve->exportname[0] != '/') {
-               fprintf(stderr, "E: The to be exported file needs to be an absolute filename!\n");
                exit(EXIT_FAILURE);
        }
-       if(++i<argc) {
-               off_t es;
-               size_t last = strlen(argv[i])-1;
-               char suffix = argv[i][last];
-               if (suffix == 'k' || suffix == 'K' ||
-                   suffix == 'm' || suffix == 'M')
-                       argv[i][last] = '\0';
-               es = (off_t)atol(argv[i]);
-               switch (suffix) {
-                       case 'm':
-                       case 'M':  es <<= 10;
-                       case 'k':
-                       case 'K':  es <<= 10;
-                       default :  break;
+       return serve;
+}
+
+/**
+ * Error codes for config file parsing
+ **/
+typedef enum {
+       CFILE_NOTFOUND,         /**< The configuration file is not found */
+       CFILE_MISSING_GENERIC,  /**< The (required) group "generic" is missing */
+       CFILE_KEY_MISSING,      /**< A (required) key is missing */
+       CFILE_VALUE_INVALID,    /**< A value is syntactically invalid */
+       CFILE_PROGERR           /**< Programmer error */
+} CFILE_ERRORS;
+
+/**
+ * Remove a SERVER from memory. Used from the hash table
+ **/
+void remove_server(gpointer s) {
+       SERVER *server;
+
+       server=(SERVER*)s;
+       g_free(server->exportname);
+       if(server->authname)
+               g_free(server->authname);
+       g_free(server);
+}
+
+/**
+ * Parse the config file.
+ *
+ * @param f the name of the config file
+ * @param e a GError. @see CFILE_ERRORS for what error values this function can
+ *     return.
+ * @return a GHashTable of SERVER* pointers, with the port number as the hash
+ *     key. If the config file is empty or does not exist, returns an empty
+ *     GHashTable; if the config file contains an error, returns NULL, and
+ *     e is set appropriately
+ **/
+GHashTable* parse_cfile(gchar* f, GError** e) {
+       SERVER *s;
+       PARAM p[] = {
+               { "exportname", TRUE,   PARAM_STRING,   NULL, 0 },
+               { "port",       TRUE,   PARAM_INT,      NULL, 0 },
+               { "authfile",   FALSE,  PARAM_STRING,   NULL, 0 },
+               { "timeout",    FALSE,  PARAM_INT,      NULL, 0 },
+               { "filesize",   FALSE,  PARAM_INT,      NULL, 0 },
+               { "readonly",   FALSE,  PARAM_BOOL,     NULL, F_READONLY },
+               { "multifile",  FALSE,  PARAM_BOOL,     NULL, F_MULTIFILE },
+               { "copyonwrite", FALSE, PARAM_BOOL,     NULL, F_COPYONWRITE },
+       };
+       GKeyFile *cfile;
+       GError *err = NULL;
+       GQuark errdomain;
+       GHashTable *retval;
+       gchar **groups;
+       gboolean value;
+       gint i,j;
+
+       errdomain = g_quark_from_string("parse_cfile");
+       cfile = g_key_file_new();
+       retval = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, remove_server);
+       if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
+                       G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
+               g_set_error(e, errdomain, CFILE_NOTFOUND, "Could not open config file.");
+               g_key_file_free(cfile);
+               return retval;
+       }
+       if(strcmp(g_key_file_get_start_group(cfile), "generic")) {
+               g_set_error(e, errdomain, CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
+               g_key_file_free(cfile);
+               return NULL;
+       }
+       groups = g_key_file_get_groups(cfile, NULL);
+       for(i=0;groups[i];i++) {
+               s=g_new0(SERVER, 1);
+               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++) {
+                       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);
+                                       break;
+                               case PARAM_STRING:
+                                       *((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;
+                                       }
+                                       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_hash_table_destroy(retval);
+                                               g_error_free(err);
+                                               g_key_file_free(cfile);
+                                               g_free(s);
+                                               return NULL;
+                                       } else {
+                                               g_error_free(err);
+                                               continue;
+                                       }
+                                       g_set_error(e, errdomain, CFILE_VALUE_INVALID, "Could not parse %s in group %s: %s", p[j].paramname, groups[i], err->message);
+                                       g_hash_table_destroy(retval);
+                                       g_error_free(err);
+                                       g_key_file_free(cfile);
+                                       g_free(s);
+                                       return NULL;
+                               }
+                       }
                }
-               serve->expected_size = es;
+               g_hash_table_insert(retval, &(s->port), s);
        }
-       return serve;
+       return retval;
 }
 
 /**
@@ -475,6 +645,7 @@ void myseek(int handle,off_t a) {
  * @param a The offset where the write should start
  * @param buf The buffer to write from
  * @param len The length of buf
+ * @param client The client we're serving for
  * @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) {
@@ -493,6 +664,7 @@ int rawexpwrite(off_t a, char *buf, size_t len, CLIENT *client) {
  * @param a The offset where the read should start
  * @param buf A buffer to read into
  * @param len The size of buf
+ * @param client The client we're serving for
  * @return The number of bytes actually read, or -1 in case of an
  * error.
  **/
@@ -512,6 +684,7 @@ int rawexpread(off_t a, char *buf, size_t len, CLIENT *client) {
  * @param a The offset where the read should start
  * @param buf A buffer to read into
  * @param len The size of buf
+ * @param client The client we're going to read for
  * @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) {
@@ -552,6 +725,7 @@ int expread(off_t a, char *buf, size_t len, CLIENT *client) {
  * @param a The offset where the write should start
  * @param buf The buffer to write from
  * @param len The length of buf
+ * @param client The client we're going to write for.
  * @return The number of bytes actually written, or -1 in case of an error
  **/
 int expwrite(off_t a, char *buf, size_t len, CLIENT *client) {
@@ -605,7 +779,7 @@ int expwrite(off_t a, char *buf, size_t len, CLIENT *client) {
 /**
  * Do the initial negotiation.
  *
- * @param net A socket to do the negotiation over
+ * @param client The client we're negotiating with.
  **/
 void negotiate(CLIENT *client) {
        char zeros[300];
@@ -634,7 +808,7 @@ void negotiate(CLIENT *client) {
  * @todo This beast needs to be split up in many tiny little manageable
  * pieces. Preferably with a chainsaw.
  *
- * @param net A network socket, connected to an nbd client
+ * @param client The client we're going to serve to.
  * @return never
  **/
 int mainloop(CLIENT *client) {
@@ -677,7 +851,7 @@ int mainloop(CLIENT *client) {
 
                if (request.magic != htonl(NBD_REQUEST_MAGIC))
                        err("Not enough magic.");
-               if (len > BUFSIZE)
+               if (len > BUFSIZE + sizeof(struct nbd_reply))
                        err("Request too big!");
 #ifdef DODBG
                printf("%s from %Lu (%Lu) len %d, ", request.type ? "WRITE" :
@@ -786,7 +960,7 @@ int copyonwrite_prepare(CLIENT* client)
  * @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
+ * @param client a connected client
  **/
 void serveconnection(CLIENT *client) {
        splitexport(client);
@@ -851,25 +1025,29 @@ void destroy_pid_t(gpointer 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.
+ *     then do not daemonize, because we're doing inetd then. This parameter
+ *     is only used to create a PID file of the form
+ *     /var/run/nbd-server.&lt;port&gt;.pid; it's not modified in any way.
  **/
 #if !defined(NODAEMON) && !defined(NOFORK)
 void daemonize(SERVER* serve) {
        FILE*pidf;
 
-       if((serve->port)) {
-               if(daemon(0,0)<0) {
-                       err("daemon");
-               }
+       if(daemon(0,0)<0) {
+               err("daemon");
+       }
+       if(serve) {
                snprintf(pidfname, sizeof(char)*255, "/var/run/nbd-server.%d.pid", serve->port);
-               pidf=fopen(pidfname, "w");
-               if(pidf) {
-                       fprintf(pidf,"%d", (int)getpid());
-                       fclose(pidf);
-               } else {
-                       perror("fopen");
-                       fprintf(stderr, "Not fatal; continuing");
-               }
+       } else {
+               strncpy(pidfname, "/var/run/nbd-server.pid", sizeof(char)*255);
+       }
+       pidf=fopen(pidfname, "w");
+       if(pidf) {
+               fprintf(pidf,"%d\n", (int)getpid());
+               fclose(pidf);
+       } else {
+               perror("fopen");
+               fprintf(stderr, "Not fatal; continuing");
        }
 }
 #else
@@ -879,13 +1057,14 @@ void daemonize(SERVER* serve) {
 /**
  * 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.
+ * @todo modularize this giant beast.
  *
+ * @param port the port we're connecting to (we don't need it, but
+ * g_hash_table_foreach requires it)
  * @param serve the server we want to connect.
+ * @param data (unused) user data.
  **/
-void setup_serve(SERVER* serve) {
+void setup_serve(gpointer port, gpointer s, gpointer data) {
        struct sockaddr_in addrin;
        struct sigaction sa;
        int addrinlen = sizeof(addrin);
@@ -894,7 +1073,9 @@ void setup_serve(SERVER* serve) {
 #else
        char yes='1';
 #endif /* sun */
+       SERVER *serve=(SERVER*)s;
 
+       g_assert(data==NULL);
        if ((serve->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
                err("socket: %m");
 
@@ -929,6 +1110,13 @@ void setup_serve(SERVER* serve) {
 }
 
 /**
+ * Connect our servers.
+ **/
+void setup_servers(GHashTable* servers) {
+       g_hash_table_foreach(servers, setup_serve, NULL);
+}
+
+/**
  * Loop through the available servers, and serve them.
  *
  * Actually, right now we only handle one server. Will change that for
@@ -982,8 +1170,9 @@ int serveloop(SERVER* serve) {
  * Main entry point...
  **/
 int main(int argc, char *argv[]) {
-       SERVER* serve;
-       GArray* servers;
+       SERVER *serve;
+       GHashTable *servers;
+       GError *err=NULL;
 
        if (sizeof( struct nbd_request )!=28) {
                fprintf(stderr,"Bad size of structure. Alignment problems?\n");
@@ -991,9 +1180,18 @@ int main(int argc, char *argv[]) {
        }
 
        logging();
+       config_file_pos = g_strdup(CFILE);
        serve=cmdline(argc, argv);
-       servers=g_array_new(TRUE, FALSE, sizeof(SERVER*));
+       servers = parse_cfile(config_file_pos, &err);
+       if(!servers) {
+               g_critical("Could not parse command file: %s", err->message);
+       }
+       if(serve) {
+               g_hash_table_insert(servers, &serve->port, serve);
+       }
 
+/* We don't support this at this time */
+#if 0
        if (!(serve->port)) {
                CLIENT *client;
 #ifndef ISSERVER
@@ -1014,8 +1212,13 @@ int main(int argc, char *argv[]) {
                serveconnection(client);
                return 0;
         }
+#endif
+       if((!serve) && (!servers)) {
+               g_message("Nothing to do! Bye!");
+               exit(EXIT_FAILURE);
+       }
        daemonize(serve);
-       setup_serve(serve);
-       serveloop(serve);
+       setup_servers(servers);
+       serveloop(servers);
        return 0 ;
 }