OSDN Git Service

Add implementation of mkstemp() and mkdtemp() functions.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Mon, 1 Dec 2014 21:12:27 +0000 (21:12 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Mon, 1 Dec 2014 21:12:27 +0000 (21:12 +0000)
mingwrt/ChangeLog
mingwrt/Makefile.in
mingwrt/include/stdlib.h
mingwrt/mingwex/cryptnam.c [new file with mode: 0644]
mingwrt/mingwex/mkdtemp.c [new file with mode: 0644]
mingwrt/mingwex/mkstemp.c [new file with mode: 0644]

index 7f6df43..b53f32d 100644 (file)
@@ -1,3 +1,31 @@
+2014-12-01  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Add implementation of mkstemp() and mkdtemp() functions.
+
+       * mingwex/cryptnam.c: New file; it implements...
+       (__mingw_crypto_tmpname): ...this helper function; it generates a
+       cryptographically secure sequence of characters, used as the random
+       component in temporary file and directory names.
+
+       * mingwex/mkstemp.c: New file; it implements...
+       (__mingw_mkstemp): ...this provider of mkstemp() functionality.
+
+       * mingwex/mkdtemp.c: New file; it implements...
+       (__mingw_mkdtemp): ...this provider of mkdtemp() functionality.
+
+       * include/stdlib.h (mkstemp): Declare protototye; provide an inline
+       function implementation, with LIBIMPL extern semantics, in terms of...
+       (__mingw_mkstemp): ...this; also declare prototype, and define...
+       (_MKSTEMP_INVOKE, _MKSTEMP_DEFAULT): ...these supporting constants.
+       (_MKSTEMP_SETMODE): New macro; define it, also providing...
+       (MKSTEMP_SETMODE) [!_NO_OLDNAMES]: ...this alias.
+       (mkdtemp): Declare prototype; provide inline implementation, with
+       JMPSTUB extern semantics, in terms of...
+       (__mingw_mkdtemp): ...this; declare prototype.
+
+       * Makefile.in (libmingwex.a): Add dependency rule, to include...
+       (mkstemp.$OBJEXT, mkdtemp.$OBJEXT, cryptname.$OBJEXT): ...these.
+
 2014-11-30  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        More JMPSTUB rationalization of inline functions.
index a711605..36f51af 100644 (file)
@@ -444,6 +444,7 @@ $(addsuffix fmt.$(OBJEXT),varo crto geto seto crtn getn setn): %.$(OBJEXT): ofmt
 #libmingwex.a: $(addsuffix .$(OBJEXT), glob membarrier)
 libmingwex.a: $(addsuffix .$(OBJEXT), mingw-aligned-malloc mingw-fseek glob)
 libmingwex.a: $(addsuffix .$(OBJEXT), getopt basename dirname ftruncate usleep)
+libmingwex.a: $(addsuffix .$(OBJEXT), mkstemp mkdtemp cryptnam)
 
 libmingwex.a: $(addsuffix .$(OBJEXT), tdelete tfind tsearch twalk)
 
index 01fb7a8..71a1b86 100644 (file)
@@ -556,9 +556,51 @@ wchar_t* __cdecl __MINGW_NOTHROW ulltow (unsigned long long _n, wchar_t * _w, in
 #endif /* __MSVCRT__ */
 #endif /* !__NO_ISOCEXT */
 
+#if !defined __STRICT_ANSI__
+/*
+ * POSIX/BSD extensions in libmingwex.a; maybe these should be exposed only on
+ * the basis of some POSIX or BSD specific feature tests, but for convenience,
+ * we require only !__STRICT_ANSI__
+ *
+ *
+ * mkstemp(3) function support; added per feature request #2003.
+ * POSIX wants _XOPEN_SOURCE >= 500, (implying _POSIX_C_SOURCE >= 200112L),
+ * but, as noted above, we will settle for !__STRICT_ANSI__.
+ */
+int __cdecl __MINGW_NOTHROW mkstemp( char * );
+int __cdecl __MINGW_NOTHROW __mingw_mkstemp( int, char * );
+
+/* The following macros are a MinGW specific extension, to facilite
+ * use of _O_TEMPORARY, (in addition to the POSIX required attributes),
+ * when creating the temporary file.  Note that they require fcntl.h,
+ * which stdlib.h should NOT automatically include; we leave the onus
+ * on the user to explicitly include it, if using MKSTEMP_SETMODE.
+ */
+#define _MKSTEMP_INVOKE       0
+#define _MKSTEMP_DEFAULT     _O_CREAT | _O_EXCL | _O_RDWR
+#define _MKSTEMP_SETMODE(M) __mingw_mkstemp( _MKSTEMP_DEFAULT | (M), NULL )
 
+#ifndef _NO_OLDNAMES
+#define MKSTEMP_SETMODE(M)  __mingw_mkstemp( _MKSTEMP_DEFAULT | (M), NULL )
 #endif
 
+__CRT_ALIAS __LIBIMPL__(( FUNCTION = mkstemp ))
+int __cdecl __MINGW_NOTHROW mkstemp( char *__filename_template )
+{ return __mingw_mkstemp( _MKSTEMP_INVOKE, __filename_template ); }
+
+/* mkdtemp(3) function support: added as adjunct to feature request #2003.
+ * POSIX wants _XOPEN_SOURCE >= 700, (implying _POSIX_C_SOURCE >= 200809L),
+ * but once again, we will settle for !__STRICT_ANSI__.
+ */
+char * __cdecl __MINGW_NOTHROW mkdtemp( char * );
+char * __cdecl __MINGW_NOTHROW __mingw_mkdtemp( char * );
+
+__CRT_ALIAS __JMPSTUB__(( FUNCTION = mkdtemp ))
+char * __cdecl __MINGW_NOTHROW mkdtemp( char *__dirname_template )
+{ return __mingw_mkdtemp( __dirname_template ); }
+
+#endif /* (!__STRICT_ANSI__) */
+
 _END_C_DECLS
 
 #endif /* Not RC_INVOKED */
diff --git a/mingwrt/mingwex/cryptnam.c b/mingwrt/mingwex/cryptnam.c
new file mode 100644 (file)
index 0000000..f7f6a0a
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * cryptnam.c
+ *
+ * Implementation of a cryptographically secure random character sequence
+ * generator; this is specifically tailored to satisfy the requirement for
+ * replacement of the sequence of six 'XXXXXX's, within the templates for
+ * the file name, or the directory name, in MinGW.org implementations of
+ * the mkstemp(3) and mkdtemp(3) functions, respectively.
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall  <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2013, 2014, MinGW.org Project.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice, this permission notice, and the following
+ * disclaimer shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+#define WIN32_LEAN_AND_MEAN
+
+#include <limits.h>
+#include <windows.h>
+#include <wincrypt.h>
+#include <string.h>
+
+#define CRYPTO_INLINE  static __inline__ __attribute__((__always_inline__))
+
+CRYPTO_INLINE HCRYPTPROV crypto_provider( void )
+#define RSA_MODE( OPT, FLAG )  PROV_RSA_##OPT, CRYPT_##FLAG
+{
+  /* Helper to establish a cryptographic provider context for the
+   * cryptographically secure random number generator.
+   *
+   * At the outset, this provider requires initialization.
+   */
+  static HCRYPTPROV id = (HCRYPTPROV)(0);
+
+  /* On second, and subsequent calls, it should already have been
+   * initialized...
+   */
+  if( id != (HCRYPTPROV)(0) )
+    /*
+     * ...so, simply return the saved context handle...
+     */
+    return id;
+
+  /* If we're still here, this must be the first call, (or any
+   * preceding call failed to initialize the context); initialize
+   * it now, and if successful...
+   */
+  if( CryptAcquireContext( &id, NULL, NULL, RSA_MODE( FULL, VERIFYCONTEXT ) ) )
+    /*
+     * ...return the now-initialized context handle.
+     */
+    return id;
+
+  /* And finally, if we ever get to here, the context remains
+   * uninitialized; ensure that it remains marked as such, and
+   * return the uninitialized context handle.
+   */
+  return id = (HCRYPTPROV)(0);
+}
+
+CRYPTO_INLINE void *crypto_randomize( void *buf, size_t buflen )
+{
+  /* Helper to fill a specified buffer, of specified length,
+   * with cryptographically secure random bytes...
+   */
+  if( CryptGenRandom( crypto_provider(), buflen, buf ) )
+    /*
+     * ...returning a pointer to the buffer, when successful...
+     */
+    return buf;
+
+  /* ...or nothing, otherwise.
+   */
+  return NULL;
+}
+
+CRYPTO_INLINE unsigned char *crypto_random_filename_char( unsigned char *caret )
+{
+  /* Helper to generate a random sequence of characters, suitable for
+   * use in file names; although there are other valid possibilities, we
+   * restrict this to the set of lower case ASCII alpha-numerics, giving
+   * us 36 degrees of freedom for each character; (note that we cannot
+   * gain additional degrees of freedom by using mixed case, because
+   * the MS-Windows file system is case-insensitive).
+   */
+  const unsigned char span = 'z' - 'a' + 1 + '9' - '0' + 1;
+
+  /* We also wish to ensure that each of the possible 36 characters has
+   * an equal probability of selection; thus, of the UCHAR_MAX possible
+   * raw byte selections, we want to consider at most the largest even
+   * multiple of the 36 character span, which lies below the UCHAR_MAX
+   * limit, (which, since zero is a valid choice, is one less than the
+   * result of discounting the remainder from modulo division).
+   */
+  const unsigned char max = UCHAR_MAX - (UCHAR_MAX % span) - 1;
+
+  /* Deposit randomly selected characters at the "caret" location...
+   */
+  do { if( crypto_randomize( caret, sizeof( unsigned char ) ) == NULL )
+        /*
+         * ...bailing out, on any failure of the sequence generator...
+         */
+         return NULL;
+
+       /* ...until we get one which is within the largest possible
+       * subset which yields equal probabilty to each outcome, when
+       * reduced modulo the 36 available degrees of freedom.
+       */
+     } while( *caret > max );
+
+  /* Perform the modulo 36 reduction, and offset the result into the
+   * alpha-numeric character range...
+   */
+  *caret = '0' + (*caret % span);
+  /*
+   * ...while discounting those unsuitable characters which lie within
+   * the range, between '9' and 'a' exclusively.
+   */
+  if( *caret > '9' ) *caret += 'a' - '9' - 1;
+
+  /* Finally, return the "caret" location, indicating the successful
+   * transformation of the character in that position.
+   */
+  return caret;
+}
+
+char *__mingw_crypto_tmpname( char *template )
+{
+  /* Helper function, based on Microsoft's wincrypt API, to construct
+   * the candidate names for temporary files, both in a less predictable
+   * manner than Microsoft's _mktemp() function, and without suffering
+   * its inherent limitation of allowing no more than 26 file names
+   * per template per process thread.
+   *
+   * We begin by locating the position, within the given template,
+   * where the string of six replaceable 'XXXXXX's should begin.
+   */
+  char *tail = template + strlen( template ) - 6;
+
+  /* Provided this appears sane -- i.e. it at least doesn't place the
+   * six character "tail" before the start of the template itself...
+   */
+  if( tail >= template )
+  {
+    /* ...then, walk over each of the six bytes of the "tail", until
+     * we reach the NUL terminator...
+     */
+    while( *tail )
+    {
+      /* ...checking that each byte is initially ASCII 'X', as POSIX
+       * requires them to be; (note that we don't consider that these
+       * may be MBCS trail bytes, since the required 'X' is a single
+       * byte in an MBCS representation anyway)...
+       */
+      if( (*tail != 'X') || (crypto_random_filename_char( tail++ ) == NULL) )
+       /*
+        * ...bailing out, and returning nothing, if not.
+        */
+       return NULL;
+    }
+  }
+  /* Finally, when we have successfully replaced all six 'XXXXXX's,
+   * we return the modified template, in place.
+   */
+  return template;
+}
+
+/* $RCSfile$: end of file */
diff --git a/mingwrt/mingwex/mkdtemp.c b/mingwrt/mingwex/mkdtemp.c
new file mode 100644 (file)
index 0000000..c91f935
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * mkdtemp.c
+ *
+ * Implementation of an (approximately) POSIX conforming mkdtemp(3).
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall  <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2013, 2014, MinGW.org Project.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice, this permission notice, and the following
+ * disclaimer shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <direct.h>
+#include <errno.h>
+
+/* Candidate names for the temporary directory are generated, based on
+ * a cryptographically secure random character sequence; this externally
+ * implemented character sequence generator is shared by mkstemp(3) and
+ * mkdtemp(3).
+ */
+extern char *__mingw_crypto_tmpname( char * );
+
+char *__mingw_mkdtemp( char *template )
+{
+  /* Formal MinGW implementation of the mkdtemp(3) function; to begin,
+   * check that the caller gave us a viable template...
+   */
+  if( template == NULL )
+  {
+    /* ...bailing out, if nothing at all...
+     */
+    errno = EINVAL;
+  }
+  else
+  { /* ...but assume that anything at all is potentially viable;
+     * set up a retry limit, and estimate the storage requirement
+     * for a working scratch buffer.
+     */
+    int retry = TMP_MAX;
+    size_t bufsiz = 1 + strlen( template );
+
+    /* Until we either successfully create a directory, or we have
+     * exhausted the retry limit while attempting to do so...
+     */
+    while( retry-- > 0 )
+    {
+      /* ...set up the scratch buffer, copy the template into it,
+       * then transform to get a cryptographically secure candidate
+       * name for the temporary directory; (each retry cycle will
+       * generate a randomly differing candidate name)...
+       */
+      char dirname[bufsiz];
+      if( __mingw_crypto_tmpname( strcpy( dirname, template ) ) == NULL )
+      {
+       /* ...bailing out, on any unsuccessful attempt to generate
+        * the candidate name; (this is most likely to occur during
+        * the first cycle, due to a malformed template; if we can
+        * successfully generate the first candidate, successive
+        * attempts are unlikely to fail).
+        */
+       errno = EINVAL;
+       retry = 0;
+      }
+      else
+      { /* We got a usable candidate directory name; try to create
+        * the named directory...
+        */
+       if( mkdir( dirname ) == 0 )
+         /*
+          * ...and, on success, update the template to reflect the
+          * name of the directory we've created, and we are done...
+          */
+         return strcpy( template, dirname );
+
+       /* ...but, if we failed for any reason other than that a file
+        * or a directory with the candidate name already exists...
+        */
+       else if( errno != EEXIST )
+         /*
+          * ...then, any retry will most likely also fail, so we may
+          * as well just give up now.
+          */
+         retry = 0;
+      }
+    }
+  }
+  /* If we get to here, then all of our attempts to create a directory
+   * were unsuccessful; return NULL, to indicate failure.
+   */
+  return NULL;
+}
+
+/* $RCSfile$: end of file */
diff --git a/mingwrt/mingwex/mkstemp.c b/mingwrt/mingwex/mkstemp.c
new file mode 100644 (file)
index 0000000..b71c302
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * mkstemp.c
+ *
+ * Implementation of an (approximately) POSIX conforming mkstemp(3)
+ * function; invocation is via an inline wrapper, defined in stdlib.h,
+ * which delegates to the library routine defined herein.
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall  <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2013, 2014, MinGW.org Project.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice, this permission notice, and the following
+ * disclaimer shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Candidate names for the temporary file are generated, based on a
+ * cryptographically secure random character sequence; this externally
+ * implemented character sequence generator is shared by mkstemp(3)
+ * and mkdtemp(3).
+ */
+extern char *__mingw_crypto_tmpname( char * );
+
+int __mingw_mkstemp( int setmode, char *template )
+{
+  /* Implementation of the low-level functional support for mkstemp(3);
+   * this provides the formal function implementation, including support
+   * for adjustment of its behaviour w.r.t. temporary file persistence.
+   *
+   * By default, temporary files will persist until explicitly deleted;
+   * POSIX prescribes that temporary files are to be created with the
+   * following attributes, (with O_BINARY added, to ensure there is
+   * no undue influence from "helpful" text mode transformations):
+   */
+  static int omode = _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY;
+
+  if( setmode )
+    /* On POSIX platforms, programmers may adopt an idiom such as:
+     *
+     *   if( mkstemp( template ) >= 0 )
+     *   { unlink( template );
+     *     . . .
+     *   }
+     *
+     * to ensure that a temporary file does NOT persist after it is
+     * closed; MS-Windows does not allow such use of unlink(2), while
+     * the file remains open.  Thus, MS-Windows programmers must take
+     * extra care, to close and unlink temporary files AFTER use, if
+     * similar behaviour is desired.
+     *
+     * To mitigate this MS-Windows limitation, we provide support for
+     * an alternative, MinGW specific idiom:
+     *
+     *   #include <fcntl.h>
+     *
+     *   _MKSTEMP_SETMODE( _O_TEMPORARY );
+     *   if( mkstemp( template ) >= 0 )
+     *   {
+     *     . . . 
+     *   }
+     *
+     * to achieve a similar effect to that of the above POSIX idiom.
+     */
+    return omode = (omode & ~_O_TEMPORARY) | (setmode & _O_TEMPORARY);
+
+  else
+  { /* Formal MinGW implementation of the mkstemp(3) function; to begin,
+     * we assume that it may fail, and record an invalid file descriptor
+     * for return in such eventuality.
+     */
+    int fd = -1;
+
+    /* Check that the caller gave us a viable template...
+     */
+    if( template == NULL )
+    {
+      /* ...bailing out, if nothing at all...
+       */
+      errno = EINVAL;
+    }
+    else
+    { /* ...but assume that anything at all is potentially viable;
+       * set up a retry limit, and estimate the storage requirement
+       * for a working scratch buffer.
+       */
+      int retry = TMP_MAX;
+      size_t bufsiz = 1 + strlen( template );
+
+      /* Until we either get a valid file descriptor, or we exhaust
+       * the retry limit while attempting to get one...
+       */
+      while( (fd < 0) && (retry-- > 0) )
+      {
+       /* ...set up the scratch buffer, copy the template into it,
+        * then transform to get a cryptographically secure candidate
+        * file name for the temporary file; (each retry cycle will
+        * generate a randomly differing candidate file name)...
+        */
+       char filename[bufsiz];
+       if( __mingw_crypto_tmpname( strcpy( filename, template ) ) == NULL )
+       {
+         /* ...bailing out, on any unsuccessful attempt to generate
+          * the candidate name; (this is most likely to occur during
+          * the first cycle, due to a malformed template; if we can
+          * successfully generate the first candidate, successive
+          * attempts are unlikely to fail).
+          */
+         errno = EINVAL;
+         retry = 0;
+       }
+       else
+       { /* We got a usable candidate file name; attempt to open it
+          * as a new file...
+          */
+         if( (fd = open( filename, omode, _S_IREAD | _S_IWRITE )) >= 0 )
+           /*
+            * ...and, on success, update the template to reflect the
+            * name of the file we've opened, and we are done...
+            */
+           strcpy( template, filename );
+
+         /* ...but, if we failed for any reason other than that a file
+          * with the candidate name already exists...
+          */
+         else if( errno != EEXIST )
+           /*
+            * ...then, any retry will most likely also fail, so we may
+            * as well just give up now.
+            */
+           retry = 0;
+       }
+      }
+    }
+    /* Finally, whether we succeeded in opening any temporary file, or we
+     * ultimately gave up in disgust, we return the prevailing state of the
+     * file descriptor we attempted to assign.
+     */
+    return fd;
+  }
+}
+
+/* $RCSfile$: end of file */