/*
 * tempfile.c  -  Create a temporary file
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  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.
 *
 * $Id: tempfile.c,v 1.12 2007/01/06 18:31:39 gkminix Exp $
 */

#define NEED_TIME
#include <common.h>
#include <nblib.h>
#include "privlib.h"


/*
 * Maximum number of tries to generate a new temporary file name
 */
#ifdef TMP_MAX
# undef TMP_MAX
#endif
#define TMP_MAX		16384



/*
 * Variables local to this module
 */
static int isinit = FALSE;		/* flag if module initialized */
static LISTHDL templist;		/* list of temporary files */



/*
 * Information about temporary file
 */
struct tempfile {
	int              fd;
	int              dounlink;
	char            *fname;
};



/*
 * Compare file descriptor
 */
static int findfd __F((handle, data, fdptr),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar fdptr)
{
  register struct tempfile *tfp = *((struct tempfile **)(data));

  return(tfp->fd == *((int *)fdptr));
}



/*
 * Remove temporary file
 */
static void rmfile __F((handle, data), LISTHDL handle AND voidstar data)
{
  struct tempfile *tfp = (struct tempfile *)data;

  if (tfp->fd >= 0)
	  close(tfp->fd);
  if (tfp->fname != NULL) {
	if (tfp->dounlink)
		(void)unlink(tfp->fname);
	free(tfp->fname);
  }
  free(tfp);
}



/*
 * Function to be called upon program termination
 */
static void closeall __F_NOARGS
{
  releaselist(templist);
}



/*
 * Initialize module
 */
static inline int initmodule __F_NOARGS
{
  int ret;

  if (!isinit) {
	if ((ret = nbatexit(&closeall)) != 0) {
		nberror(EXIT_INTERNAL, "unable to set temporary file exit function");
		return(FALSE);
	}
	templist = createlist(&rmfile);
	isinit = TRUE;
  }
  return(TRUE);
}



/*
 * Create a unique temporary file. This has been borrowed and modified from
 * GNU's libiberty.
 */
#ifndef HAVE_MKSTEMP
static int mkstemp __F((template), char *template)
{
  static const char letters[] =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  static unsigned long value;
#ifdef HAVE_GETTIMEOFDAY
  struct timeval tv;
#endif
  char *XXXXXX;
  size_t len;
  int count;

  /* Find pointer to replacement string */
  len = strlen(template);
  assert((int)len >= 6 && !strncmp(&template[len - 6], "XXXXXX", 6))
  XXXXXX = &template[len - 6];

#ifdef HAVE_GETTIMEOFDAY
  /* Get some more or less random data.  */
  gettimeofday(&tv, NULL);
  value += ((unsigned long)(tv.tv_usec) << 16) ^ tv.tv_sec ^ getpid();
#else
  value += getpid();
#endif

  /* Try to find a unique file name */
  for (count = 0; count < TMP_MAX; count++) {
	unsigned long v = value;
	int fd;

	/* Fill in the random bits.  */
	XXXXXX[0] = letters[v % 62];
	v /= 62	;
	XXXXXX[1] = letters[v % 62];
	v /= 62;
	XXXXXX[2] = letters[v % 62];
	v /= 62;
	XXXXXX[3] = letters[v % 62];
	v /= 62;
	XXXXXX[4] = letters[v % 62];
	v /= 62;
	XXXXXX[5] = letters[v % 62];

	if ((fd = open(template, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0600)) >= 0)
		return(fd);

	/*
	 * This is a random value. It is only necessary that the next
	 * TMP_MAX values generated by adding 7777 to VALUE are different
	 * with (modulo 2^32).
	 */
	value += 7777;
  }

  /* We return the null string if we can't find a unique file name */
  template[0] = '\0';
  return(-1);
}
#endif



/*
 * Create a temporary file in binary mode
 */
int opentemp __F((dounlink), int dounlink)
{
  struct tempfile *tfp;

  /* Initialize this module */
  if (!initmodule())
	return(-1);

  /* The name of the temp directory should always exist */
  assert(nbtempdir != NULL);

  /* Create new temporary file structure with filename template */
  tfp = (struct tempfile *)nbmalloc(sizeof(struct tempfile));
  tfp->dounlink = TRUE;
  tfp->fd = -1;
  tfp->fname = (char *)nbmalloc(strlen(nbtempdir) + strlen(progname) + 9);
  sprintf(tfp->fname, "%s/%s.XXXXXX", nbtempdir, progname);

  /* Create and open new temporary file */
  if ((tfp->fd = mkstemp(tfp->fname)) == -1) {
	rmfile(LISTHDL_NULL, (voidstar)tfp);
	nberror(EXIT_TEMPNAME, "unable to create temporary file");
	return(-1);
  }

  /*
   * Change file mode to binary. This is only necessary, if O_BINARY is
   * supported at all, and if we are using mkstemp() from the system
   * library. Our own routine above already used O_BINARY.
   */
#if (O_BINARY != 0) && defined(HAVE_MKSTEMP)
  if (fcntl(tfp->fd, F_SETFL, O_BINARY) == -1) {
	rmfile(LISTHDL_NULL, (voidstar)tfp);
	nberror(EXIT_FCNTL, "unable to set binary mode for temporary file");
	return(-1);
  }
#endif

  /*
   * Add temporary file information to list
   */
  tfp->dounlink = dounlink;
  (void)appendlist(templist, tfp);
  return(tfp->fd);
}



/*
 * Close a temporary file, and optionally remove it
 */
void closetemp __F((fd), int fd)
{
  int tempid;

  /* Search through the list of temporary files to find fd */
  tempid = walklist(templist, &findfd, &fd);
  if (tempid >= 0) {
	/* Remove structure from linked list and close file */
	(void)removeatlist(templist, tempid);
  } else {
	/* If we couldn't find fd, just treat it as a regular file */
	close(fd);
  }
}

