/*
 * IRC - Internet Relay Chat, ircd/fileio.c
 * Copyright (C) 1998 Thomas Helvey <tomh@inxpress.net>
 * Copyright (C) 1990 Jarkko Oikarinen and
 *                  University of Oulu, Co Center
 *
 * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/** @file
 * @brief ANSI FILE* clone API implementation.
 * @version $Id: fileio.c,v 1.8 2005/03/20 16:06:17 entrope Exp $
 */
#include "config.h"

#include "fileio.h"
#include "ircd_alloc.h"         /* MyMalloc, MyFree */
#include "ircd_log.h"           /* assert */

/* #include <assert.h> -- Now using assert in ircd_log.h */       /* assert */
#include <fcntl.h>              /* O_RDONLY, O_WRONLY, ... */
#include <stdio.h>              /* BUFSIZ, EOF */
#include <sys/stat.h>           /* struct stat */
#include <unistd.h>             /* read, write, open, close */
#include <string.h>

#define FB_EOF  0x01            /**< File has reached EOF. */
#define FB_FAIL 0x02            /**< File operation failed. */

/** Tracks status and buffer for a file on disk. */
struct FileBuf {
  int fd;                       /**< file descriptor */
  char *endp;                   /**< one past the end */
  char *ptr;                    /**< current read pos */
  int flags;                    /**< file state */
  char buf[BUFSIZ];             /**< buffer */
};

/** Open a new FBFILE.
 * @param[in] filename Name of file to open.
 * @param[in] mode fopen()-style mode string.
 * @return Pointer to newly allocated FBFILE.
 */
FBFILE* fbopen(const char *filename, const char *mode)
{
  int openmode = 0;
  int pmode = 0;
  FBFILE *fb = NULL;
  int fd;
  assert(filename);
  assert(mode);

  while (*mode) {
    switch (*mode) {
    case 'r':
      openmode = O_RDONLY;
      break;
    case 'w':
      openmode = O_WRONLY | O_CREAT | O_TRUNC;
      pmode = S_IRUSR | S_IWUSR;
      break;
    case 'a':
      openmode = O_WRONLY | O_CREAT | O_APPEND;
      pmode = S_IRUSR | S_IWUSR;
      break;
    case '+':
      openmode &= ~(O_RDONLY | O_WRONLY);
      openmode |= O_RDWR;
      break;
    default:
      break;
    }
    ++mode;
  }
  /*
   * stop NFS hangs...most systems should be able to open a file in
   * 3 seconds. -avalon (courtesy of wumpus)
   */
  alarm(3);
  if ((fd = open(filename, openmode, pmode)) == -1) {
    alarm(0);
    return fb;
  }
  alarm(0);

  if (NULL == (fb = fdbopen(fd, NULL)))
    close(fd);
  return fb;
}

/** Open a FBFILE from a file descriptor.
 * @param[in] fd File descriptor to use.
 * @param[in] mode fopen()-style mode string (ignored).
 */
FBFILE* fdbopen(int fd, const char *mode)
{
  /*
   * ignore mode, if file descriptor hasn't been opened with the
   * correct mode, the first use will fail
   */
  FBFILE *fb = (FBFILE *) MyMalloc(sizeof(FBFILE));
  assert(0 != fb);
  fb->ptr   = fb->endp = fb->buf;
  fb->fd    = fd;
  fb->flags = 0;

  return fb;
}

/** Close a FBFILE.
 * @param[in] fb File buffer to close.
 */
void fbclose(FBFILE* fb)
{
  assert(fb);
  close(fb->fd);
  MyFree(fb);
}

/** Attempt to fill a file's buffer.
 * @param[in] fb File to operate on.
 * @return Number of bytes read into buffer, or a negative number on error.
 */
static int fbfill(FBFILE * fb)
{
  int n;
  assert(fb);
  if (fb->flags)
    return -1;
  n = read(fb->fd, fb->buf, BUFSIZ);
  if (0 < n)
  {
    fb->ptr = fb->buf;
    fb->endp = fb->buf + n;
  }
  else if (n < 0)
    fb->flags |= FB_FAIL;
  else
    fb->flags |= FB_EOF;
  return n;
}

/** Get a single character from a file.
 * @param[in] fb File to fetch from.
 * @return Character value read, or EOF on error or end-of-file.
 */
int fbgetc(FBFILE * fb)
{
  assert(fb);
  if (fb->ptr < fb->endp || fbfill(fb) > 0)
    return *fb->ptr++;
  return EOF;
}

/** Get a line of input from a file.
 * @param[out] buf Output buffer to read to.
 * @param[in] len Maximum number of bytes to write to buffer
 * (including terminating NUL).
 * @param[in] fb File to read from.
 */
char *fbgets(char *buf, size_t len, FBFILE * fb)
{
  char *p = buf;
  assert(buf);
  assert(fb);
  assert(0 < len);

  if (fb->ptr == fb->endp && fbfill(fb) < 1)
    return 0;
  --len;
  while (len--) {
    *p = *fb->ptr++;
    if ('\n' == *p)
    {
      ++p;
      break;
    }
    /*
     * deal with CR's
     */
    else if ('\r' == *p) {
      if (fb->ptr < fb->endp || fbfill(fb) > 0) {
        if ('\n' == *fb->ptr)
          ++fb->ptr;
      }
      *p++ = '\n';
      break;
    }
    ++p;
    if (fb->ptr == fb->endp && fbfill(fb) < 1)
      break;
  }
  *p = '\0';
  return buf;
}

/** Write a string to a file.
 * @param[in] str String to write to file.
 * @param[in] fb File to write to.
 * @return Number of bytes written, or -1 on error.
 */
int fbputs(const char *str, FBFILE * fb)
{
  int n = -1;
  assert(str);
  assert(fb);

  if (0 == fb->flags) {
    n = write(fb->fd, str, strlen(str));
    if (-1 == n)
      fb->flags |= FB_FAIL;
  }
  return n;
}

/** Get file status.
 * @param[out] sb Receives file status.
 * @param[in] fb File to get status for.
 * @return Zero on success, -1 on error.
 */
int fbstat(struct stat *sb, FBFILE * fb)
{
  assert(sb);
  assert(fb);
  return fstat(fb->fd, sb);
}

