src/openvpn/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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN 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.
  *
caa54ac3
  *  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.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
6fbf66fa
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
6fbf66fa
 #include "syshead.h"
 
 #include "status.h"
 #include "perf.h"
bdf9ab75
 #include "misc.h"
09ee4192
 #include "fdmisc.h"
6fbf66fa
 
 #include "memdbg.h"
 
 /*
  * printf-style interface for outputting status info
  */
 
 static const char *
81d882d5
 print_status_mode(unsigned int flags)
6fbf66fa
 {
81d882d5
     switch (flags)
6fbf66fa
     {
81d882d5
         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";
6fbf66fa
     }
 }
 
 struct status_output *
81d882d5
 status_open(const char *filename,
             const int refresh_freq,
             const int msglevel,
             const struct virtual_output *vout,
             const unsigned int flags)
6fbf66fa
 {
81d882d5
     struct status_output *so = NULL;
     if (filename || msglevel >= 0 || vout)
6fbf66fa
     {
81d882d5
         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)
71bbbd76
         {
81d882d5
             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
71bbbd76
             {
81d882d5
                 msg(M_WARN, "Note: cannot open %s for %s", filename, print_status_mode(so->flags));
                 so->errors = true;
71bbbd76
             }
81d882d5
         }
         else
         {
             so->flags = STATUS_OUTPUT_WRITE;
         }
 
         if ((so->flags & STATUS_OUTPUT_WRITE) && refresh_freq > 0)
         {
             event_timeout_init(&so->et, refresh_freq, 0);
         }
6fbf66fa
     }
81d882d5
     return so;
6fbf66fa
 }
 
 bool
81d882d5
 status_trigger(struct status_output *so)
6fbf66fa
 {
81d882d5
     if (so)
6fbf66fa
     {
81d882d5
         struct timeval null;
         CLEAR(null);
         return event_timeout_trigger(&so->et, &null, ETT_DEFAULT);
     }
     else
     {
         return false;
6fbf66fa
     }
 }
 
 bool
81d882d5
 status_trigger_tv(struct status_output *so, struct timeval *tv)
6fbf66fa
 {
81d882d5
     if (so)
     {
         return event_timeout_trigger(&so->et, tv, ETT_DEFAULT);
     }
     else
     {
         return false;
     }
6fbf66fa
 }
 
 void
81d882d5
 status_reset(struct status_output *so)
6fbf66fa
 {
81d882d5
     if (so && so->fd >= 0)
     {
         lseek(so->fd, (off_t)0, SEEK_SET);
     }
6fbf66fa
 }
 
 void
81d882d5
 status_flush(struct status_output *so)
6fbf66fa
 {
81d882d5
     if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_WRITE))
6fbf66fa
     {
 #if defined(HAVE_FTRUNCATE)
81d882d5
         {
             const off_t off = lseek(so->fd, (off_t)0, SEEK_CUR);
             if (ftruncate(so->fd, off) != 0)
             {
56b396dc
                 msg(M_WARN | M_ERRNO, "Failed to truncate status file");
81d882d5
             }
         }
6fbf66fa
 #elif defined(HAVE_CHSIZE)
81d882d5
         {
             const long off = (long) lseek(so->fd, (off_t)0, SEEK_CUR);
             chsize(so->fd, off);
         }
 #else  /* if defined(HAVE_FTRUNCATE) */
6fbf66fa
 #warning both ftruncate and chsize functions appear to be missing from this OS
 #endif
 
81d882d5
         /* clear read buffer */
         if (buf_defined(&so->read_buf))
         {
             ASSERT(buf_init(&so->read_buf, 0));
         }
6fbf66fa
     }
 }
 
 /* return false if error occurred */
 bool
81d882d5
 status_close(struct status_output *so)
6fbf66fa
 {
81d882d5
     bool ret = true;
     if (so)
6fbf66fa
     {
81d882d5
         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;
6fbf66fa
     }
81d882d5
     return ret;
6fbf66fa
 }
 
6cd276ba
 #define STATUS_PRINTF_MAXLEN 512
6fbf66fa
 
 void
81d882d5
 status_printf(struct status_output *so, const char *format, ...)
6fbf66fa
 {
81d882d5
     if (so && (so->flags & STATUS_OUTPUT_WRITE))
6fbf66fa
     {
81d882d5
         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);
         }
6fbf66fa
     }
 }
 
 bool
81d882d5
 status_read(struct status_output *so, struct buffer *buf)
6fbf66fa
 {
81d882d5
     bool ret = false;
6fbf66fa
 
81d882d5
     if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_READ))
6fbf66fa
     {
81d882d5
         ASSERT(buf_defined(&so->read_buf));
         ASSERT(buf_defined(buf));
         while (true)
         {
             const int c = buf_read_u8(&so->read_buf);
6fbf66fa
 
81d882d5
             /* read more of file into buffer */
             if (c == -1)
             {
                 int len;
6fbf66fa
 
81d882d5
                 ASSERT(buf_init(&so->read_buf, 0));
                 len = read(so->fd, BPTR(&so->read_buf), BCAP(&so->read_buf));
                 if (len <= 0)
                 {
                     break;
                 }
6fbf66fa
 
81d882d5
                 ASSERT(buf_inc_len(&so->read_buf, len));
                 continue;
             }
6fbf66fa
 
81d882d5
             ret = true;
6fbf66fa
 
81d882d5
             if (c == '\r')
             {
                 continue;
             }
6fbf66fa
 
81d882d5
             if (c == '\n')
             {
                 break;
             }
6fbf66fa
 
81d882d5
             buf_write_u8(buf, c);
         }
6fbf66fa
 
81d882d5
         buf_null_terminate(buf);
6fbf66fa
     }
 
81d882d5
     return ret;
6fbf66fa
 }