Blame gnulib/lib/tempname.c

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