| /* |
| * 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-2018 OpenVPN 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; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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; |
| } |
| } |
| |
| 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 | M_ERRNO, "Failed to truncate status file"); |
| } |
| } |
| #elif defined(HAVE_CHSIZE) |
| { |
| const long off = (long) lseek(so->fd, (off_t)0, SEEK_CUR); |
| chsize(so->fd, off); |
| } |
| #else /* if defined(HAVE_FTRUNCATE) */ |
| #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; |
| } |