/* 
logpp (Log PreProcessor) 0.16 - output.c
Copyright (C) 2006-2008 Risto Vaarandi

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
of the License, 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.

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.
*/

#include "common.h"
#include "logpp.h"

/* open_dst_file() opens an output destination file 'file', creating
   the file if necessary, and returns the descriptor of the opened file 
   on success and -1 otherwise */

int
open_dst_file(struct dst *file)
{
  struct stat fileinfo;
  int ret, flags, fd;

  /* at this point, the destination file status is unknown */
  file->status = SDSTAT_UNKN;

  /* if REOPENINT was set with a command line option, get the current time
     and assign it to the 'tloa' (time of last open) field of the 'file' */
  if (REOPENINT) file->tloa = get_time();

  /* obtain destination status info; if stat() failed because of other
     error than "file does not exist", set 'fd' to -1 and return */
  for (;;) {
    ret = stat(file->name, &fileinfo);
    if (ret != -1) break;
    if (errno == EINTR) continue;
    if (errno == ENOENT) break;
    log_msg(LOG_ERR, "stat(%s) error (%s)", file->name, strerror(errno));
    file->fd = -1;
    return -1;
  }

  /* if the file does not exist, set open flags to O_WRONLY, O_CREAT and
     O_APPEND (create the file for writing in append mode) */
  if (ret == -1) { 
    flags = O_WRONLY | O_CREAT | O_APPEND;
    file->status = SDSTAT_REG;
  }
  /* if the file is a regular file, set open flags to O_WRONLY and
     O_APPEND (write in append mode) */
  else if (S_ISREG(fileinfo.st_mode)) {
    flags = O_WRONLY | O_APPEND;
    file->status = SDSTAT_REG;
  }
  /* if the file is a FIFO, set open flags to O_RDWR and O_NONBLOCK
     (if O_NONBLOCK is not set, future calls to write(2) would block the 
     process if no-one is reading the data from FIFO; if O_NONBLOCK is set
     with O_WRONLY and no-one has the FIFO open for reading, open would
     fail, so the process itself opens the FIFO for reading as well) */
  else if (S_ISFIFO(fileinfo.st_mode)) {
    flags = O_RDWR | O_NONBLOCK;
    file->status = SDSTAT_FIFO;
  }
  else {
    log_msg(LOG_ERR, "%s is of unsupported type", file->name);
    file->fd = -1;
    return -1;
  }

  /* open the destination file and use mode 0644 for the file if it is
     created (note that the final mode will depend on the process umask) */
  for (;;) {
    fd = open(file->name, flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (fd != -1) break;
    if (errno == EINTR) continue;
    log_msg(LOG_ERR, "open(%s) error (%s)", file->name, strerror(errno));
    file->fd = -1;
    return -1;
  }

  file->fd = fd;
  return fd;
}

/* close_dst_file() closes the destination file by calling close() for 
   the 'fd' field of the 'file' structure and sets 'fd' to -1. The function 
   returns 1 if closing the file succeeded and 0 otherwise */

int
close_dst_file(struct dst *file)
{
  int ret = 1;

  while (close(file->fd) == -1) {
    if (errno == EINTR) continue;
    log_msg(LOG_ERR, "close(%s) error (%s)", file->name, strerror(errno));
    ret = 0;
    break;
  }

  file->fd = -1;
  return ret;
}

/* open_outputs() opens all output destination files */

void
open_outputs(void)
{
  struct output *output;
  size_t i;

  for (output = OUTPUTLIST; output; output = output->next)
    for (i = 0; i < output->dstl_size; ++i)
      if (output->dstlist[i].type == SRCDST_FILE &&
          open_dst_file(output->dstlist + i) == -1)
        log_msg(LOG_ERR, "Failed to open output file %s", 
                         output->dstlist[i].name);
}

/* close_outputs() closes all output destination files */

void
close_outputs(void)
{
  struct output *output;
  size_t i;

  for (output = OUTPUTLIST; output; output = output->next)
    for (i = 0; i < output->dstl_size; ++i)
      if (output->dstlist[i].type == SRCDST_FILE &&
          output->dstlist[i].fd != -1 &&
          !close_dst_file(output->dstlist + i))
        log_msg(LOG_ERR, "Failed to close output file %s", 
                         output->dstlist[i].name);
}

/* write_line() writes 'line' to output destination 'dst' (the size
   of 'line' is 'count' bytes + terminating 0); the function returns
   1 on success and 0 on failure */

int
write_line(struct dst *dst, char *line, size_t count)
{
  ssize_t nbytes;
  int fd[2], flags;

  switch (dst->type) {
   /* destination is a file */
   case SRCDST_FILE:
    /* replace the terminating 0 with a newline */
    line[count] = NEWLINE;
    for (;;) {
      nbytes = write(dst->fd, line, count + 1);
      /* if the file is a regular file, "short write" is an error */
      if (dst->status == SDSTAT_REG) {
        if (nbytes == count + 1) break;
        if (nbytes >= 0) {
          log_msg(LOG_ERR, "Failed to write all bytes to %s", dst->name);
          line[count] = 0;
          return 0;
        }
      } 
      /* if the file is a FIFO, "short write" and "pipe full" are 
         not errors, since they occur because of the reader */
      else {
        if (nbytes != -1) break;
        if (errno == EAGAIN) break;
      }
      if (errno == EINTR) continue;
      log_msg(LOG_ERR, "write(%s) error (%s)", dst->name, strerror(errno));
      line[count] = 0;
      return 0;
    }
    /* restore the terminating 0 */
    line[count] = 0;
    break;

   /* destination is the system logger */ 
   case SRCDST_SYSLOG:
    syslog(dst->priority, "%s", line);
    break;

   /* destination is an external program */
   case SRCDST_EXEC:
    /* create a pipe for communicating with the program */
    if (pipe(fd) == -1) {
      log_msg(LOG_ERR, "pipe() error (%s)", strerror(errno));
      return 0;
    }
    /* create a process for the program */
    switch (fork()) {
     /* if the process creation failed, return */
     case -1:
      log_msg(LOG_ERR, "fork() error (%s)", strerror(errno));
      while (close(fd[0]) == -1 && errno == EINTR);
      while (close(fd[1]) == -1 && errno == EINTR);
      return 0;
     /* child process */
     case 0:
      /* close the write end of the pipe and connect the stdin
         of the process to the read end of the pipe */
      while (close(fd[1]) == -1 && errno == EINTR);
      while (dup2(fd[0], 0) == -1) {
        if (errno == EINTR) continue;
        log_msg(LOG_ERR, "dup2() error (%s)", strerror(errno));
        exit(1);
      }
      while (close(fd[0]) == -1 && errno == EINTR);
      /* execute the program */
      execl(SHELL, SHELL, "-c", dst->name, (char *) 0);
      log_msg(LOG_ERR, "exec(%s) error (%s)", dst->name, strerror(errno));
      exit(1);
     /* parent process */
     default:
      /* close the read end of the pipe and make the write end nonblocking */
      while (close(fd[0]) == -1 && errno == EINTR);
      flags = fcntl(fd[1], F_GETFL, 0);
      if (flags == -1 || fcntl(fd[1], F_SETFL, flags | O_NONBLOCK) == -1) {
        log_msg(LOG_ERR, "fcntl() error (%s)", strerror(errno));
        while (close(fd[1]) == -1 && errno == EINTR);
        return 0;
      }
      /* replace the terminating 0 with a newline */
      line[count] = NEWLINE;
      /* write to the pipe, ignoring "short write", "pipe full" and
         "broken pipe" conditions, since they occur because of the reader */
      for (;;) {
        nbytes = write(fd[1], line, count + 1);
        if (nbytes != -1) break;
        if (errno == EAGAIN || errno == EPIPE) break;
        if (errno == EINTR) continue;
        log_msg(LOG_ERR, "write(|%s) error (%s)", dst->name, strerror(errno));
        while (close(fd[1]) == -1 && errno == EINTR);
        line[count] = 0;
        return 0;
      }
      while (close(fd[1]) == -1 && errno == EINTR);
      /* restore the terminating 0 */
      line[count] = 0;
    }
  }

  /* increment the counter of successful write's */
  ++dst->counter;
  return 1;
}
