diff options
Diffstat (limited to 'src/log-buffer.c')
-rw-r--r-- | src/log-buffer.c | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/src/log-buffer.c b/src/log-buffer.c new file mode 100644 index 0000000..c7cb6a9 --- /dev/null +++ b/src/log-buffer.c @@ -0,0 +1,529 @@ +/* CVS client logging buffer. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + 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. */ + +#include <config.h> + +#include <stdio.h> + +#include "cvs.h" +#include "buffer.h" + +#if defined CLIENT_SUPPORT || defined SERVER_SUPPORT + +/* We want to be able to log data sent between us and the server. We + do it using log buffers. Each log buffer has another buffer which + handles the actual I/O, and a file to log information to. + + This structure is the closure field of a log buffer. */ + +struct log_buffer +{ + /* The underlying buffer. */ + struct buffer *buf; + /* The file to log information to. */ + FILE *log; + +#ifdef PROXY_SUPPORT + /* Whether errors writing to the log file should be fatal or not. */ + bool fatal_errors; + + /* The name of the file backing this buffer so that it may be deleted on + * buffer shutdown. + */ + char *back_fn; + + /* Set once logging is permanently disabled for a buffer. */ + bool disabled; + + /* The memory buffer (cache) backing this log. */ + struct buffer *back_buf; + + /* The maximum number of bytes to store in memory before beginning logging + * to a file. + */ + size_t max; + + /* Once we start logging to a file we do not want to stop unless asked. */ + bool tofile; +#endif /* PROXY_SUPPORT */ +}; + + + +#ifdef PROXY_SUPPORT +/* Force the existance of lb->log. + * + * INPUTS + * lb The log buffer. + * + * OUTPUTS + * lb->log The new FILE *. + * lb->back_fn The name of the new log, for later disposal. + * + * ASSUMPTIONS + * lb->log is NULL or, at least, does not require freeing. + * lb->back_fn is NULL or, at least, does not require freeing.. + * + * RETURNS + * Nothing. + * + * ERRORS + * Errors creating the log file will output a message via error(). Whether + * the error is fatal or not is dependent on lb->fatal_errors. + */ +static inline void +log_buffer_force_file (struct log_buffer *lb) +{ + lb->log = cvs_temp_file (&lb->back_fn); + if (!lb->log) + error (lb->fatal_errors, errno, "failed to open log file."); +} +#endif /* PROXY_SUPPORT */ + + + +/* Create a log buffer. + * + * INPUTS + * buf A pointer to the buffer structure to log input from. + * fp A file name to log data to. May be NULL. +#ifdef PROXY_SUPPORT + * fatal_errors Whether errors writing to a log file should be + * considered fatal. +#else + * fatal_errors unused +#endif + * input Whether we will log data for an input or output + * buffer. +#ifdef PROXY_SUPPORT + * max The maximum size of our memory cache. +#else + * max unused +#endif + * memory The function to call when memory allocation errors are + * encountered. + * + * RETURNS + * A pointer to a new buffer structure. + */ +static int log_buffer_input (void *, char *, size_t, size_t, size_t *); +static int log_buffer_output (void *, const char *, size_t, size_t *); +static int log_buffer_flush (void *); +static int log_buffer_block (void *, bool); +static int log_buffer_get_fd (void *); +static int log_buffer_shutdown (struct buffer *); +struct buffer * +log_buffer_initialize (struct buffer *buf, FILE *fp, +# ifdef PROXY_SUPPORT + bool fatal_errors, + size_t max, +# endif /* PROXY_SUPPORT */ + bool input, + void (*memory) (struct buffer *)) +{ + struct log_buffer *lb = xmalloc (sizeof *lb); + struct buffer *retbuf; + + lb->buf = buf; + lb->log = fp; +#ifdef PROXY_SUPPORT + lb->back_fn = NULL; + lb->fatal_errors = fatal_errors; + lb->disabled = false; + assert (size_in_bounds_p (max)); + lb->max = max; + lb->tofile = false; + lb->back_buf = buf_nonio_initialize (memory); +#endif /* PROXY_SUPPORT */ + retbuf = buf_initialize (input ? log_buffer_input : NULL, + input ? NULL : log_buffer_output, + input ? NULL : log_buffer_flush, + log_buffer_block, log_buffer_get_fd, + log_buffer_shutdown, memory, lb); + + if (!buf_empty_p (buf)) + { + /* If our buffer already had data, copy it & log it if necessary. This + * can happen, for instance, with a pserver, where we deliberately do + * not instantiate the log buffer until after authentication so that + * auth data does not get logged (the pserver data will not be logged + * in this case, but any data which was left unused in the buffer by + * the auth code will be logged and put in our new buffer). + */ + struct buffer_data *data; +#ifdef PROXY_SUPPORT + size_t total = 0; +#endif /* PROXY_SUPPORT */ + + for (data = buf->data; data != NULL; data = data->next) + { +#ifdef PROXY_SUPPORT + if (!lb->tofile) + { + total = xsum (data->size, total); + if (total >= max) + lb->tofile = true; + } + + if (lb->tofile) + { + if (!lb->log) log_buffer_force_file (lb); + if (lb->log) + { +#endif /* PROXY_SUPPORT */ + if (fwrite (data->bufp, 1, data->size, lb->log) + != (size_t) data->size) + error ( +#ifdef PROXY_SUPPORT + fatal_errors, +#else /* !PROXY_SUPPORT */ + false, +#endif /* PROXY_SUPPORT */ + errno, "writing to log file"); + fflush (lb->log); +#ifdef PROXY_SUPPORT + } + } + else + /* Log to memory buffer. */ + buf_copy_data (lb->back_buf, data, data); +#endif /* PROXY_SUPPORT */ + } + buf_append_buffer (retbuf, buf); + } + return retbuf; +} + + + +/* The input function for a log buffer. */ +static int +log_buffer_input (void *closure, char *data, size_t need, size_t size, + size_t *got) +{ + struct log_buffer *lb = closure; + int status; + + assert (lb->buf->input); + + status = (*lb->buf->input) (lb->buf->closure, data, need, size, got); + if (status != 0) + return status; + + if ( +#ifdef PROXY_SUPPORT + !lb->disabled && +#endif /* PROXY_SUPPORT */ + *got > 0) + { +#ifdef PROXY_SUPPORT + if (!lb->tofile + && xsum (*got, buf_count_mem (lb->back_buf)) >= lb->max) + lb->tofile = true; + + if (lb->tofile) + { + if (!lb->log) log_buffer_force_file (lb); + if (lb->log) + { +#endif /* PROXY_SUPPORT */ + if (fwrite (data, 1, *got, lb->log) != *got) + error ( +#ifdef PROXY_SUPPORT + lb->fatal_errors, +#else /* !PROXY_SUPPORT */ + false, +#endif /* PROXY_SUPPORT */ + errno, "writing to log file"); + fflush (lb->log); +#ifdef PROXY_SUPPORT + } + } + else + /* Log to memory buffer. */ + buf_output (lb->back_buf, data, *got); +#endif /* PROXY_SUPPORT */ + } + + return 0; +} + + + +/* The output function for a log buffer. */ +static int +log_buffer_output (void *closure, const char *data, size_t have, size_t *wrote) +{ + struct log_buffer *lb = closure; + int status; + + assert (lb->buf->output); + + status = (*lb->buf->output) (lb->buf->closure, data, have, wrote); + if (status != 0) + return status; + + if ( +#ifdef PROXY_SUPPORT + !lb->disabled && +#endif /* PROXY_SUPPORT */ + *wrote > 0) + { +#ifdef PROXY_SUPPORT + if (!lb->tofile + && xsum (*wrote, buf_count_mem (lb->back_buf)) >= lb->max) + lb->tofile = true; + + if (lb->tofile) + { + if (!lb->log) log_buffer_force_file (lb); + if (lb->log) + { +#endif /* PROXY_SUPPORT */ + if (fwrite (data, 1, *wrote, lb->log) != *wrote) + error ( +#ifdef PROXY_SUPPORT + lb->fatal_errors, +#else /* !PROXY_SUPPORT */ + false, +#endif /* PROXY_SUPPORT */ + errno, "writing to log file"); + fflush (lb->log); +#ifdef PROXY_SUPPORT + } + } + else + /* Log to memory buffer. */ + buf_output (lb->back_buf, data, *wrote); +#endif /* PROXY_SUPPORT */ + } + + return 0; +} + + + +/* The flush function for a log buffer. */ +static int +log_buffer_flush (void *closure) +{ + struct log_buffer *lb = closure; + + assert (lb->buf->flush); + + /* We don't really have to flush the log file here, but doing it + * will let tail -f on the log file show what is sent to the + * network as it is sent. + */ + if (lb->log && (fflush (lb->log))) + error (0, errno, "flushing log file"); + + return (*lb->buf->flush) (lb->buf->closure); +} + + + +/* The block function for a log buffer. */ +static int +log_buffer_block (void *closure, bool block) +{ + struct log_buffer *lb = closure; + + if (block) + return set_block (lb->buf); + else + return set_nonblock (lb->buf); +} + + + +#ifdef PROXY_SUPPORT +/* Disable logging without shutting down the next buffer in the chain. + */ +struct buffer * +log_buffer_rewind (struct buffer *buf) +{ + struct log_buffer *lb = buf->closure; + struct buffer *retbuf; + int fd; + + lb->disabled = true; + + if (lb->log) + { + FILE *tmp = lb->log; + lb->log = NULL; + + /* flush & rewind the file. */ + if (fflush (tmp) < 0) + error (0, errno, "flushing log file"); + rewind (tmp); + + /* Get a descriptor for the log and close the FILE *. */ + fd = dup (fileno (tmp)); + if (fclose (tmp) < 0) + error (0, errno, "closing log file"); + } + else + fd = open (DEVNULL, O_RDONLY); + + /* Catch dup/open errors. */ + if (fd < 0) + { + error (lb->fatal_errors, errno, "failed to rewind log buf."); + return NULL; + } + + /* Create a new fd buffer around the log. */ + retbuf = fd_buffer_initialize (fd, 0, NULL, true, buf->memory_error); + + { + struct buffer *tmp; + /* Insert the data which wasn't written to a file. */ + buf_append_buffer (retbuf, lb->back_buf); + tmp = lb->back_buf; + lb->back_buf = NULL; + buf_free (tmp); + } + + return retbuf; +} +#endif /* PROXY_SUPPORT */ + + + +/* Disable logging and close the log without shutting down the next buffer in + * the chain. + */ +#ifndef PROXY_SUPPORT +static +#endif /* !PROXY_SUPPORT */ +void +log_buffer_closelog (struct buffer *buf) +{ + struct log_buffer *lb = buf->closure; + void *tmp; + +#ifdef PROXY_SUPPORT + lb->disabled = true; +#endif /* PROXY_SUPPORT */ + + /* Close the log. */ + if (lb->log) + { + tmp = lb->log; + lb->log = NULL; + if (fclose (tmp) < 0) + error (0, errno, "closing log file"); + } + +#ifdef PROXY_SUPPORT + /* Delete the log if we know its name. */ + if (lb->back_fn) + { + tmp = lb->back_fn; + lb->back_fn = NULL; + if (CVS_UNLINK (tmp)) + error (0, errno, "Failed to delete log file."); + free (tmp); + } + + if (lb->back_buf) + { + tmp = lb->back_buf; + lb->back_buf = NULL; + buf_free (tmp); + } +#endif /* PROXY_SUPPORT */ +} + + + +/* Return the file descriptor underlying any child buffers. */ +static int +log_buffer_get_fd (void *closure) +{ + struct log_buffer *lb = closure; + return buf_get_fd (lb->buf); +} + + + +/* The shutdown function for a log buffer. */ +static int +log_buffer_shutdown (struct buffer *buf) +{ + struct log_buffer *lb = buf->closure; + + log_buffer_closelog (buf); + return buf_shutdown (lb->buf); +} + + + +void +setup_logfiles (char *var, struct buffer **to_server_p, + struct buffer **from_server_p) +{ + char *log = getenv (var); + + /* Set up logfiles, if any. + * + * We do this _after_ authentication on purpose. Wouldn't really like to + * worry about logging passwords... + */ + if (log) + { + int len = strlen (log); + char *buf = xmalloc (len + 5); + char *p; + FILE *fp; + + strcpy (buf, log); + p = buf + len; + + /* Open logfiles in binary mode so that they reflect + exactly what was transmitted and received (that is + more important than that they be maximally + convenient to view). */ + /* Note that if we create several connections in a single CVS client + (currently used by update.c), then the last set of logfiles will + overwrite the others. There is currently no way around this. */ + strcpy (p, ".in"); + fp = fopen (buf, "wb"); + if (!fp) + error (0, errno, "opening to-server logfile %s", buf); + else + *to_server_p = log_buffer_initialize (*to_server_p, fp, +# ifdef PROXY_SUPPORT + false, + 0, +# endif /* PROXY_SUPPORT */ + false, NULL); + + strcpy (p, ".out"); + fp = fopen (buf, "wb"); + if (!fp) + error (0, errno, "opening from-server logfile %s", buf); + else + *from_server_p = log_buffer_initialize (*from_server_p, fp, +# ifdef PROXY_SUPPORT + false, + 0, +# endif /* PROXY_SUPPORT */ + true, NULL); + + free (buf); + } +} + +#endif /* CLIENT_SUPPORT || SERVER_SUPPORT */ |