/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

The GNU C Library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

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

/* Return nonzero if FILE exists.  */
static int
DEFUN(exists, (file), CONST char *file)
{
  /* We can stat the file even if we can't read its data.  */
  struct stat st;
  int save = errno;
  if (_stat (file, &st) == 0)
    return 1;
  else
    {
      /* We report that the file exists if stat failed for a reason other
	 than nonexistence.  In this case, it may or may not exist, and we
	 don't know; but reporting that it does exist will never cause any
	 trouble, while reporting that it doesn't exist when it does would
	 violate the interface of __stdio_gen_tempname.  */
      int exists = errno != ENOENT;
      errno = save;
      return exists;
    }
}

/* These are the characters used in temporary filenames.  */
static CONST char letters[] =
  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

/* Generate a temporary filename.
   If DIR_SEARCH is nonzero, DIR and PFX are used as
   described for tempnam.  If not, a temporary filename
   in P_tmpdir with no special prefix is generated.  If LENPTR
   is not NULL, *LENPTR is set the to length (including the
   terminating '\0') of the resultant filename, which is returned.
   This goes through a cyclic pattern of all possible filenames
   consisting of five decimal digits of the current pid and three
   of the characters in `letters'.  Data for tempnam and tmpnam
   is kept separate, but when tempnam is using P_tmpdir and no
   prefix (i.e, it is identical to tmpnam), the same data is used.
   Each potential filename is tested for an already-existing file of
   the same name, and no name of an existing file will be returned.
   When the cycle reaches its end (12345ZZZ), NULL is returned.  */
char * tmpnam(char * rbuf)
{
  static CONST char tmpdir[] = P_tmpdir;
  char * dir;
  static struct
    {
      unsigned char digits [4];
    } infos[2], *info;
  static char buf[FILENAME_MAX];
  static pid_t oldpid = (pid_t) 0;
  pid_t pid = _getpid();
  register size_t len, plen, dlen;
  int i, carry;

  plen = 0;
  dir = tmpdir;
  dlen = strlen(tmpdir);
  info = &infos[1];

  if (pid != oldpid)
    {
      oldpid = pid;
      for (i = 0; i < sizeof (info->digits); i++)
	infos[0].digits[i] = infos[1].digits[i] = 0;
    }

  /* Remove trailing slashes from the directory name.  */
  while (dlen > 1 && dir[dlen - 1] == '/')
    --dlen;

  len = 6 + dlen;
  for (;;)
    {
      if (info->digits [sizeof (info->digits) - 1])
	{
	  errno = EEXIST;
	  return NULL;
	}

      if ((sizeof (buf) - sizeof (info->digits)) < len ||
		sprintf(buf, "%.*s/%.5d", (int) dlen, dir,
		pid % 100000) != (int) len)
	return NULL;

      /* Get the last part of string */
      for (i = 0; i < sizeof (info->digits) - 1; i++)
        buf [len++] = letters [info->digits [i]];
      buf [len] = '\0';

      /* Always return a unique string.  */
      carry = ++info->digits [0] / (sizeof (letters) - 1);
      info->digits [0] %= (sizeof (letters) - 1);
      for (i = 1; i < sizeof (info->digits); i++) {
	  info->digits [i] += carry;
	  carry = info->digits [i] / (sizeof (letters) - 1);
	  info->digits [i] %= (sizeof (letters) - 1);
      }

      if (!exists (buf))
	break;
    }
  strcpy(rbuf, buf);
  return buf;
}
