/*
 * tmpfile.c - functions to create and safely open temp files for the shell.
 */

/* Copyright (C) 2000 Free Software Foundation, Inc.

   This file is part of GNU Bash, the Bourne Again SHell.

   Bash 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.

   Bash 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 Bash; see the file COPYING.  If not, write to the Free
   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */

#include <config.h>

#include <bashtypes.h>
#include <posixstat.h>
#include <posixtime.h>
#include <filecntl.h>

#if defined (HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#include <stdio.h>
#include <errno.h>

#include <shell.h>

#ifndef errno
extern int errno;
#endif

#ifndef O_TEMPORARY
#define O_TEMPORARY 0
#endif

#define BASEOPENFLAGS   (O_CREAT | O_TRUNC | O_EXCL | O_TEMPORARY)

#define DEFAULT_TMPDIR		"."	/* bogus default, should be changed */
#define DEFAULT_NAMEROOT	"shtmp"

extern pid_t dollar_dollar_pid;

static char *sys_tmpdir = (char *)NULL;
static int ntmpfiles;
static int tmpnamelen = -1;
static unsigned long filenum = 1L;

static char *
get_sys_tmpdir ()
{
  struct stat sb;

  if (sys_tmpdir)
    return sys_tmpdir;

#ifdef P_tmpdir
  sys_tmpdir = P_tmpdir;
  if (stat (sys_tmpdir, &sb) == 0)
    return sys_tmpdir;
#endif

#ifdef __DJGPP__
  sys_tmpdir = "/dev/env/TEMP";
  if (access (sys_tmpdir, D_OK) == 0)
    return sys_tmpdir;

  sys_tmpdir = "/dev/env/TMP";
  if (access (sys_tmpdir, D_OK) == 0)
    return sys_tmpdir;

#else
  sys_tmpdir = "/tmp";
  if (stat (sys_tmpdir, &sb) == 0)
    return sys_tmpdir;

  sys_tmpdir = "/var/tmp";
  if (stat (sys_tmpdir, &sb) == 0)
    return sys_tmpdir;

  sys_tmpdir = "/usr/tmp";
  if (stat (sys_tmpdir, &sb) == 0)
    return sys_tmpdir;
#endif

  sys_tmpdir = DEFAULT_TMPDIR;

  return sys_tmpdir;
}

static char *
get_tmpdir (flags)
     int flags;
{
  char *tdir;

#ifdef HAVE_DOS_BASED_FILE_SYSTEM
  flags |= MT_USETMPDIR;
#endif

  tdir = (flags & MT_USETMPDIR) ? get_string_value ("TMPDIR") : (char *)NULL;
  if (tdir == 0)
    tdir = get_sys_tmpdir ();

#if defined (HAVE_PATHCONF) && defined (_PC_NAME_MAX)
  if (tmpnamelen == -1)
    tmpnamelen = pathconf (tdir, _PC_NAME_MAX);
#else
  tmpnamelen = 0;
#endif

  return tdir;
}

#ifdef __MSDOS__
#define TEMPLATE_STR "%s/%sXXXXXX"
#else
#define TEMPLATE_STR "%s/%sXXXXXX"
#endif

char *
sh_mktmpname (nameroot, flags)
     char *nameroot;
     int flags;
{
  char *filename, *tdir, *lroot;
  struct stat sb;
  int r, tdlen;

  filename = (char *)xmalloc (PATH_MAX + 1);
  tdir = get_tmpdir (flags);
  tdlen = strlen (tdir);

  lroot = nameroot ? nameroot : DEFAULT_NAMEROOT;

#ifdef __MSDOS__
  if (pathconf (tdir, _PC_NAME_MAX) <= 12
      && lroot[0] && lroot[1] && lroot[2])
    lroot = "sh";
#endif

#ifdef USE_MKTEMP
  sprintf (filename, TEMPLATE_STR, tdir, lroot);
  if (mktemp (filename) == 0)
    {
      free (filename);
      filename = NULL;
    }
#else  /* !USE_MKTEMP */
  while (1)
    {
      filenum = (filenum << 1) ^
		(unsigned long) time ((time_t *)0) ^
		(unsigned long) dollar_dollar_pid ^
		(unsigned long) ((flags & MT_USERANDOM) ? get_random_number () : ntmpfiles++);
#ifdef __MSDOS__
      sprintf (filename, "%s/%s%lu", tdir, lroot, filenum);
#else
      sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum);
#endif
      if (tmpnamelen > 0 && tmpnamelen < 32)
	filename[tdlen + 1 + tmpnamelen] = '\0';
#  ifdef HAVE_LSTAT
      r = lstat (filename, &sb);
#  else
      r = stat (filename, &sb);
#  endif
      if (r < 0 && errno == ENOENT)
	break;
    }
#endif /* !USE_MKTEMP */

  return filename;
}

int
sh_mktmpfd (nameroot, flags, namep)
     char *nameroot;
     int flags;
     char **namep;
{
  char *filename, *tdir, *lroot;
  int fd, tdlen;

  filename = (char *)xmalloc (PATH_MAX + 1);
  tdir = get_tmpdir (flags);
  tdlen = strlen (tdir);

  lroot = nameroot ? nameroot : DEFAULT_NAMEROOT;

#ifdef __MSDOS__
  if (pathconf (tdir, _PC_NAME_MAX) <= 12
      && lroot[0] && lroot[1] && lroot[2])
    lroot = "sh";
#endif

#ifdef USE_MKSTEMP
  sprintf (filename, TEMPLATE_STR, tdir, lroot);
  fd = mkstemp (filename);
  if (fd < 0 || namep == 0)
    {
      free (filename);
      filename = NULL;
    }
  if (namep)
    *namep = filename;
  return fd;
#else /* !USE_MKSTEMP */
  do
    {
      filenum = (filenum << 1) ^
		(unsigned long) time ((time_t *)0) ^
		(unsigned long) dollar_dollar_pid ^
		(unsigned long) ((flags & MT_USERANDOM) ? get_random_number () : ntmpfiles++);
      sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum);
      if (tmpnamelen > 0 && tmpnamelen < 32)
	filename[tdlen + 1 + tmpnamelen] = '\0';
      fd = open (filename, BASEOPENFLAGS | ((flags & MT_READWRITE) ? O_RDWR : O_WRONLY), 0600);
    }
  while (fd < 0 && errno == EEXIST);

  if (namep)
    *namep = filename;
  else
    free (filename);

  return fd;
#endif /* !USE_MKSTEMP */
}

FILE *
sh_mktmpfp (nameroot, flags, namep)
     char *nameroot;
     int flags;
     char **namep;
{
  int fd;
  FILE *fp;

  fd = sh_mktmpfd (nameroot, flags, namep);
  if (fd < 0)
    return ((FILE *)NULL);
  fp = fdopen (fd, (flags & MT_READWRITE) ? "w+" : "w");
  if (fp == 0)
    close (fd);
  return fp;
}
