/* notice.c -- Request file change notifiction messages.
   Copyright (C) 2002 Wolfgang J"ahrling <wolfgang@pro-linux.de>

   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 2 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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */

#define _GNU_SOURCE 1

#include <hurd.h>
#include <hurd/fs.h>
#include <hurd/ports.h>
#include <argp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <error.h>

const char *argp_program_version = "notice 0.2";
const char *argp_program_bug_address = "wolfgang@pro-linux.de";

static struct port_bucket *bucket;
static struct port_class *class;

/* Command to execute for each change.  */
static const char *cmd_template = "echo %t `date` '%f'";

struct notice_handle
{
  struct port_info pi;

  int changes;  /* Changes which won't be ignored.  See below.  */
  char name[1]; /* Actually, it will be larger.  */
};

/* The CHANGES field above is a set of those flags.
   See <hurd/hurd_types.h> for details.  */
#define NOTICE_NULL     (1 << FILE_CHANGED_NULL)
#define NOTICE_WRITE    (1 << FILE_CHANGED_WRITE)
#define NOTICE_EXTEND   (1 << FILE_CHANGED_EXTEND)
#define NOTICE_TRUNCATE (1 << FILE_CHANGED_TRUNCATE)
#define NOTICE_META     (1 << FILE_CHANGED_META)
#define NOTICE_ALL (NOTICE_NULL | NOTICE_WRITE | NOTICE_EXTEND \
		   | NOTICE_TRUNCATE | NOTICE_META)

static struct argp_option options[] =
{
  {"null",      'n', 0, 0, "Regard startup message of the following files"},
  {"no-null",   'N', 0, 0, "Ignore startup message of the following files"},
  {"write",     'w', 0, 0, "Regard file writes on the following files"},
  {"no-write",  'W', 0, 0, "Ignore file writes on the following files"},
  {"extend",    'e', 0, 0, "Regard extensions of the following files"},
  {"no-extend", 'E', 0, 0, "Ignore extensions of the following files"},
  {"trunc",     't', 0, 0, "Regard truncation of the following files"},
  {"no-trunc",  'T', 0, 0, "Ignore truncation of the following files"},
  {"meta",      'm', 0, 0, "Regard changes of meta information"},
  {"no-meta",   'M', 0, 0, "Ignore changes of meta information"},
  {"all",       'a', 0, 0, "Regard all changes on the following files"},
  {"none",      'A', 0, 0, "Ignore all changes on the following files"},

  {"command",   'c', "TEMPLATE",0,"Shell command to execute for each change"},
  {0}
};

static const char *args_doc = "FILE [FILE-OR-OPTION...]";
static const char *doc = "Request file change notification messages.";

static struct flag_and_letter
{
  char letter;
  int flag;
} flags_and_letters[] =
{
  {'n', NOTICE_NULL},
  {'w', NOTICE_WRITE},
  {'e', NOTICE_EXTEND},
  {'t', NOTICE_TRUNCATE},
  {'m', NOTICE_META},
  {'a', NOTICE_ALL},
  {0}
};

/* Converting flags to letters and vice versa.  */
#define CONVERT_X_TO_Y(x, y, txt, defretval)			\
static typeof (flags_and_letters->y)				\
convert_ ## x ## _to_ ## y (typeof (flags_and_letters->x) arg)	\
{								\
  struct flag_and_letter *fl;					\
								\
  for (fl = flags_and_letters; fl->letter; fl++)		\
    if (arg == fl->x)						\
      return fl->y;						\
  error (0, EGRATUITOUS, txt, arg);				\
  return defretval;						\
}
CONVERT_X_TO_Y (letter, flag, "Unknown notification type %c", 0)
CONVERT_X_TO_Y (flag, letter, "Unknown flag 0x%x", '-')

static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  static int changes = NOTICE_ALL;

  switch (key)
    {
    case 'a' ... 'z':
      if (key != 'c')
	changes |= convert_letter_to_flag (key);
      else
	/* Special case --command="...".  */
	cmd_template = arg;
      break;

    case 'A' ... 'Z':
      changes &= ~convert_letter_to_flag (tolower (key));
      break;

    case ARGP_KEY_ARG:
      {
	file_t file;
	struct notice_handle *notice;
	error_t err;

	/* Open the file.  */
	file = file_name_lookup (arg, 0, 0);
	if (file == MACH_PORT_NULL)
	  argp_error (state, arg);

	/* Create the notification port.  */
	err = ports_create_port (class, bucket,
				 sizeof (struct notice_handle) + strlen (arg),
				 &notice);
	if (err)
	  argp_error (state, "Could not allocate port");
	/* Record the file name and the changes we are interested in.  */
	strcpy (notice->name, arg);
	notice->changes = changes;

	/* Request notification messages.  */
	err = file_notice_changes (file, ports_get_right (notice),
				   MACH_MSG_TYPE_MAKE_SEND);
	if (err)
	  argp_error (state, arg);
	break;
      }

    default:
      return ARGP_ERR_UNKNOWN;
    }

  return 0;
}

static int notice_demuxer (mach_msg_header_t *, mach_msg_header_t *);

int
main (int argc, char **argv)
{
  bucket = ports_create_bucket ();
  class = ports_create_class (NULL, NULL);

  /* Process arguments.  */
  {
    struct argp argp = {options, parse_opt, args_doc, doc};
    argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0);
  }

  /* Be a server.  */
  ports_manage_port_operations_one_thread (bucket, notice_demuxer, 0);

  return 0;
}

/* Called by MiG.  */
struct notice_handle *
notice_port_to_handle (mach_port_t port)
{
  return ports_lookup_port (bucket, port, NULL);
}

/* Called by MiG.  */
struct notice_handle *
notice_payload_to_handle (unsigned long payload)
{
  return ports_lookup_payload (bucket, payload, NULL);
}

error_t
S_file_changed (struct notice_handle *notice, file_changed_type_t change,
		off_t start, off_t end)
{
  if ((1 << change) & notice->changes) /* Are we interested?  */
    {
      size_t size = strlen (cmd_template) + strlen (notice->name) + 2;
      char *cmd = malloc (size);
      const char *src;
      char *dst;

      /* Construct command.  */
      for (src = cmd_template, dst = cmd;
	   *src != '\0';
	   src++, dst++)
	{
	  if (*src != '%')
	    *dst = *src;
	  else
	    {
	      src++;
	      switch (*src)
		{
		case '%':
		  /* Literal percent.  */
		  *dst = '%';
		  break;

		case 't':
		  /* Notification type.  */
		  *dst = convert_flag_to_letter (1 << change);
		  break;

		case 'f':
		  /* File name.  */
		  {
		    size_t offset = dst - cmd;
		    size_t name_len = strlen (notice->name);

		    if ((offset + name_len + 1) > size)
		      {
			size += name_len;
			cmd = realloc (cmd, size);
			dst = cmd + offset;
		      }
		    memcpy (dst, notice->name, name_len);
		    dst += name_len - 1;
		  }
		}
	    }
	}
      *dst = '\0';
      system (cmd);
    }
  return 0;
}

error_t
S_dir_changed (struct notice_handle *notice, dir_changed_type_t change,
	       string_t name)
{
  return EOPNOTSUPP;
}

static int
notice_demuxer (mach_msg_header_t *inp, mach_msg_header_t *outp)
{
  extern int fs_notify_server (mach_msg_header_t *, mach_msg_header_t *);

  return (fs_notify_server (inp, outp)
	  || ports_interrupt_server (inp, outp)
	  || ports_notify_server (inp, outp));
}
