aboutsummaryrefslogblamecommitdiffstats
path: root/src/coopgammad.c
blob: e81840ef54058055fbc1cbda9d082cc98c9e3791 (plain) (tree)
1
2
3
4
5
6
7
8
9
   
                                         

                                                        
                                                                       



                                                                       
                                                                  




                                                                    
                                                                        
   

                 
                  
                           



                              
 
                         
                     
                     
                  
                  
                  
                   
                   
                   
 
 
 
   






                                                









                                             





                                                    









                                                         





















                                                                


 
   

                               
                              
                              



                             
                                 
                                 
 





                                    
                                     


                                 
                          
             
                            
                      






                                   
                                     





                                    
 
 
   







                                     
                          
                                    
                                
                      


 



















                                                                           

   




                                                                  

                                               




                                                    
                         
  


                                                
















                                                                               






                                                          



                                                          








                                                          
                                     
   
                                          
                                                                     
   
                                                  
 




                                
  
                          
              





                                                                          
  


                         
     
                                      



                                             
                  
                          

                                                           

     








































                                                                                          

                      


                                              
                      
                      



   
                         
   

                                                       
                                                                
                                                                     
   
                                                                              
 
                       
              
                
        
                     
  
                                                           

                                 
             










                                                                                  

                                 




                                              
  
                  
                                                                 
                                                                              
  

                                                                                 
                        
  
                
                            
              
  






                                              
     



                                    

     


                                 
  
                            

                                                                    

                                  


                                                                  

                                
                
  
                                                        

                                         
  











                                                                                     
  
                              
                           
              
  
                                         
                      
                                  
      


                                                        

                                                                     



                                                 
                      

                      




                           


                                                            
   
                             
 
           



                               
                                         

                        
                  
                   
                                

                    


 






                                              





                                                       
                                               
   
                                         
 
                    
                          




                                        









                                    
                                             
  
             



   




                                                           
                                  
                                                 
 

                                
  
                                                 






                                                                                             









                                          
                                       


             



             





                           
   

                                                    



                                                   
                                  
                                                        
 

                           



                           
  


                                 
  
                                    



                                      

                            
              
             
     

                                                                                                                

                
     
                                      
  





                          






                      





                      























                                                       
                                                     



                
                                                                        












                                                          
                          
                                                                        


                                                                                                              


















                                       






                                                       
                                         





                     


                                               




                                         
                             




                                           


                                                               




























                                            

                     



               

   

                                   


                                          

                       
                                                             



          




                                                                 


                                                                          


                                                           

                                                             
                                                           

                                                                       


                                                        
   











                                                                        












                                                                           


                                      


                               
                                                            
                         
                          




                                



                                                        





                                          



                                           
                                             
                                              
            






               
         
  










                                                             
     
                                  

                                          

     






                                           
              


                      

                           
                                    

                        
                                                                               
                     
                       
         
                    


                                                                      

     
      

         

                      

                    
             
            
      

                  
              
 
 
/**
 * coopgammad -- Cooperative gamma server
 * Copyright (C) 2016  Mattias Andrée (maandree@kth.se)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "arg.h"
#include "util.h"
#include "state.h"
#include "servers/master.h"
#include "servers/kernel.h"
#include "servers/crtc.h"
#include "servers/gamma.h"
#include "servers/coopgamma.h"

#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>



/**
 * Number put in front of the marshalled data
 * so the program an detect incompatible updates
 */
#define MARSHAL_VERSION  0



#ifndef GCC_ONLY
# if defined(__GNUC__) && !defined(__clang__)
#  define GCC_ONLY(...)  __VA_ARGS__
# else
#  define GCC_ONLY(...)  /* nothing */
# endif
#endif



/**
 * Lists all function recognised adjustment methods,
 * will call macro X with the code for the each
 * adjustment method as the first argument and
 * corresponding name as the second argument
 */
#define LIST_ADJUSTMENT_METHODS				\
  X(LIBGAMMA_METHOD_DUMMY,                "dummy")	\
  X(LIBGAMMA_METHOD_X_RANDR,              "randr")	\
  X(LIBGAMMA_METHOD_X_VIDMODE,            "vidmode")	\
  X(LIBGAMMA_METHOD_LINUX_DRM,            "drm")	\
  X(LIBGAMMA_METHOD_W32_GDI,              "gdi")	\
  X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz")



/**
 * Used by initialisation functions as their return type. If a
 * value not listed here is returned by such function, it is the
 * exit value the process shall exit with as soon as possible.
 */
enum init_status
{
  /**
   * Initialisation was successful
   */
  INIT_SUCCESS = -1,
  
  /**
   * Initialisation failed
   */
  INIT_FAILURE = -2,
  
  /**
   * Server is already running
   */
  INIT_RUNNING = -3,
};



/**
 * The pathname of the PID file
 */
extern char* restrict pidpath;
char* restrict pidpath = NULL;

/**
 * The pathname of the socket
 */
extern char* restrict socketpath;
char* restrict socketpath = NULL;



/**
 * Called when the process receives
 * a signal telling it to re-execute
 * 
 * @param  signo  The received signal
 */
static void sig_reexec(int signo)
{
  int saved_errno = errno;
  reexec = 1;
  signal(signo, sig_reexec);
  errno = saved_errno;
}


/**
 * Called when the process receives
 * a signal telling it to terminate
 * 
 * @param  signo  The received signal
 */
static void sig_terminate(int signo)
{
  terminate = 1;
  (void) signo;
}


/**
 * Called when the process receives
 * a signal telling it to disconnect
 * from or reconnect to the site
 * 
 * @param  signo  The received signal
 */
static void sig_connection(int signo)
{
  int saved_errno = errno;
  connection = signo - SIGRTMIN + 1;
  signal(signo, sig_connection);
  errno = saved_errno;
}


/**
 * Called when the process receives
 * a signal telling it to dump its
 * state to stderr
 * 
 * @param  signo  The received signal
 */
static void sig_info(int signo)
{
  int saved_errno = errno;
  char* env;
  signal(signo, sig_info);
  env = getenv("COOPGAMMAD_PIDFILE_TOKEN");
  fprintf(stderr, "PID file token: %s\n", env ? env : "(null)");
  fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)");
  fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)");
  state_dump();
  errno = saved_errno;
}


/**
 * Parse adjustment method name (or stringised number)
 * 
 * @param   arg  The adjustment method name (or stringised number)
 * @return       The adjustment method, -1 (negative) on error
 */
GCC_ONLY(__attribute__((nonnull)))
static int get_method(const char* restrict arg)
{
#if LIBGAMMA_METHOD_MAX > 5
# warning libgamma has added more adjustment methods
#endif
  
  const char* restrict p;
  
#define X(C, N)  if (!strcmp(arg, N))  return C;
  LIST_ADJUSTMENT_METHODS;
#undef X
  
  if (!*arg || (/* avoid overflow: */ strlen(arg) > 4))
    goto bad;
  for (p = arg; *p; p++)
    if (('0' > *p) || (*p > '9'))
      goto bad;
  
  return atoi(arg);
  
 bad:
  fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg);
  errno = 0;
  return -1;
}


/**
 * Set up signal handlers
 * 
 * @return  Zero on success, -1 on error
 */
static int set_up_signals(void)
{
  if ((signal(SIGUSR1,      sig_reexec)     == SIG_ERR) ||
      (signal(SIGUSR2,      sig_info)       == SIG_ERR) ||
#if defined(SIGINFO)
      (signal(SIGINFO,      sig_info)       == SIG_ERR) ||
#endif
      (signal(SIGTERM,      sig_terminate)  == SIG_ERR) ||
      (signal(SIGRTMIN + 0, sig_connection) == SIG_ERR) ||
      (signal(SIGRTMIN + 1, sig_connection) == SIG_ERR))
    return -1;
  return 0;
}


/**
 * Fork the process to the background
 * 
 * @param   keep_stderr  Keep stderr open?
 * @return               An `enum init_status` value or an exit value
 */
static enum init_status daemonise(int keep_stderr)
{
  pid_t pid;
  int fd = -1, saved_errno;
  int notify_rw[2] = { -1, -1 };
  char a_byte = 0;
  ssize_t got;
  
  if (pipe(notify_rw) < 0)
    goto fail;
  if (notify_rw[0] <= STDERR_FILENO)
    if ((notify_rw[0] = dup2atleast(notify_rw[0], STDERR_FILENO + 1)) < 0)
      goto fail;
  if (notify_rw[1] <= STDERR_FILENO)
    if ((notify_rw[1] = dup2atleast(notify_rw[1], STDERR_FILENO + 1)) < 0)
      goto fail;
  
  if ((pid = fork()) < 0)
    goto fail;
  if (pid > 0)
    {
      /* Original process (parent): */
      waitpid(pid, NULL, 0);
      close(notify_rw[1]), notify_rw[1] = -1;
      got = read(notify_rw[0], &a_byte, 1);
      if (got < 0)
	goto fail;
      close(notify_rw[0]);
      errno = 0;
      return got == 0 ? INIT_FAILURE : (enum init_status)0;
    }
  
  /* Intermediary process (child): */
  close(notify_rw[0]), notify_rw[0] = -1;
  if (setsid() < 0)
    goto fail;
  if ((pid = fork()) < 0)
    goto fail;
  if (pid > 0)
    {
      /* Intermediary process (parent): */
      return (enum init_status)0;
    }
  
  /* Daemon process (child): */
  
  /* Replace std* with /dev/null */
  fd = open("/dev/null", O_RDWR);
  if (fd < 0)
    goto fail;
#define xdup2(s, d)  do if (s != d) { close(d); if (dup2(s, d) < 0) goto fail; } while (0)
  xdup2(fd, STDIN_FILENO);
  xdup2(fd, STDOUT_FILENO);
  if (keep_stderr)
    xdup2(fd, STDERR_FILENO);
  if (fd > STDERR_FILENO)
    close(fd);
  fd = -1;
  
  /* Update PID file */
  fd = open(pidpath, O_WRONLY);
  if (fd < 0)
    goto fail;
  if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
    goto fail;
  close(fd), fd = -1;
  
  /* Notify */
  if (write(notify_rw[1], &a_byte, 1) <= 0)
    goto fail;
  close(notify_rw[1]);
  
  return INIT_SUCCESS;
 fail:
  saved_errno = errno;
  if (fd >= 0)            close(fd);
  if (notify_rw[0] >= 0)  close(notify_rw[0]);
  if (notify_rw[1] >= 0)  close(notify_rw[1]);
  errno = saved_errno;
  return INIT_FAILURE;
}


/**
 * Initialise the process
 * 
 * @param   foreground   Keep process in the foreground
 * @param   keep_stderr  Keep stderr open
 * @param   query        Was -q used, see `main` for description
 * @return               An `enum init_status` value or an exit value
 */
static enum init_status initialise(int foreground, int keep_stderr, int query)
{
  struct rlimit rlimit;
  size_t i, n;
  sigset_t mask;
  int s;
  enum init_status r;
  
  /* Zero out some memory so it can be destoried safely. */
  memset(&site, 0, sizeof(site));
  
  if (!query)
    {
      /* Close all file descriptors above stderr */
      if (getrlimit(RLIMIT_NOFILE, &rlimit) || (rlimit.rlim_cur == RLIM_INFINITY))
	n = 4 << 10;
      else
	n = (size_t)(rlimit.rlim_cur);
      for (i = STDERR_FILENO + 1; i < n; i++)
	close((int)i);
      
      /* Set umask, reset signal handlers, and reset signal mask */
      umask(0);
      for (s = 1; s < _NSIG; s++)
	signal(s, SIG_DFL);
      if (sigfillset(&mask))
	perror(argv0);
      else
	sigprocmask(SIG_UNBLOCK, &mask, NULL);
    }
  
  /* Get method */
  if ((method < 0) && (libgamma_list_methods(&method, 1, 0) < 1))
    return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1;
  
  /* Go no further if we are just interested in the adjustment method and site */
  if (query)
    return INIT_SUCCESS;
  
  /* Get site */
  if (initialise_site() < 0)
    goto fail;
  
  /* Get PID file and socket pathname */
  if (!(pidpath   = get_pidfile_pathname()) ||
      !(socketpath = get_socket_pathname()))
    goto fail;
  
  /* Create PID file */
  if ((r = create_pidfile(pidpath)) < 0)
    {
      free(pidpath), pidpath = NULL;
      if (r == -2)
	return INIT_RUNNING;
      goto fail;
    }
  
  /* Get partitions and CRTC:s */
  if (initialise_crtcs() < 0)
    goto fail;
  
  /* Get CRTC information */
  if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs))))
    goto fail;
  if (initialise_gamma_info() < 0)
    goto fail;
  
  /* Sort outputs */
  qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name);
  
  /* Load current gamma ramps */
  store_gamma();
  
  /* Preserve current gamma ramps at priority=0 if -p */
  if (preserve && (preserve_gamma() < 0))
    goto fail;
  
  /* Create socket and start listening */
  if (create_socket(socketpath) < 0)
    goto fail;
  
  /* Get the real pathname of the process's binary, in case
   * it is relative, so we can re-execute without problem. */
  if ((*argv0 != '/') && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL)))
    goto fail;
  
  /* Change directory to / to avoid blocking umounting */
  if (chdir("/") < 0)
    perror(argv0);
  
  /* Set up signal handlers */
  if (set_up_signals() < 0)
    goto fail;
  
  /* Place in the background unless -f */
  if (foreground == 0)
    return daemonise(keep_stderr);
  else
    {
      /* Signal the spawner that the service is ready */
      close(STDOUT_FILENO);
      /* Avoid potential catastrophes that would occur if a library
       * that is being used was so mindless as to write to stdout. */
      if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0)
	perror(argv0);
    }
  
  return INIT_SUCCESS;
 fail:
  return INIT_FAILURE;
}


/**
 * Deinitialise the process
 * 
 * @param  full  Perform a full deinitialisation, shall be
 *               done iff the process is going to re-execute
 */
static void destroy(int full)
{
  if (full)
    {
      disconnect_all();
      close_socket(socketpath);
      free(argv0_real);
      if ((outputs != NULL) && connected)
	restore_gamma();
    }
  state_destroy();
  free(socketpath);
  if (full && (pidpath != NULL))
    unlink(pidpath);
  free(pidpath);
}



#if defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-align"
#endif


/**
 * Marshal the state of the process
 * 
 * @param   buf  Output buffer for the marshalled data,
 *               `NULL` to only measure how large the
 *               buffer needs to be
 * @return       The number of marshalled bytes
 */
static size_t marshal(void* restrict buf)
{
  size_t off = 0, n;
  char* restrict bs = buf;
  
  if (bs != NULL)
    *(int*)(bs + off) = MARSHAL_VERSION;
  off += sizeof(int);
  
  n = strlen(pidpath) + 1;
  if (bs != NULL)
    memcpy(bs + off, pidpath, n);
  off += n;
  
  n = strlen(socketpath) + 1;
  if (bs != NULL)
    memcpy(bs + off, socketpath, n);
  off += n;
  
  off += state_marshal(bs ? bs + off : NULL);
  
  return off;
}


/**
 * Unmarshal the state of the process
 * 
 * @param   buf  Buffer with the marshalled data
 * @return       The number of marshalled bytes, 0 on error
 */
GCC_ONLY(__attribute__((nonnull)))
static size_t unmarshal(const void* restrict buf)
{
  size_t off = 0, n;
  const char* restrict bs = buf;
  
  if (*(const int*)(bs + off) != MARSHAL_VERSION)
    {
      fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0);
      errno = 0;
      return 0;
    }
  off += sizeof(int);
  
  n = strlen(bs + off) + 1;
  if (!(pidpath = memdup(bs + off, n)))
    return 0;
  off += n;
  
  n = strlen(bs + off) + 1;
  if (!(socketpath = memdup(bs + off, n)))
    return 0;
  off += n;
  
  off += n = state_unmarshal(bs + off);
  if (n == 0)
    return 0;
  
  return off;
}


#if defined(__clang__)
# pragma GCC diagnostic pop
#endif



/**
 * Do minimal initialisation, unmarshal the state of
 * the process and merge with new state
 * 
 * @param   statefile  The state file
 * @return             Zero on success, -1 on error
 */
GCC_ONLY(__attribute__((nonnull)))
static int restore_state(const char* restrict statefile)
{
  void* marshalled = NULL;
  int fd = -1, saved_errno;
  size_t r, n;
  
  if (set_up_signals() < 0)
    goto fail;
  
  fd = open(statefile, O_RDONLY);
  if (fd < 0)
    goto fail;
  
  if (!(marshalled = nread(fd, &n)))
    goto fail;
  close(fd), fd = -1;
  unlink(statefile), statefile = NULL;
  
  r = unmarshal(marshalled);
  if (r == 0)
    goto fail;
  if (r != n)
    {
      fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n",
	      argv0, n > r ? "larger" : "smaller", r, n);
      errno = 0;
      goto fail;
    }
  free(marshalled), marshalled = NULL;
  
  if (connected)
    {
      connected = 0;
      if (reconnect() < 0)
	goto fail;
    }
  
  return 0;
 fail:
  saved_errno = errno;
  if (fd >= 0)
    close(fd);
  free(marshalled);
  errno = saved_errno;
  return -1;
}


/**
 * Reexecute the server
 * 
 * Returns only on failure
 * 
 * @return  Pathname of file where the state is stored,
 *          `NULL` if the state is in tact
 */
static char* reexecute(void)
{
  char* statefile = NULL;
  char* statebuffer = NULL;
  size_t buffer_size;
  int fd = -1, saved_errno;
  
  statefile = get_state_pathname();
  if (statefile == NULL)
    goto fail;
  
  buffer_size = marshal(NULL);
  statebuffer = malloc(buffer_size);
  if (statebuffer == NULL)
    goto fail;
  if (marshal(statebuffer) != buffer_size)
    {
      fprintf(stderr, "%s: internal error\n", argv0);
      errno = 0;
      goto fail;
    }
  
  fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
  if (fd < 0)
    goto fail;
  
  if (nwrite(fd, statebuffer, buffer_size) != buffer_size)
    goto fail;
  free(statebuffer), statebuffer = NULL;
  
  if ((close(fd) < 0) && (fd = -1, errno != EINTR))
    goto fail;
  fd = -1;
  
  destroy(0);
  
#if !defined(USE_VALGRIND)
  execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL);
#else
  execlp("valgrind", "valgrind", "--leak-check=full", argv0_real ? argv0_real : argv0, "- ", statefile, NULL);
#endif
  saved_errno = errno;
  free(argv0_real), argv0_real = NULL;
  errno = saved_errno;
  return statefile;
  
 fail:
  saved_errno = errno;
  free(statebuffer);
  if (fd >= 0)
    close(fd);
  if (statefile != NULL)
    unlink(statefile), free(statefile);
  errno = saved_errno;
  return NULL;
}



/**
 * Print the response for the -q option
 * 
 * @param   query  See -q for `main`, must be atleast 1
 * @return         Zero on success, -1 on error
 */
static int print_method_and_site(int query)
{
  const char* restrict methodname = NULL;
  char* p;
  
  if (query == 1)
    {
      switch (method)
	{
#define X(C, N)  case C: methodname = N; break;
	LIST_ADJUSTMENT_METHODS;
#undef X
	default:
	  if (printf("%i\n", method) < 0)
	    return -1;
	  break;
	}
      if (methodname != NULL)
	if (printf("%s\n", methodname) < 0)
	  return -1;
    }
  
  if (sitename == NULL)
    if ((sitename = libgamma_method_default_site(method)))
      if (!(sitename = memdup(sitename, strlen(sitename) + 1)))
	return -1;
  
  if (sitename != NULL)
    switch (method)
      {
      case LIBGAMMA_METHOD_X_RANDR:
      case LIBGAMMA_METHOD_X_VIDMODE:
	if ((p = strrchr(sitename, ':')))
	  if ((p = strchr(p, '.')))
	    *p = '\0';
	break;
      default:
	break;
      }
  
  if ((sitename != NULL) && (query == 1))
    if (printf("%s\n", sitename) < 0)
      return -1;
  
  if (query == 2)
    {
      site.method = method;
      site.site = sitename, sitename = NULL;
      socketpath = get_socket_pathname();
      if (socketpath == NULL)
	return -1;
      if (printf("%s\n", socketpath) < 0)
	return -1;
    }
  
  if (fflush(stdout))
    return -1;
  return 0;    
}



/**
 * Print usage information and exit
 */
#if defined(__GNU__) || defined(__clang__)
__attribute__((noreturn))
#endif
static void usage(void)
{
  printf("Usage: %s [-m method] [-s site] [-fkpq]\n", argv0);
  exit(1);
}


#if defined(__clang__)
# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command"
#endif


/**
 * Must not be started without stdin, stdout, or stderr (may be /dev/null)
 * 
 * argv[0] must refer to the real command name or pathname,
 * otherwise, re-execute will not work
 * 
 * The process closes stdout when the socket has been created
 * 
 * @signal  SIGUSR1     Re-execute to updated process image
 * @signal  SIGUSR2     Dump the state of the process to standard error
 * @signal  SIGINFO     Ditto
 * @signal  SIGTERM     Terminate the process gracefully
 * @signal  SIGRTMIN+0  Disconnect from the site
 * @signal  SiGRTMIN+1  Reconnect to the site
 * 
 * @param   argc  The number of elements in `argv`
 * @param   argv  Command line arguments. Recognised options:
 *                  -s SITENAME
 *                    The site to which to connect
 *                  -m METHOD
 *                    Adjustment method name or adjustment method number
 *                  -p
 *                    Preserve current gamma ramps at priority 0
 *                  -f
 *                    Do not fork the process into the background
 *                  -k
 *                    Keep stderr open
 *                  -q
 *                    Print the select (possiblity default) adjustment
 *                    method on the first line in stdout, and the
 *                    selected (possibility defasult) site on the second
 *                    line in stdout, and exit. If the site name is `NULL`,
 *                    the second line is omitted. This is indented to
 *                    be used by clients to figure out to which instance
 *                    of the service it should connect. Use twice to
 *                    simply ge the socket pathname, an a terminating LF.
 *                    By combining -q and -m you can enumerate the name
 *                    of all recognised adjustment method, start from 0
 *                    and work up until a numerical adjustment method is
 *                    returned.
 * @return        0: Successful
 *                1: An error occurred
 *                2: Already running
 */
int main(int argc, char** argv)
{
  int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r;
  char* statefile = NULL;
  char* statefile_ = NULL;
  
  ARGBEGIN
    {
    case 's':
      sitename = EARGF(usage());
      /* To simplify re-exec: */
      sitename = memdup(sitename, strlen(sitename) + 1);
      if (sitename == NULL)
	goto fail;
      break;
    case 'm':
      method = get_method(EARGF(usage()));
      if (method < 0)
	goto fail;
      break;
    case 'p':  preserve    = 1;      break;
    case 'f':  foreground  = 1;      break;
    case 'k':  keep_stderr = 1;      break;
    case 'q':  query = 1 + !!query;  break;
    case ' ': /* Internal, do not document */
      statefile = statefile_ = EARGF(usage());
      break;
    default:
      usage();
    }
  ARGEND;
  if (argc > 0)
    usage();
  
 restart:
  
  if (statefile == NULL)
    switch ((r = initialise(foreground, keep_stderr, query)))
      {
      case INIT_SUCCESS:  break;
      case INIT_RUNNING:  rc = 2;  /* fall through */
      case INIT_FAILURE:  goto fail;
      default:            return r;
      }
  else if (restore_state(statefile) < 0)
    goto fail;
  else
    {
      if (statefile != statefile_)
	free(statefile);
      unlink(statefile), statefile = NULL;
    }
  
  if (query)
    {
      if (print_method_and_site(query) < 0)
	goto fail;
      goto done;
    }
  
 reenter_loop:
  if (main_loop() < 0)
    goto fail;
  
  if (reexec && !terminate)
    {
      if ((statefile = reexecute()))
	{
	  perror(argv0);
	  fprintf(stderr, "%s: restoring state without re-executing\n", argv0);
	  reexec = 0;
	  goto restart;
	}
      perror(argv0);
      fprintf(stderr, "%s: continuing without re-executing\n", argv0);
      reexec = 0;
      goto reenter_loop;
    }
  
 done:
  rc = 0;
 deinit:
  if (statefile)
    unlink(statefile);
  if (reexec)
    free(statefile);
  destroy(1);
  return rc;
 fail:
  if (errno != 0)
    perror(argv0);
  goto deinit;
}