/*  Functions from hack's utils library.
    Copyright (C) 1989-2018 Free Software Foundation, Inc.

    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 3, 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */

#include <config.h>

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>

#include "unlocked-io.h"
#include "utils.h"
#include "fwriting.h"

const char *myname;

/* Store information about files opened with ck_fopen
   so that error messages from ck_fread, ck_fwrite, etc. can print the
   name of the file that had the error */

struct open_file
  {
    FILE *fp;
    char *name;
    struct open_file *link;
    unsigned temp : 1;
  };

static struct open_file *open_files = NULL;
static void do_ck_fclose (FILE *fp);

/* Print an error message and exit */

void
panic(const char *str, ...)
{
  va_list ap;

  fprintf(stderr, "%s: ", myname);
  va_start(ap, str);
  vfprintf(stderr, str, ap);
  va_end(ap);
  putc('\n', stderr);

  /* Unlink the temporary files.  */
  while (open_files)
    {
      if (open_files->temp)
        {
          fclose (open_files->fp);
          errno = 0;
          unlink (open_files->name);
          if (errno != 0)
            fprintf (stderr, _("cannot remove %s: %s"), open_files->name,
                     strerror (errno));
        }

      open_files = open_files->link;
    }

  exit(EXIT_PANIC);
}


/* Internal routine to get a filename from open_files */
static const char * _GL_ATTRIBUTE_PURE
utils_fp_name(FILE *fp)
{
  struct open_file *p;

  for (p=open_files; p; p=p->link)
    if (p->fp == fp)
      return p->name;
  if (fp == stdin)
    return "stdin";
  else if (fp == stdout)
    return "stdout";
  else if (fp == stderr)
    return "stderr";

  return "<unknown>";
}

static void
register_open_file (FILE *fp, const char *name)
{
  struct open_file *p;
  for (p=open_files; p; p=p->link)
    {
      if (fp == p->fp)
        {
          free(p->name);
          break;
        }
    }
  if (!p)
    {
      p = MALLOC(1, struct open_file);
      p->link = open_files;
      open_files = p;
    }
  p->name = ck_strdup(name);
  p->fp = fp;
  p->temp = false;
}

/* Panic on failing fopen */
FILE *
ck_fopen(const char *name, const char *mode, int fail)
{
  FILE *fp;

  fp = fopen (name, mode);
  if (!fp)
    {
      if (fail)
        panic(_("couldn't open file %s: %s"), name, strerror(errno));

      return NULL;
    }

  register_open_file (fp, name);
  return fp;
}

/* Panic on failing fdopen */
FILE *
ck_fdopen( int fd, const char *name, const char *mode, int fail)
{
  FILE *fp;

  fp = fdopen (fd, mode);
  if (!fp)
    {
      if (fail)
        panic(_("couldn't attach to %s: %s"), name, strerror(errno));

      return NULL;
    }

  register_open_file (fp, name);
  return fp;
}

FILE *
ck_mkstemp (char **p_filename, const char *tmpdir,
            const char *base, const char *mode)
{
  char *template = xmalloc (strlen (tmpdir) + strlen (base) + 8);
  sprintf (template, "%s/%sXXXXXX", tmpdir, base);

   /* The ownership might change, so omit some permissions at first
      so unauthorized users cannot nip in before the file is ready.
      mkstemp forces O_BINARY on cygwin, so use mkostemp instead.  */
  mode_t save_umask = umask (0077);
  int fd = mkostemp (template, 0);
  umask (save_umask);
  if (fd == -1)
    panic(_("couldn't open temporary file %s: %s"), template, strerror(errno));

  *p_filename = template;
  FILE *fp = fdopen (fd, mode);
  register_open_file (fp, template);
  return fp;
}

/* Panic on failing fwrite */
void
ck_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  clearerr(stream);
  if (size && fwrite(ptr, size, nmemb, stream) != nmemb)
    panic(ngettext("couldn't write %llu item to %s: %s",
                   "couldn't write %llu items to %s: %s", nmemb),
          (unsigned long long) nmemb, utils_fp_name(stream),
          strerror(errno));
}

/* Panic on failing fread */
size_t
ck_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  clearerr(stream);
  if (size && (nmemb=fread(ptr, size, nmemb, stream)) <= 0 && ferror(stream))
    panic(_("read error on %s: %s"), utils_fp_name(stream), strerror(errno));

  return nmemb;
}

size_t
ck_getdelim(char **text, size_t *buflen, char buffer_delimiter, FILE *stream)
{
  ssize_t result;
  bool error;

  error = ferror (stream);
  if (!error)
    {
      result = getdelim (text, buflen, buffer_delimiter, stream);
      error = ferror (stream);
    }

  if (error)
    panic (_("read error on %s: %s"), utils_fp_name(stream), strerror(errno));

  return result;
}

/* Panic on failing fflush */
void
ck_fflush(FILE *stream)
{
  if (!fwriting(stream))
    return;

  clearerr(stream);
  if (fflush(stream) == EOF && errno != EBADF)
    panic("couldn't flush %s: %s", utils_fp_name(stream), strerror(errno));
}

/* Panic on failing fclose */
void
ck_fclose(FILE *stream)
{
  struct open_file r;
  struct open_file *prev;
  struct open_file *cur;

  /* a NULL stream means to close all files */
  r.link = open_files;
  prev = &r;
  while ( (cur = prev->link) )
    {
      if (!stream || stream == cur->fp)
        {
          do_ck_fclose (cur->fp);
          prev->link = cur->link;
          free(cur->name);
          free(cur);
        }
      else
        prev = cur;
    }

  open_files = r.link;

  /* Also care about stdout, because if it is redirected the
     last output operations might fail and it is important
     to signal this as an error (perhaps to make). */
  if (!stream)
    {
      do_ck_fclose (stdout);
      do_ck_fclose (stderr);
    }
}

/* Close a single file. */
void
do_ck_fclose(FILE *fp)
{
  ck_fflush(fp);
  clearerr(fp);

  if (fclose(fp) == EOF)
    panic("couldn't close %s: %s", utils_fp_name(fp), strerror(errno));
}


/* Follow symlink and panic if something fails.  Return the ultimate
   symlink target, stored in a temporary buffer that the caller should
   not free.  */
const char *
follow_symlink(const char *fname)
{
#ifdef ENABLE_FOLLOW_SYMLINKS
  static char *buf1, *buf2;
  static int buf_size;

  struct stat statbuf;
  const char *buf = fname, *c;
  int rc;

  if (buf_size == 0)
    {
      buf1 = ck_malloc (PATH_MAX + 1);
      buf2 = ck_malloc (PATH_MAX + 1);
      buf_size = PATH_MAX + 1;
    }

  while ((rc = lstat (buf, &statbuf)) == 0
         && (statbuf.st_mode & S_IFLNK) == S_IFLNK)
    {
      if (buf == buf2)
        {
          strcpy (buf1, buf2);
          buf = buf1;
        }

      while ((rc = readlink (buf, buf2, buf_size)) == buf_size)
        {
          buf_size *= 2;
          buf1 = ck_realloc (buf1, buf_size);
          buf2 = ck_realloc (buf2, buf_size);
        }
      if (rc < 0)
        panic (_("couldn't follow symlink %s: %s"), buf, strerror(errno));
      else
        buf2 [rc] = '\0';

      if (buf2[0] != '/' && (c = strrchr (buf, '/')) != NULL)
        {
          /* Need to handle relative paths with care.  Reallocate buf1 and
             buf2 to be big enough.  */
          int len = c - buf + 1;
          if (len + rc + 1 > buf_size)
            {
              buf_size = len + rc + 1;
              buf1 = ck_realloc (buf1, buf_size);
              buf2 = ck_realloc (buf2, buf_size);
            }

          /* Always store the new path in buf1.  */
          if (buf != buf1)
            memcpy (buf1, buf, len);

          /* Tack the relative symlink at the end of buf1.  */
          memcpy (buf1 + len, buf2, rc + 1);
          buf = buf1;
        }
      else
        {
          /* Use buf2 as the buffer, it saves a strcpy if it is not pointing to
             another link.  It works for absolute symlinks, and as long as
             symlinks do not leave the current directory.  */
           buf = buf2;
        }
    }

  if (rc < 0)
    panic (_("cannot stat %s: %s"), buf, strerror(errno));

  return buf;
#else
  return fname;
#endif /* ENABLE_FOLLOW_SYMLINKS */
}

/* Panic on failing unlink */
void
ck_unlink (const char *name)
{
  if (unlink (name) == -1)
    panic (_("cannot remove %s: %s"), name, strerror (errno));
}

/* Attempt to unlink denoted file if operation rd failed. */
static int
_unlink_if_fail (rd, unlink_if_fail)
  int rd;
  const char *unlink_if_fail;
{
  if (rd == -1 && unlink_if_fail)
    {
      int save_errno = errno;
      ck_unlink (unlink_if_fail);
      errno = save_errno;
    }

  return rd != -1;
}

/* Copy contents between files. */
static int
_copy (from, to)
  const char *from, *to;
{
  static char buf[4096];

  FILE *infile, *outfile;
  int c, retval = 0;
      errno = 0;

  infile = fopen (from, "r");
  if (infile == NULL)
    return -1;

  outfile = fopen (to, "w");
  if (outfile == NULL)
    {
      fclose (infile);
      return -1;
    }

  while (1)
    {
      size_t bytes_in = fread (buf, 1, sizeof (buf), infile);
      size_t bytes_out;
      if (bytes_in == 0)
       {
         if (ferror (infile))
           retval = -1;
         break;
       }

      bytes_out = fwrite (buf, 1, bytes_in, outfile);
      if (bytes_out != bytes_in)
       {
         retval = -1;
         break;
       }
    }

  fclose (outfile);
  fclose (infile);

  return retval;
}

/* Panic on failing rename */
void
ck_rename (from, to, unlink_if_fail)
  const char *from, *to;
  const char *unlink_if_fail;
{
  if (!_unlink_if_fail (rename (from, to), unlink_if_fail))
  panic (_("cannot rename %s: %s"), from, strerror (errno));
}

/* Attempt to copy file contents between the files. */
void
ck_fccopy (from, to, unlink_if_fail)
  const char *from, *to;
  const char *unlink_if_fail;
{
  if (!_unlink_if_fail (_copy (from, to), unlink_if_fail))
    panic (_("cannot copy %s to %s: %s"), from, to, strerror (errno));
}

/* Copy contents between files, and then unlink the source. */
void
ck_fcmove (from, to, unlink_if_fail)
  const char *from, *to;
  const char *unlink_if_fail;
{
  ck_fccopy (from, to, unlink_if_fail);
  ck_unlink (from);
}



/* Panic on failing malloc */
void *
ck_malloc(size_t size)
{
  void *ret = calloc(1, size ? size : 1);
  if (!ret)
    panic("couldn't allocate memory");
  return ret;
}

/* Panic on failing realloc */
void *
ck_realloc(void *ptr, size_t size)
{
  void *ret;

  if (size == 0)
    {
      free(ptr);
      return NULL;
    }
  if (!ptr)
    return ck_malloc(size);
  ret = realloc(ptr, size);
  if (!ret)
    panic("couldn't re-allocate memory");
  return ret;
}

/* Return a malloc()'d copy of a string */
char *
ck_strdup(const char *str)
{
  char *ret = MALLOC(strlen(str)+1, char);
  return strcpy(ret, str);
}

/* Return a malloc()'d copy of a block of memory */
void *
ck_memdup(const void *buf, size_t len)
{
  void *ret = ck_malloc(len);
  return memcpy(ret, buf, len);
}


/* Implement a variable sized buffer of `stuff'.  We don't know what it is,
nor do we care, as long as it doesn't mind being aligned by malloc. */

struct buffer
  {
    size_t allocated;
    size_t length;
    char *b;
  };

#define MIN_ALLOCATE 50

struct buffer *
init_buffer(void)
{
  struct buffer *b = MALLOC(1, struct buffer);
  b->b = MALLOC(MIN_ALLOCATE, char);
  b->allocated = MIN_ALLOCATE;
  b->length = 0;
  return b;
}

char *
get_buffer(struct buffer const *b)
{
  return b->b;
}

size_t
size_buffer(struct buffer const *b)
{
  return b->length;
}

static void
resize_buffer(struct buffer *b, size_t newlen)
{
  char *try = NULL;
  size_t alen = b->allocated;

  if (newlen <= alen)
    return;
  alen *= 2;
  if (newlen < alen)
    try = realloc(b->b, alen);	/* Note: *not* the REALLOC() macro! */
  if (!try)
    {
      alen = newlen;
      try = REALLOC(b->b, alen, char);
    }
  b->allocated = alen;
  b->b = try;
}

char *
add_buffer(struct buffer *b, const char *p, size_t n)
{
  char *result;
  if (b->allocated - b->length < n)
    resize_buffer(b, b->length+n);
  result = memcpy(b->b + b->length, p, n);
  b->length += n;
  return result;
}

char *
add1_buffer(struct buffer *b, int c)
{
  /* This special case should be kept cheap;
   *  don't make it just a mere convenience
   *  wrapper for add_buffer() -- even "builtin"
   *  versions of memcpy(a, b, 1) can become
   *  expensive when called too often.
   */
  if (c != EOF)
    {
      char *result;
      if (b->allocated - b->length < 1)
        resize_buffer(b, b->length+1);
      result = b->b + b->length++;
      *result = c;
      return result;
    }

  return NULL;
}

void
free_buffer(struct buffer *b)
{
  if (b)
    free(b->b);
  free(b);
}
