/* GConf
 * Copyright (C) 2002 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "gconf.h"
#include "gconf-internals.h"
#include "gconf-sources.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <popt.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

struct poptOption options[] = {
  { 
    NULL, 
    '\0', 
    POPT_ARG_INCLUDE_TABLE, 
    poptHelpOptions,
    0, 
    N_("Help options"), 
    NULL 
  },
  {
    NULL,
    '\0',
    0,
    NULL,
    0,
    NULL,
    NULL
  }
};

static void     show_fatal_error_dialog (const char *format,
                                         ...) G_GNUC_PRINTF (1, 2);
static gboolean check_file_locking (void);
static gboolean check_gconf (void);

int 
main (int argc, char** argv)
{
  poptContext ctx;
  gint nextopt;
  
  ctx = poptGetContext ("gconf-sanity-check-1", argc, (const char **) argv, options, 0);

  poptReadDefaultConfig (ctx, TRUE);

  while ((nextopt = poptGetNextOpt(ctx)) > 0)
    /*nothing*/;

  if (nextopt != -1) 
    {
      g_printerr (_("Error on option %s: %s.\nRun '%s --help' to see a full list of available command line options.\n"),
                  poptBadOption(ctx, 0),
                  poptStrerror(nextopt),
                  argv[0]);
      return 1;
    }

  poptFreeContext (ctx);

  if (!check_file_locking ())
    return 1;

  if (!check_gconf ())
    return 1;
  
  return 0;
}

/* Your basic Stevens cut-and-paste */
static int
lock_reg (int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
  struct flock lock;

  lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
  lock.l_start = offset; /* byte offset relative to whence */
  lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
  lock.l_len = len; /* #bytes, 0 for eof */

  return fcntl (fd, cmd, &lock);
}

#define lock_entire_file(fd) \
  lock_reg ((fd), F_SETLK, F_WRLCK, 0, SEEK_SET, 0)
#define unlock_entire_file(fd) \
  lock_reg ((fd), F_SETLK, F_UNLCK, 0, SEEK_SET, 0)

static gboolean
check_file_locking (void)
{
  char *testfile;
  int fd;
  gboolean retval;

  testfile = g_strconcat (g_get_home_dir (),
                          "/.gconf-test-locking-file",
                          NULL);
  
  retval = FALSE;

  /* keep the open from failing due to non-writable old file or something */
  unlink (testfile);
  
  fd = open (testfile, O_WRONLY | O_CREAT, 0700);

  if (fd < 0)
    {      
      show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                                 "Could not open or create the file \"%s\"; this indicates "
                                 "that there may be a problem with your configuration, "
                                 "as many programs will need to create files in your "
                                 "home directory. The error was \"%s\" (errno = %d)."),
                               testfile, strerror (errno), errno);

      goto out;
    }

  if (lock_entire_file (fd) < 0)
    {      
      show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                                 "Could not lock the file \"%s\"; this indicates "
                                 "that there may be a problem with your operating system "
                                 "configuration. If you have an NFS-mounted home directory, "
                                 "either the client or the server may be set up incorrectly. "
                                 "See the rpc.statd and rpc.lockd documentation. "
                                 "A common cause of this error is that the \"nfslock\" service has been disabled."
                                 "The error was \"%s\" (errno = %d)."),
                               testfile, strerror (errno), errno); 
      goto out;
    }

  retval = TRUE;

 out:
  close (fd);
  if (unlink (testfile) < 0)
    g_printerr (_("Can't remove file %s: %s\n"), testfile, strerror (errno));
  g_free (testfile);
  
  return retval;
}

static gboolean
check_gconf (void)
{
  char** addresses;
  int i;
  gchar* conffile;
  GError* error;
  gboolean retval;
  GConfLock *daemon_lock;
  char *gconfd_dir;
  char *lock_dir;
  
  retval = FALSE;
  conffile = NULL;
  gconfd_dir = NULL;
  lock_dir = NULL;
  
  /* If gconfd is already running, it's expected that we won't be able
   * to get locks etc., and everything is already fine.
   * Plus we can skip the slow sanity checks like resolve_address.
   */
  if (gconf_ping_daemon ())
    {
      retval = TRUE;
      goto out;
    }

  gconfd_dir = gconf_get_daemon_dir ();
  lock_dir = gconf_get_lock_dir ();
  
  if (mkdir (gconfd_dir, 0700) < 0 && errno != EEXIST)
    show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                               "Failed to create %s: %s"),
                             gconfd_dir, g_strerror (errno));  
  
  error = NULL;
  daemon_lock = gconf_get_lock (lock_dir, &error);
  if (daemon_lock == NULL)
    {
      g_assert (error);
      
      show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                                 "Failed to get a file lock: %s"),
                               error->message);

                                 
      g_error_free (error);

      goto out;
    }

  gconf_release_lock (daemon_lock, NULL);
  
  conffile = g_strconcat (GCONF_CONFDIR, "/path", NULL);

  error = NULL;
  addresses = gconf_load_source_path (conffile, &error);

  if (addresses == NULL)
    {
      show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                                 "No configuration sources in the configuration file \"%s\"; this means that preferences and other settings can't be saved. %s%s"),
                               conffile,
                               error ? _("Error reading the file: ") : "",
                               error ? error->message : "");

      if (error)
        g_error_free (error);

      goto out;
    }

  i = 0;
  while (addresses[i])
    {
      GConfSource *source;
      const char *address;

      address = addresses[i];
      
      error = NULL;      
      source = gconf_resolve_address (address, &error);

      if (error)
        {
          show_fatal_error_dialog (_("Please contact your system administrator to resolve the following problem:\n"
                                     "Could not resolve the address \"%s\" in the configuration file \"%s\": %s"),
                                   address, conffile, error->message);
          g_error_free (error);
          goto out;
        }

      gconf_source_free (source);
      
      ++i;
    }

  g_strfreev (addresses);
  
  retval = TRUE;
  
 out:
  g_free (conffile);
  g_free (gconfd_dir);
  g_free (lock_dir);
  
  return retval;
}

/* Well, in GConf 1 it doesn't actually show a dialog */
static void
show_fatal_error_dialog (const char *format,
                         ...)
{
  char *str;
  va_list args;

  va_start (args, format);
  str = g_strdup_vprintf (format, args);
  va_end (args);

  g_printerr ("%s\n", str);
}
