mkstemp() implementation for win32
Asked Answered
L

3

14

Can anybody point me to the code that implements mkstemp() (C/C++) on Win32, or very close analog?

Must be race-free.

It's supposed to look like

#include <windows.h>
#include <io.h>

// port of mkstemp() to win32. race-free.
// behaviour as described in http://linux.die.net/man/3/mkstemp
// 
int mkstemp(char *template) {
     ...
}
Lenny answered 17/5, 2011 at 19:43 Comment(1)
related: #52381144Counterstatement
G
9

You can use the following function which is extracted from wcecompat library (from file src/stdlib_extras.cpp)

/* mkstemp extracted from libc/sysdeps/posix/tempname.c.  Copyright
   (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc.

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

static const char letters[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

/* Generate a temporary file name based on TMPL.  TMPL must match the
   rules for mk[s]temp (i.e. end in "XXXXXX").  The name constructed
   does not exist at the time of the call to mkstemp.  TMPL is
   overwritten with the result.  */
int
mkstemp (char *tmpl)
{
  int len;
  char *XXXXXX;
  static unsigned long long value;
  unsigned long long random_time_bits;
  unsigned int count;
  int fd = -1;
  int save_errno = errno;

  /* A lower bound on the number of temporary files to attempt to
     generate.  The maximum total number of temporary file names that
     can exist for a given template is 62**6.  It should never be
     necessary to try all these combinations.  Instead if a reasonable
     number of names is tried (we define reasonable as 62**3) fail to
     give the system administrator the chance to remove the problems.  */
#define ATTEMPTS_MIN (62 * 62 * 62)

  /* The number of times to attempt to generate a temporary file.  To
     conform to POSIX, this must be no smaller than TMP_MAX.  */
#if ATTEMPTS_MIN < TMP_MAX
  unsigned int attempts = TMP_MAX;
#else
  unsigned int attempts = ATTEMPTS_MIN;
#endif

  len = strlen (tmpl);
  if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
    {
      errno = EINVAL;
      return -1;
    }

/* This is where the Xs start.  */
  XXXXXX = &tmpl[len - 6];

  /* Get some more or less random data.  */
  {
    SYSTEMTIME      stNow;
    FILETIME ftNow;

    // get system time
    GetSystemTime(&stNow);
    stNow.wMilliseconds = 500;
    if (!SystemTimeToFileTime(&stNow, &ftNow))
    {
        errno = -1;
        return -1;
    }

    random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32)
                        | (unsigned long long)ftNow.dwLowDateTime);
  }
  value += random_time_bits ^ (unsigned long long)GetCurrentThreadId ();

  for (count = 0; count < attempts; value += 7777, ++count)
    {
      unsigned long long v = value;

      /* 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];

      fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE);
      if (fd >= 0)
    {
      errno = save_errno;
      return fd;
    }
      else if (errno != EEXIST)
    return -1;
    }

  /* We got out of the loop because we ran out of combinations to try.  */
  errno = EEXIST;
  return -1;
}

It defines O_EXCL as;

#define _O_EXCL         0x0400
#define O_EXCL          _O_EXCL

You can rip out mkstemp support out of it easily.

Gherardo answered 17/5, 2011 at 19:52 Comment(2)
@Andrei, do please note the flaws in this proposed implementation: it incorrectly attributes 62 degrees of freedom to the choice of file name characters, where in reality only 36 exist; (upper and lower case alphabetic letters are indistinguishable in MS-Windows file names). Also, Windows has better APIs for generating cryptographically secure random number sequences, than the technique illustrated here.Plasma
As the top of the code snippet says, this implementation (including the code comments) was taken from glibc, specifically __gen_tempname in sysdeps/posix/tempname.c. As @KeithMarshall points out, its conversion to Windows is incomplete and there might be even more pitfalls...Chaplain
P
5

Actually, using _mktemp_s() is a really bad idea -- only 26 possible file name candidates in any one context, and, with this limited range to be attacked, it exposes the very race condition that mkstemp() is designed to overcome. However, the other proposed solution, while much better, is also flawed, insofar as it attributes 62 degrees of freedom in the choice of substitute file name characters, whereas the case insensitivity of the Windows file system consumes 26 of these, thus leaving only 36; this has the effect of weighting the probability of selecting any logically distinguishable alphabetic character at double that for a numeric.

With this in mind, I've posted a MinGW patch here: https://sourceforge.net/p/mingw/bugs/2003/

If adopted, this will formally add both mkstemp() and mkdtemp() to the standard MinGW distribution.

Plasma answered 18/7, 2013 at 13:35 Comment(2)
For the record, MinGW.org now provide mkstemp() and mkdtemp(), in releases of the MinGW runtime library, mingwrt-3.21 and later, (but not in the fatally flawed mingwrt-4.x variants, which have now been withdrawn from general release).Plasma
Do you have a link regarding the withdrawal? Looking at the sourceforge files section I can see some mingwrt 4er versions being available for download.Hagerman
L
2

You can use _mktemp_s() function, or any of it's variations:

errno_t _mktemp_s(
   char *template,
   size_t sizeInChars
);

where:

  • template: File name pattern.
  • sizeInChars: Size of the buffer in single-byte characters in _mktemp_s; wide characters in _wmktemp_s, including the null terminator.

It returns 0 on success, and an error code on failure. Note that the function modifyes the template argument.

Lemcke answered 17/9, 2012 at 7:38 Comment(1)
Using _mktemp_s() is surely better than reinventing the wheel. But be aware that, unlike POSIX's mkstemp(), it does not open a file or return an fd. It simply returns a filename string, and you must open it yourself.Oomph

© 2022 - 2024 — McMap. All rights reserved.