11.2 Implementation

All but the very smallest programs written in C require careful organization to preserve the modularity and maintainability of the source code. This program is divided into four main source files.

Each source file exports functions or variables that may be accessed by the other parts of the program. For simplicity, all exported functions and variables are declared in a single header file, server.h (see Listing 11.1), which is included by the other files. Functions that are intended for use within a single compilation unit only are declared static and are not declared in server.h.

Listing 11.1 (server.h) Function and Variable Declarations
#ifndef SERVER_H 
#define SERVER_H 
 
#include <netinet/in.h> 
#include <sys/types.h> 
 
/*** Symbols defined in common.c.  ************************************/ 
 
/* The name of this program.  */ 
extern const char* program_name; 
 
/* If nonzero, print verbose messages.  */ 
extern int verbose; 
 
/* Like malloc, except aborts the program if allocation fails.  */ 
extern void* xmalloc (size_t size); 
 
/* Like realloc, except aborts the program if allocation fails.  */ 
extern void* xrealloc (void* ptr, size_t size); 
 
/* Like strdup, except aborts the program if allocation fails.  */ 
extern char* xstrdup (const char* s); 
 
/* Print an error message for a failed call OPERATION, using the value 
   of errno, and end the program.  */ 
extern void system_error (const char* operation); 
 
/* Print an error message for failure involving CAUSE, including a 
   descriptive MESSAGE, and end the program.  */ 
extern void error (const char* cause, const char* message); 
 
/* Return the directory containing the running program's executable. 
   The return value is a memory buffer that the caller must deallocate 
   using free. This function calls abort on failure.  */ 
extern char* get_self_executable_directory (); 
 
 
/*** Symbols defined in module.c **************************************/ 
 
/* An instance of a loaded server module.  */ 
struct server_module {
  /* The shared library handle corresponding to the loaded module.  */ 
  void* handle; 
  /* A name describing the module.  */ 
  const char* name; 
  /* The function that generates the HTML results for this module.  */ 
void (* generate_function) (int); 
}; 
/* The directory from which modules are loaded.  */ 
extern char* module_dir; 
 
/* Attempt to load a server module with the name MODULE_PATH. If a 
   server module exists with this path, loads the module and returns a 
   server_module structure representing it. Otherwise, returns NULL.  */ 
extern struct server_module* module_open (const char* module_path); 
 
/* Close a server module and deallocate the MODULE object.  */ 
extern void module_close (struct server_module* module); 
 
 
/*** Symbols defined in server.c.  ************************************/ 
 
/* Run the server on LOCAL_ADDRESS and PORT.  */ 
extern void server_run (struct in_addr local_address, uint16_t port); 
 
 
#endif  /* SERVER_H  */ 

11.2.1 Common Functions

common.c (see Listing 11.2) contains functions of general utility that are used throughout the program.

Listing 11.2 (common.c) General Utility Functions
#include <errno.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
 
#include "server.h" 
 
const char* program_name; 
 
int verbose; 
 
void* xmalloc (size_t size) 
{
  void* ptr = malloc (size); 
  /* Abort if the allocation failed.  */ 
  if (ptr == NULL) 
    abort (); 
  else 
    return ptr; 
} 
void* xrealloc (void* ptr, size_t size) 
{
  ptr = realloc (ptr, size); 
  /* Abort if the allocation failed.  */ 
  if (ptr == NULL) 
    abort (); 
  else 
    return ptr; 
} 
 
char* xstrdup (const char* s) 
{
  char* copy = strdup (s); 
  /* Abort if the allocation failed.  */ 
  if (copy == NULL) 
    abort (); 
  else 
    return copy; 
} 
 
void system_error (const char* operation) 
{
  /* Generate an error message for errno.  */ 
  error (operation, strerror (errno)); 
} 
 
void error (const char* cause, const char* message) 
{
  /* Print an error message to stderr.  */ 
  fprintf (stderr, "%s: error: (%s) %s\n", program_name, cause, message); 
  /* End the program.  */ 
  exit (1); 
} 
 
char* get_self_executable_directory () 
{
  int rval; 
  char link_target[1024]; 
  char* last_slash; 
  size_t result_length; 
  char* result; 
 
  /* Read the target of the symbolic link /proc/self/exe.  */ 
  rval = readlink ("/proc/self/exe", link_target, sizeof (link_target)); 
  if (rval == -1) 
    /* The call to readlink failed, so bail.  */ 
    abort (); 
  else 
    /* NUL-terminate the target.  */ 
    link_target[rval] = '\0'; 
  /* We want to trim the name of the executable file, to obtain the 
     directory that contains it. Find the rightmost slash.  */ 
  last_slash = strrchr (link_target, '/'); 
  if (last_slash == NULL || last_slash == link_target) 
    /* Something strange is going on.  */ 
    abort (); 
  /* Allocate a buffer to hold the resulting path.  */ 
  result_length = last_slash - link_target; 
  result = (char*) xmalloc (result_length + 1); 
  /* Copy the result.  */ 
  strncpy (result, link_target, result_length); 
  result[result_length] = '\0'; 
  return result; 
} 

You could use these functions in other programs as well; the contents of this file might be included in a common code library that is shared among many projects:

·         xmalloc, xrealloc, and xstrdup are error-checking versions of the C library functions malloc, realloc, and strdup, respectively. Unlike the standard versions, which return a null pointer if the allocation fails, these functions immediately abort the program when insufficient memory is available.

Early detection of memory allocation failure is a good idea. Otherwise, failed allocations introduce null pointers at unexpected places into the program. Because allocation failures are not easy to reproduce, debugging such problems can be difficult. Allocation failures are usually catastrophic, so aborting the program is often an acceptable course of action.

·         The error function is for reporting a fatal program error. It prints a message to stderr and ends the program. For errors caused by failed system calls or library calls, system_error generates part of the error message from the value of errno (see Section 2.2.3, "Error Codes from System Calls," in Chapter 2, "Writing Good GNU/Linux Software").

·         get_self_executable_directory determines the directory containing the executable file being run in the current process. The directory path can be used to locate other components of the program, which are installed in the same place at runtime. This function works by examining the symbolic link /proc/self/exe in the /proc file system (see Section 7.2.1, "proc/self," in Chapter 7, "The /proc File System").

In addition, common.c defines two useful global variables:

·         The value of program_name is the name of the program being run, as specified in its argument list (see Section 2.1.1, "The Argument List," in Chapter 2). When the program is invoked from the shell, this is the path and name of the program as the user entered it.

·         The variable verbose is nonzero if the program is running in verbose mode. In this case, various parts of the program print progress messages to stdout.

11.2.2 Loading Server Modules

module.c (see Listing 11.3) provides the implementation of dynamically loadable server modules. A loaded server module is represented by an instance of struct server_module, which is defined in server.h.

Listing 11.3 (module.c) Server Module Loading and Unloading
#include <dlfcn.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
 
#include "server.h" 
 
char* module_dir; 
 
struct server_module* module_open (const char* module_name) 
{
  char* module_path; 
  void* handle; 
  void (* module_generate) (int); 
  struct server_module* module; 
 
  /* Construct the full path of the module shared library we'll try to 
     load.  */ 
  module_path = 
    (char*) xmalloc (strlen (module_dir) + strlen (module_name) + 2); 
  sprintf (module_path, "%s/%s", module_dir, module_name); 
 
  /* Attempt to open MODULE_PATH as a shared library.  */ 
  handle = dlopen (module_path, RTLD_NOW); 
  free (module_path); 
  if (handle == NULL) {
    /* Failed; either this path doesn't exist or it isn't a shared 
       library.  */ 
    return NULL; 
  } 
 
  /* Resolve the module_generate symbol from the shared library.  */ 
  module_generate = (void (*) (int)) dlsym (handle, "module_generate"); 
  /* Make sure the symbol was found.  */ 
  if (module_generate == NULL) {
  /* The symbol is missing. While this is a shared library, it 
     probably isn't a server module. Close up and indicate failure.  */ 
  dlclose (handle); 
  return NULL; 
} 
 
  /* Allocate and initialize a server_module object.  */ 
  module = (struct server_module*) xmalloc (sizeof (struct server_module)); 
  module->handle = handle; 
  module->name = xstrdup (module_name); 
  module->generate_function = module_generate; 
  /* Return it, indicating success.  */ 
  return module; 
} 
 
void module_close (struct server_module* module) 
{
  /* Close the shared library.  */ 
  dlclose (module->handle); 
  /* Deallocate the module name.  */ 
  free ((char*) module->name); 
  /* Deallocate the module object.  */ 
  free (module); 
} 

Each module is a shared library file (see Section 2.3.2, "Shared Libraries," in Chapter 2) and must define and export a function named module_generate. This function generates an HTML Web page and writes it to the client socket file descriptor passed as its argument.

module.c contains two functions:

·         module_open attempts to load a server module with a given name. The name normally ends with the .so extension because server modules are implemented as shared libraries. This function opens the shared library with dlopen and resolves a symbol named module_generate from the library with dlsym (see Section 2.3.6, "Dynamic Loading and Unloading," in Chapter 2). If the library can't be opened, or if module_generate isn't a name exported by the library, the call fails and module_open returns a null pointer. Otherwise, it allocates and returns a module object.

·         module_close closes the shared library corresponding to the server module and deallocates the struct server_module object.

module.c also defines a global variable module_dir. This is the path of the directory in which module_open attempts to find shared libraries corresponding to server modules.

11.2.3 The Server

server.c (see Listing 11.4) is the implementation of the minimal HTTP server.

Listing 11.4 (server.c) Server Implementation
#include <arpa/inet.h> 
#include <assert.h> 
#include <errno.h> 
#include <netinet/in.h> 
#include <signal.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/wait.h> 
#include <unistd.h> 
 
#include "server.h" 
 
/* HTTP response and header for a successful request.  */ 
 
static char* ok_response = 
  "HTTP/1.0 200 OK\n" 
  "Content-type: text/html\n" 
  "\n"; 
 
/* HTTP response, header, and body, indicating that we didn't 
   understand the request.  */ 
 
static char* bad_request_response = 
  "HTTP/1.0 400 Bad Request\n" 
  "Content-type: text/html\n" 
  "\n" 
  "<html>\n" 
  " <body>\n" 
  "  <h1>Bad Request</h1>\n" 
  "  <p>This server did not understand your request.</p>\n" 
  " </body>\n" 
  "</html>\n"; 
 
/* HTTP response, header, and body template, indicating that the 
   requested document was not found.  */ 
 
static char* not_found_response_template = 
  "HTTP/1.0 404 Not Found\n" 
  "Content-type: text/html\n" 
  "\n" 
  "<html>\n" 
  " <body>\n" 
  "  <h1>Not Found</h1>\n" 
  "  <p>The requested URL %s was not found on this server.</p>\n" 
  " </body>\n" 
  "</html>\n"; 
 
/* HTTP response, header, and body template, indicating that the 
   method was not understood.  */ 
 
static char* bad_method_response_template = 
  "HTTP/1.0 501 Method Not Implemented\n" 
  "Content-type: text/html\n" 
  "\n" 
  "<html>\n" 
  " <body>\n" 
  "  <h1>Method Not Implemented</h1>\n" 
  "  <p>The method %s is not implemented by this server.</p>\n" 
  " </body>\n" 
  "</html>\n"; 
 
/* Handler for SIGCHLD, to clean up child processes that have 
   terminated.  */ 
 
static void clean_up_child_process (int signal_number) 
{
  int status; 
  wait (&status); 
} 
 
/* Process an HTTP "GET" request for PAGE, and send the results to the 
   file descriptor CONNECTION_FD.  */ 
 
static void handle_get (int connection_fd, const char* page) 
{
  struct server_module* module = NULL; 
 
  /* Make sure the requested page begins with a slash and does not 
     contain any additional slashes -- we don't support any 
     subdirectories.  */ 
  if (*page == '/' && strchr (page + 1, '/') == NULL) {
    char module_file_name[64]; 
 
    /* The page name looks OK. Construct the module name by appending 
       ".so" to the page name.  */ 
    snprintf (module_file_name, sizeof (module_file_name), 
              "%s.so", page + 1); 
    /* Try to open the module.  */ 
    module = module_open (module_file_name); 
  } 
 
  if (module == NULL) {
    /* Either the requested page was malformed, or we couldn't open a 
       module with the indicated name. Either way, return the HTTP 
       response 404, Not Found.  */ 
 
      char response[1024]; 
 
      /* Generate the response message.  */ 
      snprintf (response, sizeof (response), not_found_response_template, page); 
      /* Send it to the client.  */ 
      write (connection_fd, response, strlen (response)); 
   } 
   else {
      /* The requested module was loaded successfully.  */ 
 
      /* Send the HTTP response indicating success, and the HTTP header 
         for an HTML page.  */ 
      write (connection_fd, ok_response, strlen (ok_response)); 
      /* Invoke the module, which will generate HTML output and send it 
         to the client file descriptor.  */ 
      (*module->generate_function) (connection_fd); 
      /* We're done with the module.  */ 
      module_close (module); 
} 
} 
 
/* Handle a client connection on the file descriptor CONNECTION_FD.  */ 
 
static void handle_connection (int connection_fd) 
{
   char buffer[256]; 
   ssize_t bytes_read; 
 
   /* Read some data from the client.  */ 
   bytes_read = read (connection_fd, buffer, sizeof (buffer) - 1); 
   if (bytes_read > 0) {
     char method[sizeof (buffer)]; 
     char url[sizeof (buffer)]; 
     char protocol[sizeof (buffer)]; 
 
     /* Some data was read successfully. NUL-terminate the buffer so 
        we can use string operations on it.  */ 
     buffer[bytes_read] = '\0'; 
     /* The first line the client sends is the HTTP request, which is 
        composed of a method, the requested page, and the protocol 
        version.  */ 
     sscanf (buffer, "%s %s %s", method, url, protocol); 
     /* The client may send various header information following the 
        request. For this HTTP implementation, we don't care about it. 
        However, we need to read any data the client tries to send. Keep 
        on reading data until we get to the end of the header, which is 
        delimited by a blank line. HTTP specifies CR/LF as the line 
        delimiter.  */ 
     while (strstr (buffer, "\r\n\r\n") == NULL) 
         bytes_read = read (connection_fd, buffer, sizeof (buffer)); 
      /* Make sure the last read didn't fail. If it did, there's a 
         problem with the connection, so give up.  */ 
      if (bytes_read == -1) {
         close (connection_fd); 
         return; 
      } 
      /* Check the protocol field. We understand HTTP versions 1.0 and 
         1.1.  */ 
      if (strcmp (protocol, "HTTP/1.0") && strcmp (protocol, "HTTP/1.1")) {
         /* We don't understand this protocol. Report a bad response.  */ 
         write (connection_fd, bad_request_response, 
                sizeof (bad_request_response)); 
      } 
      else if (strcmp (method, "GET")) {
         /* This server only implements the GET method. The client 
            specified some other method, so report the failure.  */ 
         char response[1024]; 
 
        snprintf (response, sizeof (response), 
                  bad_method_response_template, method); 
        write (connection_fd, response, strlen (response)); 
      } 
      else 
        /* A valid request. Process it.  */ 
        handle_get (connection_fd, url); 
   } 
   else if (bytes_read == 0) 
     /* The client closed the connection before sending any data. 
        Nothing to do.  */ 
     ; 
   else 
     /* The call to read failed.  */ 
     system_error ("read"); 
} 
 
 
void server_run (struct in_addr local_address, uint16_t port) 
{
   struct sockaddr_in socket_address; 
   int rval; 
   struct sigaction sigchld_action; 
   int server_socket; 
 
   /* Install a handler for SIGCHLD that cleans up child processes that 
      have terminated.  */ 
   memset (&sigchld_action, 0, sizeof (sigchld_action)); 
   sigchld_action.sa_handler = &clean_up_child_process; 
   sigaction (SIGCHLD, &sigchld_action, NULL); 
    /* Create a TCP socket.  */ 
    server_socket = socket (PF_INET, SOCK_STREAM, 0); 
    if (server_socket == -1) 
      system_error ("socket"); 
    /* Construct a socket address structure for the local address on 
       which we want to listen for connections.  */ 
    memset (&socket_address, 0, sizeof (socket_address)); 
    socket_address.sin_family = AF_INET; 
    socket_address.sin_port = port; 
    socket_address.sin_addr = local_address; 
    /* Bind the socket to that address.  */ 
    rval = bind (server_socket, &socket_address, sizeof (socket_address)); 
    if (rval != 0) 
      system_error ("bind"); 
    /* Instruct the socket to accept connections.  */ 
    rval = listen (server_socket, 10); 
    if (rval != 0) 
      system_error ("listen"); 
 
    if (verbose) {
      /* In verbose mode, display the local address and port number 
         we're listening on.  */ 
      socklen_t address_length; 
 
      /* Find the socket's local address.  */ 
      address_length = sizeof (socket_address); 
      rval = getsockname (server_socket, &socket_address, &address_length); 
      assert (rval == 0); 
      /* Print a message. The port number needs to be converted from 
         network byte order (big endian) to host byte order.  */ 
      printf ("server listening on %s:%d\n", 
              inet_ntoa (socket_address.sin_addr), 
              (int) ntohs (socket_address.sin_port)); 
    } 
 
    /* Loop forever, handling connections.  */ 
    while (1) {
      struct sockaddr_in remote_address; 
      socklen_t address_length; 
      int connection; 
      pid_t child_pid; 
 
      /* Accept a connection. This call blocks until a connection is 
         ready.  */ 
      address_length = sizeof (remote_address); 
      connection = accept (server_socket, &remote_address, &address_length); 
      if (connection == -1) {
        /* The call to accept failed.  */ 
        if (errno == EINTR) 
          /* The call was interrupted by a signal. Try again.  */ 
          continue; 
        else 
          /* Something else went wrong.  */ 
          system_error ("accept"); 
    } 
 
    /* We have a connection. Print a message if we're running in 
       verbose mode.  */ 
    if (verbose) {
      socklen_t address_length; 
 
      /* Get the remote address of the connection.  */ 
      address_length = sizeof (socket_address); 
      rval = getpeername (connection, &socket_address, &address_length); 
      assert (rval == 0); 
      /* Print a message.  */ 
      printf ("connection accepted from %s\n", 
              inet_ntoa (socket_address.sin_addr)); 
    } 
 
    /* Fork a child process to handle the connection.  */ 
    child_pid = fork (); 
    if (child_pid == 0) {
      /* This is the child process. It shouldn't use stdin or stdout, 
         so close them.  */ 
      close (STDIN_FILENO); 
      close (STDOUT_FILENO); 
      /* Also this child process shouldn't do anything with the 
         listening socket.  */ 
      close (server_socket); 
      /* Handle a request from the connection. We have our own copy 
         of the connected socket descriptor.  */ 
      handle_connection (connection); 
      /* All done; close the connection socket, and end the child 
         process.  */ 
      close (connection); 
      exit (0); 
    } 
    else if (child_pid > 0) {
      /* This is the parent process. The child process handles the 
         connection, so we don't need our copy of the connected socket 
         descriptor. Close it. Then continue with the loop and 
         accept another connection.  */ 
      close (connection); 
    } 
    else 
      /* Call to fork failed.  */ 
      system_error ("fork"); 
   } 
} 

These are the functions in server.c:

·         server_run is the main entry point for running the server. This function starts the server and begins accepting connections, and does not return unless a serious error occurs. The server uses a TCP stream server socket (see Section 5.5.3, "Servers," in Chapter 5, "Interprocess Communication").

The first argument to server_run specifies the local address at which connections are accepted. A GNU/Linux computer may have multiple network addresses, and each address may be bound to a different network interface. [2] To restrict the server to accept connections from a particular interface, specify the corresponding network address. Specify the local address INADDR_ANY to accept connections for any local address.

[2] Your computer might be configured to include such interfaces as eth0, an Ethernet card; lo, the local (loopback) network; or ppp0, a dial-up network connection.

The second argument to server_run is the port number on which to accept connections. If the port number is already in use, or if it corresponds to a privileged port and the server is not being run with superuser privilege, the server fails. The special value 0 instructs Linux to select an unused port automatically. See the inet man page for more information about Internet-domain addresses and port numbers.

The server handles each client connection in a child process created with fork (see Section 3.2.2, "Using fork and exec," in Chapter 3, "Processes"). The main (parent) process continues accepting new connections while existing ones are being serviced. The child process invokes handle_connection and then closes the connection socket and exits.

·         handle_connection processes a single client connection, using the socket file descriptor passed as its argument. This function reads data from the socket and attempts to interpret this as an HTTP page request.

The server processes only HTTP version 1.0 and version 1.1 requests. When faced with a different protocol or version, it responds by sending the HTTP result code 400 and the message bad_request_response. The server understands only the HTTP GET method. If the client requests any other method, the server responds by sending the HTTP result code 501 and the message bad_method_response_template.

·         If the client sends a well-formed GET request, handle_connection calls handle_get to service it. This function attempts to load a server module with a name generated from the requested page. For example, if the client requests the page named information, it attempts to load a server module named information.so. If the module can't be loaded, handle_get sends the client the HTTP result code 404 and the message not_found_response_template.

If the client sends a page request that corresponds to a server module, handle_get sends a result code 200 header to the client, which indicates that the request was processed successfully and invokes the module's module_generate function. This function generates the HTML source for a Web page and sends it to the Web client.

·         server_run installs clean_up_child_process as the signal handler for SIGCHLD. This function simply cleans up terminated child processes (see Section 3.4.4, "Cleaning Up Children Asynchronously," in Chapter 3).

11.2.4 The Main Program

main.c (see Listing 11.5) provides the main function for the server program. Its responsibility is to parse command-line options, detect and report command-line errors, and configure and run the server.

Listing 11.5 (main.c) Main Server Program and Command-Line Parsing
#include <assert.h> 
#include <getopt.h> 
#include <netdb.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/stat.h> 
#include <unistd.h> 
 
#include "server.h" 
 
/* Description of long options for getopt_long.  */ 
 
static const struct option long_options[] = {
  { "address",          1, NULL, 'a' }, 
  { "help",             0, NULL, 'h' }, 
  { "module-dir",       1, NULL, 'm' }, 
  { "port",             1, NULL, 'p' }, 
  { "verbose",          0, NULL, 'v' }, 
}; 
 
/* Description of short options for getopt_long.  */ 
 
static const char* const short_options = "a:hm:p:v"; 
 
/* Usage summary text.  */ 
 
static const char* const usage_template = 
  "Usage: %s [ options ]\n" 
  "  -a, --address ADDR        Bind to local address (by default, bind\n" 
  "                              to all local addresses).\n" 
  " -h, --help                 Print this information.\n" 
  " -m, --module-dir DIR       Load modules from specified directory\n" 
  "                              (by default, use executable directory).\n" 
  " -p, --port PORT            Bind to specified port.\n" 
  " -v, --verbose              Print verbose messages.\n"; 
 
 /* Print usage information and exit. If IS_ERROR is nonzero, write to 
    stderr and use an error exit code. Otherwise, write to stdout and 
    use a non-error termination code. Does not return.  */ 
 
    static void print_usage (int is_error) 
    {
       fprintf (is_error ? stderr : stdout, usage_template, program_name); 
       exit (is_error ? 1 : 0); 
    } 
 
    int main (int argc, char* const argv[]) 
    {
      struct in_addr local_address; 
      uint16_t port; 
      int next_option; 
 
      /* Store the program name, which we'll use in error messages.  */ 
      program_name = argv[0]; 
 
      /* Set defaults for options. Bind the server to all local addresses, 
         and assign an unused port automatically.  */ 
      local_address.s_addr = INADDR_ANY; 
      port = 0; 
      /* Don't print verbose messages.  */ 
      verbose = 0; 
      /* Load modules from the directory containing this executable. */ 
      module_dir = get_self_executable_directory (); 
      assert (module_dir != NULL); 
 
      /* Parse options.  */ 
      do {
        next_option = 
          getopt_long (argc, argv, short_options, long_options, NULL); 
        switch (next_option) {
        case 'a': 
          /* User specified -a or --address.  */ 
          {
            struct hostent* local_host_name; 
 
            /* Look up the hostname the user specified.  */ 
            local_host_name = gethostbyname (optarg); 
            if (local_host_name == NULL || local_host_name->h_length == 0) 
               /* Could not resolve the name.  */ 
               error (optarg, "invalid host name"); 
             else 
               /* Hostname is OK, so use it.  */ 
               local_address.s_addr = 
                 *((int*) (local_host_name->h_addr_list[0])); 
       } 
       break; 
 
    case 'h': 
      /* User specified -h or --help.  */ 
      print_usage (0); 
 
    case 'm': 
      /* User specified -m or --module-dir.  */ 
      {
        struct stat dir_info; 
 
        /* Check that it exists.  */ 
        if (access (optarg, F_OK) != 0) 
          error (optarg, "module directory does not exist"); 
        /* Check that it is accessible.  */ 
        if (access (optarg, R_OK | X_OK) != 0) 
          error (optarg, "module directory is not accessible"); 
        /* Make sure that it is a directory.  */ 
          if (stat (optarg, &dir_info) != 0 || !S_ISDIR (dir_info.st_mode)) 
          error (optarg, "not a directory"); 
        /* It looks OK, so use it.  */ 
        module_dir = strdup (optarg); 
      } 
      break; 
 
    case 'p': 
      /* User specified -p or --port.  */ 
      {
        long value; 
        char* end; 
 
        value = strtol (optarg, &end, 10); 
        if (*end != '\0') 
          /* The user specified nondigits in the port number.  */ 
          print_usage (1); 
         /* The port number needs to be converted to network (big endian) 
            byte order.  */ 
         port = (uint16_t) htons (value); 
       } 
       break; 
 
     case 'v': 
       /* User specified -v or --verbose.  */ 
       verbose = 1; 
       break; 
       case '?': 
          /* User specified an unrecognized option. */ 
          print_usage (1); 
 
       case -1: 
           /* Done with options.  */ 
          break; 
 
       default: 
          abort (); 
       } 
     } while (next_option != -1); 
 
    /* This program takes no additional arguments. Issue an error if the 
      user specified any.  */ 
   if (optind != argc) 
     print_usage (1); 
 
   /* Print the module directory, if we're running verbose.  */ 
   if (verbose) 
     printf ("modules will be loaded from %s\n", module_dir); 
 
   /* Run the server.  */ 
   server_run (local_address, port); 
 
   return 0; 
} 

main.c contains these functions:

·         main invokes getopt_long (see Section 2.1.3, "Using getopt_long," in Chapter 2) to parse command-line options. It provides both long and short option forms, the former in the long_options array and the latter in the short_options string.

The default value for the server port is 0 and for a local address is INADDR_ANY. These can be overridden by the --port (-p) and --address (-a) options, respectively. If the user specifies an address, main calls the library function gethostbyname to convert it to a numerical Internet address. [3]

[3] gethostbyname performs name resolution using DNS, if necessary.

The default value for the directory from which to load server modules is the directory containing the server executable, as determined by get_self_executable_directory. The user may override this with the --module-dir (-m) option; main makes sure that the specified directory is accessible.

By default, verbose messages are not printed. The user may enable them by specifying the --verbose (-v) option.

·         If the user specifies the --help (-h) option or specifies invalid options, main invokes print_usage, which prints a usage summary and exits.