Blame gl/tempname.c

Packit a4aae4
/* tempname.c - generate the name of a temporary file.
Packit a4aae4
Packit a4aae4
   Copyright (C) 1991-2003, 2005-2007, 2009-2017 Free Software Foundation, Inc.
Packit a4aae4
Packit a4aae4
   This program is free software: you can redistribute it and/or modify
Packit a4aae4
   it under the terms of the GNU Lesser General Public License as published by
Packit a4aae4
   the Free Software Foundation; either version 3 of the License, or
Packit a4aae4
   (at your option) any later version.
Packit a4aae4
Packit a4aae4
   This program is distributed in the hope that it will be useful,
Packit a4aae4
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit a4aae4
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit a4aae4
   GNU Lesser General Public License for more details.
Packit a4aae4
Packit a4aae4
   You should have received a copy of the GNU Lesser General Public License
Packit a4aae4
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit a4aae4
Packit a4aae4
/* Extracted from glibc sysdeps/posix/tempname.c.  See also tmpdir.c.  */
Packit a4aae4
Packit a4aae4
#if !_LIBC
Packit a4aae4
# include <config.h>
Packit a4aae4
# include "tempname.h"
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#include <sys/types.h>
Packit a4aae4
#include <assert.h>
Packit a4aae4
Packit a4aae4
#include <errno.h>
Packit a4aae4
#ifndef __set_errno
Packit a4aae4
# define __set_errno(Val) errno = (Val)
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#include <stdio.h>
Packit a4aae4
#ifndef P_tmpdir
Packit a4aae4
# define P_tmpdir "/tmp"
Packit a4aae4
#endif
Packit a4aae4
#ifndef TMP_MAX
Packit a4aae4
# define TMP_MAX 238328
Packit a4aae4
#endif
Packit a4aae4
#ifndef __GT_FILE
Packit a4aae4
# define __GT_FILE      0
Packit a4aae4
# define __GT_DIR       1
Packit a4aae4
# define __GT_NOCREATE  2
Packit a4aae4
#endif
Packit a4aae4
#if !_LIBC && (GT_FILE != __GT_FILE || GT_DIR != __GT_DIR       \
Packit a4aae4
               || GT_NOCREATE != __GT_NOCREATE)
Packit a4aae4
# error report this to bug-gnulib@gnu.org
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#include <stddef.h>
Packit a4aae4
#include <stdlib.h>
Packit a4aae4
#include <string.h>
Packit a4aae4
Packit a4aae4
#include <fcntl.h>
Packit a4aae4
#include <sys/time.h>
Packit a4aae4
#include <stdint.h>
Packit a4aae4
#include <unistd.h>
Packit a4aae4
Packit a4aae4
#include <sys/stat.h>
Packit a4aae4
Packit a4aae4
#if _LIBC
Packit a4aae4
# define struct_stat64 struct stat64
Packit a4aae4
#else
Packit a4aae4
# define struct_stat64 struct stat
Packit a4aae4
# define __try_tempname try_tempname
Packit a4aae4
# define __gen_tempname gen_tempname
Packit a4aae4
# define __getpid getpid
Packit a4aae4
# define __gettimeofday gettimeofday
Packit a4aae4
# define __mkdir mkdir
Packit a4aae4
# define __open open
Packit a4aae4
# define __lxstat64(version, file, buf) lstat (file, buf)
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#ifdef _LIBC
Packit a4aae4
# include <hp-timing.h>
Packit a4aae4
# if HP_TIMING_AVAIL
Packit a4aae4
#  define RANDOM_BITS(Var) \
Packit a4aae4
  if (__builtin_expect (value == UINT64_C (0), 0))                            \
Packit a4aae4
    {                                                                         \
Packit a4aae4
      /* If this is the first time this function is used initialize           \
Packit a4aae4
         the variable we accumulate the value in to some somewhat             \
Packit a4aae4
         random value.  If we'd not do this programs at startup time          \
Packit a4aae4
         might have a reduced set of possible names, at least on slow         \
Packit a4aae4
         machines.  */                                                        \
Packit a4aae4
      struct timeval tv;                                                      \
Packit a4aae4
      __gettimeofday (&tv, NULL);                                             \
Packit a4aae4
      value = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec;                      \
Packit a4aae4
    }                                                                         \
Packit a4aae4
  HP_TIMING_NOW (Var)
Packit a4aae4
# endif
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
/* Use the widest available unsigned type if uint64_t is not
Packit a4aae4
   available.  The algorithm below extracts a number less than 62**6
Packit a4aae4
   (approximately 2**35.725) from uint64_t, so ancient hosts where
Packit a4aae4
   uintmax_t is only 32 bits lose about 3.725 bits of randomness,
Packit a4aae4
   which is better than not having mkstemp at all.  */
Packit a4aae4
#if !defined UINT64_MAX && !defined uint64_t
Packit a4aae4
# define uint64_t uintmax_t
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
#if _LIBC
Packit a4aae4
/* Return nonzero if DIR is an existent directory.  */
Packit a4aae4
static int
Packit a4aae4
direxists (const char *dir)
Packit a4aae4
{
Packit a4aae4
  struct_stat64 buf;
Packit a4aae4
  return __xstat64 (_STAT_VER, dir, &buf) == 0 && S_ISDIR (buf.st_mode);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/* Path search algorithm, for tmpnam, tmpfile, etc.  If DIR is
Packit a4aae4
   non-null and exists, uses it; otherwise uses the first of $TMPDIR,
Packit a4aae4
   P_tmpdir, /tmp that exists.  Copies into TMPL a template suitable
Packit a4aae4
   for use with mk[s]temp.  Will fail (-1) if DIR is non-null and
Packit a4aae4
   doesn't exist, none of the searched dirs exists, or there's not
Packit a4aae4
   enough space in TMPL. */
Packit a4aae4
int
Packit a4aae4
__path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx,
Packit a4aae4
               int try_tmpdir)
Packit a4aae4
{
Packit a4aae4
  const char *d;
Packit a4aae4
  size_t dlen, plen;
Packit a4aae4
Packit a4aae4
  if (!pfx || !pfx[0])
Packit a4aae4
    {
Packit a4aae4
      pfx = "file";
Packit a4aae4
      plen = 4;
Packit a4aae4
    }
Packit a4aae4
  else
Packit a4aae4
    {
Packit a4aae4
      plen = strlen (pfx);
Packit a4aae4
      if (plen > 5)
Packit a4aae4
        plen = 5;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
  if (try_tmpdir)
Packit a4aae4
    {
Packit a4aae4
      d = __secure_getenv ("TMPDIR");
Packit a4aae4
      if (d != NULL && direxists (d))
Packit a4aae4
        dir = d;
Packit a4aae4
      else if (dir != NULL && direxists (dir))
Packit a4aae4
        /* nothing */ ;
Packit a4aae4
      else
Packit a4aae4
        dir = NULL;
Packit a4aae4
    }
Packit a4aae4
  if (dir == NULL)
Packit a4aae4
    {
Packit a4aae4
      if (direxists (P_tmpdir))
Packit a4aae4
        dir = P_tmpdir;
Packit a4aae4
      else if (strcmp (P_tmpdir, "/tmp") != 0 && direxists ("/tmp"))
Packit a4aae4
        dir = "/tmp";
Packit a4aae4
      else
Packit a4aae4
        {
Packit a4aae4
          __set_errno (ENOENT);
Packit a4aae4
          return -1;
Packit a4aae4
        }
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
  dlen = strlen (dir);
Packit a4aae4
  while (dlen > 1 && dir[dlen - 1] == '/')
Packit a4aae4
    dlen--;                     /* remove trailing slashes */
Packit a4aae4
Packit a4aae4
  /* check we have room for "${dir}/${pfx}XXXXXX\0" */
Packit a4aae4
  if (tmpl_len < dlen + 1 + plen + 6 + 1)
Packit a4aae4
    {
Packit a4aae4
      __set_errno (EINVAL);
Packit a4aae4
      return -1;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
  sprintf (tmpl, "%.*s/%.*sXXXXXX", (int) dlen, dir, (int) plen, pfx);
Packit a4aae4
  return 0;
Packit a4aae4
}
Packit a4aae4
#endif /* _LIBC */
Packit a4aae4
Packit a4aae4
/* These are the characters used in temporary file names.  */
Packit a4aae4
static const char letters[] =
Packit a4aae4
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Packit a4aae4
Packit a4aae4
int
Packit a4aae4
__try_tempname (char *tmpl, int suffixlen, void *args,
Packit a4aae4
                int (*tryfunc) (char *, void *))
Packit a4aae4
{
Packit a4aae4
  int len;
Packit a4aae4
  char *XXXXXX;
Packit a4aae4
  static uint64_t value;
Packit a4aae4
  uint64_t random_time_bits;
Packit a4aae4
  unsigned int count;
Packit a4aae4
  int fd = -1;
Packit a4aae4
  int save_errno = errno;
Packit a4aae4
Packit a4aae4
  /* A lower bound on the number of temporary files to attempt to
Packit a4aae4
     generate.  The maximum total number of temporary file names that
Packit a4aae4
     can exist for a given template is 62**6.  It should never be
Packit a4aae4
     necessary to try all of these combinations.  Instead if a reasonable
Packit a4aae4
     number of names is tried (we define reasonable as 62**3) fail to
Packit a4aae4
     give the system administrator the chance to remove the problems.  */
Packit a4aae4
#define ATTEMPTS_MIN (62 * 62 * 62)
Packit a4aae4
Packit a4aae4
  /* The number of times to attempt to generate a temporary file.  To
Packit a4aae4
     conform to POSIX, this must be no smaller than TMP_MAX.  */
Packit a4aae4
#if ATTEMPTS_MIN < TMP_MAX
Packit a4aae4
  unsigned int attempts = TMP_MAX;
Packit a4aae4
#else
Packit a4aae4
  unsigned int attempts = ATTEMPTS_MIN;
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
  len = strlen (tmpl);
Packit a4aae4
  if (len < 6 + suffixlen || memcmp (&tmpl[len - 6 - suffixlen], "XXXXXX", 6))
Packit a4aae4
    {
Packit a4aae4
      __set_errno (EINVAL);
Packit a4aae4
      return -1;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
  /* This is where the Xs start.  */
Packit a4aae4
  XXXXXX = &tmpl[len - 6 - suffixlen];
Packit a4aae4
Packit a4aae4
  /* Get some more or less random data.  */
Packit a4aae4
#ifdef RANDOM_BITS
Packit a4aae4
  RANDOM_BITS (random_time_bits);
Packit a4aae4
#else
Packit a4aae4
  {
Packit a4aae4
    struct timeval tv;
Packit a4aae4
    __gettimeofday (&tv, NULL);
Packit a4aae4
    random_time_bits = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec;
Packit a4aae4
  }
Packit a4aae4
#endif
Packit a4aae4
  value += random_time_bits ^ __getpid ();
Packit a4aae4
Packit a4aae4
  for (count = 0; count < attempts; value += 7777, ++count)
Packit a4aae4
    {
Packit a4aae4
      uint64_t v = value;
Packit a4aae4
Packit a4aae4
      /* Fill in the random bits.  */
Packit a4aae4
      XXXXXX[0] = letters[v % 62];
Packit a4aae4
      v /= 62;
Packit a4aae4
      XXXXXX[1] = letters[v % 62];
Packit a4aae4
      v /= 62;
Packit a4aae4
      XXXXXX[2] = letters[v % 62];
Packit a4aae4
      v /= 62;
Packit a4aae4
      XXXXXX[3] = letters[v % 62];
Packit a4aae4
      v /= 62;
Packit a4aae4
      XXXXXX[4] = letters[v % 62];
Packit a4aae4
      v /= 62;
Packit a4aae4
      XXXXXX[5] = letters[v % 62];
Packit a4aae4
Packit a4aae4
      fd = tryfunc (tmpl, args);
Packit a4aae4
      if (fd >= 0)
Packit a4aae4
        {
Packit a4aae4
          __set_errno (save_errno);
Packit a4aae4
          return fd;
Packit a4aae4
        }
Packit a4aae4
      else if (errno != EEXIST)
Packit a4aae4
        return -1;
Packit a4aae4
    }
Packit a4aae4
Packit a4aae4
  /* We got out of the loop because we ran out of combinations to try.  */
Packit a4aae4
  __set_errno (EEXIST);
Packit a4aae4
  return -1;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
static int
Packit a4aae4
try_file (char *tmpl, void *flags)
Packit a4aae4
{
Packit a4aae4
  int *openflags = flags;
Packit a4aae4
  return __open (tmpl,
Packit a4aae4
                 (*openflags & ~O_ACCMODE)
Packit a4aae4
                 | O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
static int
Packit a4aae4
try_dir (char *tmpl, void *flags _GL_UNUSED)
Packit a4aae4
{
Packit a4aae4
  return __mkdir (tmpl, S_IRUSR | S_IWUSR | S_IXUSR);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
static int
Packit a4aae4
try_nocreate (char *tmpl, void *flags _GL_UNUSED)
Packit a4aae4
{
Packit a4aae4
  struct_stat64 st;
Packit a4aae4
Packit a4aae4
  if (__lxstat64 (_STAT_VER, tmpl, &st) == 0 || errno == EOVERFLOW)
Packit a4aae4
    __set_errno (EEXIST);
Packit a4aae4
  return errno == ENOENT ? 0 : -1;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/* Generate a temporary file name based on TMPL.  TMPL must match the
Packit a4aae4
   rules for mk[s]temp (i.e. end in "XXXXXX", possibly with a suffix).
Packit a4aae4
   The name constructed does not exist at the time of the call to
Packit a4aae4
   __gen_tempname.  TMPL is overwritten with the result.
Packit a4aae4
Packit a4aae4
   KIND may be one of:
Packit a4aae4
   __GT_NOCREATE:       simply verify that the name does not exist
Packit a4aae4
                        at the time of the call.
Packit a4aae4
   __GT_FILE:           create the file using open(O_CREAT|O_EXCL)
Packit a4aae4
                        and return a read-write fd.  The file is mode 0600.
Packit a4aae4
   __GT_DIR:            create a directory, which will be mode 0700.
Packit a4aae4
Packit a4aae4
   We use a clever algorithm to get hard-to-predict names. */
Packit a4aae4
int
Packit a4aae4
__gen_tempname (char *tmpl, int suffixlen, int flags, int kind)
Packit a4aae4
{
Packit a4aae4
  int (*tryfunc) (char *, void *);
Packit a4aae4
Packit a4aae4
  switch (kind)
Packit a4aae4
    {
Packit a4aae4
    case __GT_FILE:
Packit a4aae4
      tryfunc = try_file;
Packit a4aae4
      break;
Packit a4aae4
Packit a4aae4
    case __GT_DIR:
Packit a4aae4
      tryfunc = try_dir;
Packit a4aae4
      break;
Packit a4aae4
Packit a4aae4
    case __GT_NOCREATE:
Packit a4aae4
      tryfunc = try_nocreate;
Packit a4aae4
      break;
Packit a4aae4
Packit a4aae4
    default:
Packit a4aae4
      assert (! "invalid KIND in __gen_tempname");
Packit a4aae4
      abort ();
Packit a4aae4
    }
Packit a4aae4
  return __try_tempname (tmpl, suffixlen, &flags, tryfunc);
Packit a4aae4
}