/*
* 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-2008 OpenVPN Solutions LLC <info@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
*/
#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;
}
#define STATUS_PRINTF_MAXLEN 256
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;
va_start (arglist, format);
vsnprintf (buf, STATUS_PRINTF_MAXLEN, format, arglist);
va_end (arglist);
buf[STATUS_PRINTF_MAXLEN - 1] = 0;
if (so->msglevel >= 0)
msg (so->msglevel, "%s", buf);
if (so->fd >= 0)
{
int len;
strcat (buf, "\n");
len = strlen (buf);
if (len > 0)
{
if (write (so->fd, buf, len) < 0)
so->errors = true;
}
}
if (so->vout)
{
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;
}