/* Entry for the GNATS system.
   Copyright (C) 1993 Free Software Foundation, Inc.
   Contributed by Tim Wicinski (wicinski@barn.com).

This file is part of GNU GNATS.

GNU GNATS 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 2, or (at your option)
any later version.

GNU GNATS 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 GNU GNATS; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <sys/types.h>

#include <sys/stat.h>

#include <getopt.h>
#include <ansidecl.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif

#include "gnats.h"
#include "pathmax.h"

static void version		PARAMS((void));
static int check_pr		PARAMS((FILE*));
static int modify_pr		PARAMS((FILE*));
static int lock_pr		PARAMS((char*, char*));
static int unlock_pr		PARAMS((char*));
static char *get_path		PARAMS((char*));
static void usage		PARAMS((void));

extern char *basename		PARAMS((const char*));
extern char *xmalloc		PARAMS((size_t));

extern char *version_string;

/* Name of this program.  */
char *program_name;

/* Whether we should ignore the index and drop the PR on stdin into the
   database as a new one.  */
int force = 0;

enum {
  NONE,
  LOCK,
  UNLOCK,
  LOCKDB,
  UNLOCKDB,
  CHECK,
  MODIFY
} edit_options;

struct option long_options[] =
{
  {"lock", 1, NULL, 'l'},
  {"unlock", 0, NULL, 'u'},
  {"lockdb", 0, NULL, 'L'},
  {"unlockdb", 0, NULL, 'U'},
  {"check", 0, NULL, 'c'},
  {"directory", 1, NULL, 'd'},
  {"filename", 1, NULL, 'f'},
  {"version", 0, NULL, 'V'},
  {"help", 0, NULL, 'h'},
  {NULL, 0, NULL, 0}
};

void
main (argc, argv)
     int argc;
     char **argv;
{

  FILE *fp = stdin;
  int option;
  char *fname = NULL;
  int result = 0;
  char *username = NULL;

  program_name = basename (argv[0]);

  edit_options = MODIFY;

  while ((option = getopt_long (argc, argv, "l:uhcd:f:VF",
				long_options, (int *)0)) != EOF)
    {
      switch (option)
	{
	case 'd':
	  gnats_root = optarg;
	  break;

	case 'f':
	  fp = fopen (optarg, "r");
	  /* If they gave a bogus argument, then exit right away; we don't
	     want to send mail to gnats-admin every time someone tries to
	     edit a bogus PR number.  */
	  if (fp == (FILE *)NULL)
	    exit (2);
	  break;

	case 'F':
	  force = 1;
	  break;

	case 'l': 
	  edit_options = LOCK;
	  username = optarg;
	  break;

	case 'u':
	  edit_options = UNLOCK;
	  break;

	case 'L':
	  edit_options = LOCKDB;
	  break;

	case 'U':
	  edit_options = UNLOCKDB;
	  break;

	case 'c':
	  edit_options = CHECK;
	  break; 

	case 'V':
	  version ();
	  exit (0);
	  break;
	  
	case 'h':
	  usage ();
	  exit (0);
	  break;
	  
	default:
	  usage ();
	  exit (3);
	  break;
	}
    }

  /* if there is another arg, then we take it to be a file name. */
  if (optind < argc)
    fname = argv[optind];
 
  configure ();
  init_gnats ();
  umask (022);
  
  block_signals ();

  if (edit_options != UNLOCKDB)
    lock_gnats ();
  
  switch (edit_options) {
    case LOCK:
      result = lock_pr (fname, username);
      break;
    case UNLOCK:
      result = unlock_pr (fname);
      break;
    case CHECK:
      result = check_pr (fp);
      break;
    case LOCKDB:
    case UNLOCKDB:
      result = 1; /* so that the program exits with 0 status */
      break;
    default: 
      result = modify_pr (fp);
      break;
  }

  fclose (fp);

  if (edit_options != LOCKDB)
    unlock_gnats ();

  unblock_signals ();

  exit (!result);
}

static int
modify_pr (fp)
    FILE *fp;
{
  FILE *outfile;
  struct stat buf;

  /* Where to keep the static index if necessary.  */
  Index *index_chain;

  char *path, *old_path, *lock_path = NULL;
  char *p;

  Index *new_index = (Index *) xmalloc (sizeof (Index));
  Index *s = (Index *) xmalloc (sizeof (Index));
  Index *i, *prev_index = NULL, *old_index = NULL;

  /* this will print the bad values out. then return and exit.  */
  if (!check_pr (fp))
    return 0;

  /* build the new index entry.  */
  new_index->category = field_value (CATEGORY);
  new_index->number = field_value (NUMBER);
  new_index->submitter = field_value (SUBMITTER);
  new_index->responsible = field_value (RESPONSIBLE);
  if (*new_index->responsible)
    {
      p = (char *) strchr (new_index->responsible, ' ');
      if (p != NULL)
	{
	  int x = (int) (p - new_index->responsible);

	  /* Copy it instead of chopping off the full name in
	     the responsible field's value itself, so the PR
	     gets written out with what was read in.  */
	  p = (char *) strdup (new_index->responsible);
	  new_index->responsible = p;
	  p += x;
	  *p = '\0';
	}
    }
  new_index->state = field_value (STATE);
  new_index->confidential = field_value (CONFIDENTIAL);
  new_index->severity = field_value (SEVERITY);
  new_index->priority = field_value (PRIORITY);

  s->number = field_value (NUMBER);

  if (! force)
    {
      index_chain = get_index ();
 
      /* Let's see if we can find what we are looking for.  */
      for (i = index_chain; i ; i = i->next)
	{
	  if (strcmp (i->number, s->number) == 0)
	    {
	      /* replace the current index with the new. */ 
	      old_index = i;
	      if (prev_index)
		prev_index->next = new_index;
	      else
		prev_index = new_index;
	      new_index->next = i->next;
	      break; 
	    }
	  prev_index = i;
	}

      if (old_index == NULL) 
	punt (1, "%s: can't find index entry for %s\n", program_name, s->number);

      lock_path = (char *) xmalloc (PATH_MAX);
      sprintf (lock_path, "%s/%s/%s.lock", gnats_root, old_index->category,
	       old_index->number);

      if (stat (lock_path, &buf) < 0)
	punt (1, "%s: can't stat lock file %s: %s",
	      program_name, lock_path, strerror (errno));

      old_path = (char *) xmalloc (PATH_MAX);
      sprintf (old_path, "%s/%s/%s",  gnats_root, old_index->category, 
	       old_index->number);

      /* set this to be the file to be saved. now called .old. */
      sprintf (lock_path, "%s/%s/%s.old", gnats_root, old_index->category,
	       old_index->number);
    }
  else
    add_to_index ();

  path = (char *) xmalloc (PATH_MAX);

  /* check to see if the category changes, and if so, write the 
     new file out, and unlink the old one.  */
  if (! force)
    {
      if (strcmp (old_index->category, new_index->category) != 0)
	sprintf (path, "%s/%s/%s",  gnats_root, new_index->category, 
		 new_index->number);
      else
	sprintf (path, "%s/%s/%s", gnats_root, old_index->category,
		 old_index->number);
    }
  else
    sprintf (path, "%s/%s/%s", gnats_root, new_index->category,
	     new_index->number);

  if (! force && rename (old_path, lock_path) < 0)
    {
      if (errno != EXDEV)
	punt (1, "%s: could not rename %s: %s\n", program_name,
	      old_path, strerror (errno));

      if (copy_file (old_path, lock_path))
	punt (1, "%s: could not copy %s to %s: %s\n", program_name,
		   old_path, lock_path, strerror (errno));

      /* Don't complain if this fails, since trying to write to it will give
	 us the diagnostic if it's really serious.  */
      unlink (old_path);
    }

  /* Now build the file.  */
  outfile = fopen (path, "w+");
  if (outfile == (FILE *) NULL)
    punt (1,  "%s: can't write the PR to %s: %s", program_name, path,
	  strerror (errno));

  write_header (outfile, NUM_HEADER_ITEMS);
  fprintf (outfile, "\n");
  write_pr (outfile, NUM_PR_ITEMS);

  if (fclose (outfile) == EOF)
    punt (1, "%s: error writing out PR %s: %s", program_name, path,
	  strerror (errno));

  if (! force)
    unlink (lock_path);
  
  /* unlink the old file, if it is in another category dir. */
  if (! force)
    {
      if (strcmp (old_index->category, new_index->category) != 0)
	unlink (old_path);

      /* write out the new index.  */
      write_index (index_chain);
    }

  return 1;
}

/* check_pr - reads the PR in, searches for bogus enum types, lack of
   a PR number, and prints out the bogus info.  */

static int
check_pr (fp)
     FILE* fp;
{
  struct bad_enum *enums, *e;
  char *p;

  /* Read the message header in.  */
  read_header (fp);
  read_pr (fp, 0);

  enums = check_enum_types (2);
  p = field_value (NUMBER);

  if (enums || *p == '-')
    {
      printf ("pr-edit: invalid fields");
      
      if (enums)
	for (e = enums; e; e = e->next)
	  printf ("%s", e->msg);
      
      /* It was missing its number! */
      if (*p == '-')
	printf ("Number: %s\n", p);
      
      return 0;
    }

  return 1;
}

static int
lock_pr (fname, user)
     char *fname, *user;
{
  char *lock_path;
  struct stat buf;
  FILE *ifp;

  if (fname == NULL)
    return 0;

  lock_path = get_path (fname);

  if (stat (lock_path, &buf) == 0)
    {
      FILE *fp = fopen (lock_path, "r");
      char buf[1024];

      /* If we couldn't open it for reading, something else is hosed,
	 so just bail now.  */
      if (fp == (FILE *) NULL)
	return 0;

      fscanf (fp, "%s", buf);
      fprintf (stderr, "%s: PR %s locked by %s\n", program_name,
	       fname, buf);
      fclose (fp);
      return 0;
    }

  ifp = fopen (lock_path, "w");
  if (ifp == NULL)
    punt (1, "%s: can't create lock file %s", program_name, lock_path);

  fprintf (ifp, "%s\n", user);
  fclose (ifp);

  return 1;
}

static int
unlock_pr (fname)
    char *fname;
{
  char *lock_path;
  struct stat buf;

  if (fname == NULL)
    return 0;

  lock_path = get_path (fname);

  if (lock_path == NULL)
    {
      fprintf (stderr, "%s: no such PR `%s'\n", program_name, fname);
      return 0;
    }

  if (stat (lock_path, &buf) != 0)
    {
      fprintf (stderr, "%s: lock file %s does not exist\n",
	       program_name, lock_path);
      return 0;
    }

  if (unlink (lock_path) == -1)
    {
      fprintf (stderr, "%s: can't delete lock file %s\n",
	       program_name, lock_path);
      return 0;
    }

  return 1;
}

static void
usage ()
{
  fprintf (stderr, "\
Usage: %s [-cuVFh] [-d directory] [-f filename] [-l username]\n\
          [--directory=directory] [--filename=filename] [--lock=user]\n\
          [--unlock] [--help] [--version] [--check] [PR]\n", program_name);
}

static void
version ()
{
  printf ("gnats-edit %s\n", version_string);
}

/* returns actually the path of the lock file.  */
static char*
get_path (fname)
    char *fname;
{
  char *p;
  FILE *ifp;
  char *category = NULL; 
  char *path;

  if (fname == NULL)
    return NULL;

  p = (char *) strchr (fname, '/');
  if (p == NULL)
    {
      ifp = open_index ();
      if (ifp == NULL)
	return NULL;

      category = find_pr_category (ifp, fname);
      close_index (ifp);

      if (category == NULL)
        return NULL;
    }

  path = (char *) xmalloc (PATH_MAX);
  if (category)
    sprintf (path, "%s/%s/%s.lock", gnats_root, category, fname);
  else
    sprintf (path, "%s/%s.lock", gnats_root, fname);

  return path;
}
