/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  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 (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#include "status.h"
#include "perf.h"
#include "misc.h"
#include "fdmisc.h"

#include "memdbg.h"

/*
 * printf-style interface for outputting status info
 */

static const char *
print_status_mode (unsigned int flags)
{
  switch (flags)
    {
    case STATUS_OUTPUT_WRITE:
      return "WRITE";
    case STATUS_OUTPUT_READ:
      return "READ";
    case STATUS_OUTPUT_READ|STATUS_OUTPUT_WRITE:
      return "READ/WRITE";
    default:
      return "UNDEF";
    }
}

struct status_output *
status_open (const char *filename,
	     const int refresh_freq,
	     const int msglevel,
	     const struct virtual_output *vout,
	     const unsigned int flags)
{
  struct status_output *so = NULL;
  if (filename || msglevel >= 0 || vout)
    {
      ALLOC_OBJ_CLEAR (so, struct status_output);
      so->flags = flags;
      so->msglevel = msglevel;
      so->vout = vout;
      so->fd = -1;
      buf_reset (&so->read_buf);
      event_timeout_clear (&so->et);
      if (filename)
        {
          switch (so->flags)
            {
            case STATUS_OUTPUT_WRITE:
              so->fd = platform_open (filename,
                                     O_CREAT | O_TRUNC | O_WRONLY,
                                     S_IRUSR | S_IWUSR);
              break;
            case STATUS_OUTPUT_READ:
              so->fd = platform_open (filename,
                                     O_RDONLY,
                                     S_IRUSR | S_IWUSR);
              break;
            case STATUS_OUTPUT_READ|STATUS_OUTPUT_WRITE:
              so->fd = platform_open (filename,
                                     O_CREAT | O_RDWR,
                                     S_IRUSR | S_IWUSR);
              break;
            default:
              ASSERT (0);
            }
	  if (so->fd >= 0)
	    {
	      so->filename = string_alloc (filename, NULL);
             set_cloexec (so->fd);

	      /* allocate read buffer */
	      if (so->flags & STATUS_OUTPUT_READ)
		so->read_buf = alloc_buf (512);
	    }
	  else
	    {
	      msg (M_WARN, "Note: cannot open %s for %s", filename, print_status_mode (so->flags));
	      so->errors = true;
	    }
	}
      else
	so->flags = STATUS_OUTPUT_WRITE;

      if ((so->flags & STATUS_OUTPUT_WRITE) && refresh_freq > 0)
	{
	  event_timeout_init (&so->et, refresh_freq, 0);
	}
    }
  return so;
}

bool
status_trigger (struct status_output *so)
{
  if (so)
    {
      struct timeval null;
      CLEAR (null);
      return event_timeout_trigger (&so->et, &null, ETT_DEFAULT);
    }
  else
    return false;
}

bool
status_trigger_tv (struct status_output *so, struct timeval *tv)
{
  if (so)
    return event_timeout_trigger (&so->et, tv, ETT_DEFAULT);
  else
    return false;
}

void
status_reset (struct status_output *so)
{
  if (so && so->fd >= 0)
    lseek (so->fd, (off_t)0, SEEK_SET);
}

void
status_flush (struct status_output *so)
{
  if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_WRITE))
    {
#if defined(HAVE_FTRUNCATE)
      {
	const off_t off = lseek (so->fd, (off_t)0, SEEK_CUR);
	if (ftruncate (so->fd, off) != 0) {
	  msg (M_WARN, "Failed to truncate status file: %s", strerror(errno));
	}
      }
#elif defined(HAVE_CHSIZE)
      {
	const long off = (long) lseek (so->fd, (off_t)0, SEEK_CUR);
	chsize (so->fd, off);
      }
#else
#warning both ftruncate and chsize functions appear to be missing from this OS
#endif

      /* clear read buffer */
      if (buf_defined (&so->read_buf))
	{
	  ASSERT (buf_init (&so->read_buf, 0));
	}
    }
}

/* return false if error occurred */
bool
status_close (struct status_output *so)
{
  bool ret = true;
  if (so)
    {
      if (so->errors)
	ret = false;
      if (so->fd >= 0)
	{
	  if (close (so->fd) < 0)
	    ret = false;
	}
      if (so->filename)
	free (so->filename);
      if (buf_defined (&so->read_buf))
	free_buf (&so->read_buf);
      free (so);
    }
  else
    ret = false;
  return ret;
}

#define STATUS_PRINTF_MAXLEN 512

void
status_printf (struct status_output *so, const char *format, ...)
{
  if (so && (so->flags & STATUS_OUTPUT_WRITE))
    {
      char buf[STATUS_PRINTF_MAXLEN+2]; /* leave extra bytes for CR, LF */
      va_list arglist;
      int stat;

      va_start (arglist, format);
      stat = vsnprintf (buf, STATUS_PRINTF_MAXLEN, format, arglist);
      va_end (arglist);
      buf[STATUS_PRINTF_MAXLEN - 1] = 0;

      if (stat < 0 || stat >= STATUS_PRINTF_MAXLEN)
	so->errors = true;

      if (so->msglevel >= 0 && !so->errors)
	msg (so->msglevel, "%s", buf);

      if (so->fd >= 0 && !so->errors)
	{
	  int len;
	  strcat (buf, "\n");
	  len = strlen (buf);
	  if (len > 0)
	    {
	      if (write (so->fd, buf, len) != len)
		so->errors = true;
	    }
	}

      if (so->vout && !so->errors)
	{
	  chomp (buf);
	  (*so->vout->func) (so->vout->arg, so->vout->flags_default, buf);
	}
    }
}

bool
status_read (struct status_output *so, struct buffer *buf)
{
  bool ret = false;

  if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_READ))
    {
      ASSERT (buf_defined (&so->read_buf));      
      ASSERT (buf_defined (buf));
      while (true)
	{
	  const int c = buf_read_u8 (&so->read_buf);

	  /* read more of file into buffer */
	  if (c == -1)
	    {
	      int len;

	      ASSERT (buf_init (&so->read_buf, 0));
	      len = read (so->fd, BPTR (&so->read_buf), BCAP (&so->read_buf));
	      if (len <= 0)
		break;

	      ASSERT (buf_inc_len (&so->read_buf, len));
	      continue;
	    }

	  ret = true;

	  if (c == '\r')
	    continue;

	  if (c == '\n')
	    break;

	  buf_write_u8 (buf, c);
	}

      buf_null_terminate (buf);
    }

  return ret;
}
