status.c
6fbf66fa
 /*
  *  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.
  *
564a2109
  *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  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
  */
 
 #include "syshead.h"
 
 #include "status.h"
 #include "perf.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)
 	    {
 #ifdef _MSC_VER
             case STATUS_OUTPUT_WRITE:
               so->fd = open (filename,
                              O_CREAT | O_TRUNC | O_WRONLY,
 			     _S_IREAD | _S_IWRITE);
               break;
             case STATUS_OUTPUT_READ:
               so->fd = open (filename,
                              O_RDONLY,
 			     _S_IREAD | _S_IWRITE);
               break;
             case STATUS_OUTPUT_READ|STATUS_OUTPUT_WRITE:
               so->fd = open (filename,
                              O_CREAT | O_RDWR,
 			     _S_IREAD | _S_IWRITE);
               break;
 #else
 	    case STATUS_OUTPUT_WRITE:
 	      so->fd = open (filename,
 			     O_CREAT | O_TRUNC | O_WRONLY,
 			     S_IRUSR | S_IWUSR);
 	      break;
 	    case STATUS_OUTPUT_READ:
 	      so->fd = open (filename,
 			     O_RDONLY,
 			     S_IRUSR | S_IWUSR);
 	      break;
 	    case STATUS_OUTPUT_READ|STATUS_OUTPUT_WRITE:
 	      so->fd = open (filename,
 			     O_CREAT | O_RDWR,
 			     S_IRUSR | S_IWUSR);
 	      break;
 #endif
 	    default:
 	      ASSERT (0);
 	    }
 	  if (so->fd >= 0)
 	    {
 	      so->filename = string_alloc (filename, NULL);
 
 	      /* 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);
 	ftruncate (so->fd, off);
       }
 #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;
 }
 
6cd276ba
 #define STATUS_PRINTF_MAXLEN 512
6fbf66fa
 
 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;
6cd276ba
       int stat;
6fbf66fa
 
       va_start (arglist, format);
6cd276ba
       stat = vsnprintf (buf, STATUS_PRINTF_MAXLEN, format, arglist);
6fbf66fa
       va_end (arglist);
       buf[STATUS_PRINTF_MAXLEN - 1] = 0;
 
6cd276ba
       if (stat < 0 || stat >= STATUS_PRINTF_MAXLEN)
 	so->errors = true;
 
       if (so->msglevel >= 0 && !so->errors)
6fbf66fa
 	msg (so->msglevel, "%s", buf);
 
6cd276ba
       if (so->fd >= 0 && !so->errors)
6fbf66fa
 	{
 	  int len;
 	  strcat (buf, "\n");
 	  len = strlen (buf);
 	  if (len > 0)
 	    {
6cd276ba
 	      if (write (so->fd, buf, len) != len)
6fbf66fa
 		so->errors = true;
 	    }
 	}
 
6cd276ba
       if (so->vout && !so->errors)
6fbf66fa
 	{
 	  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;
 }