libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

mhdt_tmpfile.c (23910B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2026 Evgeny Grin (Karlson2k)
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file src/tests/client_server/mhdt_tmpfile.c
     41  * @brief  MHD test framework temporary file helpers
     42  * @author Karlson2k (Evgeny Grin)
     43  */
     44 
     45 #include "mhd_sys_options.h"
     46 
     47 #include <stdint.h>
     48 #include <string.h>
     49 #include <stdlib.h>
     50 #include <fcntl.h>
     51 #include <errno.h>
     52 
     53 #if !defined(_WIN32) || defined(__CYGWIN__)
     54 #  ifdef HAVE_UNISTD_H
     55 #    include <unistd.h>
     56 #  endif
     57 #  include <stdio.h> /* for P_tmpdir */
     58 #else
     59 #  define mhdt_USE_W32  1
     60 #  ifndef WIN32_LEAN_AND_MEAN
     61 #    define WIN32_LEAN_AND_MEAN 1
     62 #  endif
     63 #  include <windows.h>
     64 #  include <wchar.h>
     65 #  include <sys/types.h>
     66 #  include <sys/stat.h>
     67 #  include <io.h>
     68 #endif
     69 
     70 #include "mhdt_tmpfile.h"
     71 
     72 #ifndef mhdt_ARR_CNT
     73 #  define mhdt_ARR_CNT(arr)     (sizeof(arr)/sizeof(arr[0]))
     74 #endif
     75 
     76 #ifndef MHDT_TMP_FILE_BASENAME_PREFIX
     77 #  define MHDT_TMP_FILE_BASENAME_PREFIX "mhdt-tmp-"
     78 #endif
     79 
     80 /* The length of the unique file suffix */
     81 #ifndef MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN
     82 #  define MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN 6
     83 #endif
     84 
     85 #if MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN < 6
     86 #  error MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN is less than 6
     87 #endif
     88 
     89 /* Maximum length of a normalised temporary directory name,
     90    including the trailing slash but excluding the terminating NUL. */
     91 #if !defined(MHDT_FILENAME_MAX)
     92 #  if defined(FILENAME_MAX)
     93 #    define MHDT_FILENAME_MAX     FILENAME_MAX
     94 #  elif defined(MAX_PATH)
     95 #    define MHDT_FILENAME_MAX     MAX_PATH
     96 #  endif
     97 #endif
     98 
     99 #if defined(MHDT_FILENAME_MAX) && (MHDT_FILENAME_MAX + 0) > 4096
    100 #  undef MHDT_FILENAME_MAX
    101 #endif
    102 
    103 #ifndef MHDT_FILENAME_MAX
    104 #  define MHDT_FILENAME_MAX     4096
    105 #endif
    106 
    107 #ifndef mhdt_MAX_TRIES
    108 #  define mhdt_MAX_TRIES 127
    109 #endif
    110 
    111 #define MHDTL_UINT32_MASK       0xFFFFFFFFu
    112 
    113 
    114 static inline uint_least32_t
    115 mhdtl_rotl32 (uint_least32_t val,
    116               unsigned int rot)
    117 {
    118   uint_fast32_t v;
    119 
    120   rot &= 31u;
    121   v = ((uint_fast32_t)val) & MHDTL_UINT32_MASK;
    122 
    123   return (uint_least32_t)
    124          (((v << rot)
    125            | (v >> ((32u - rot) & 31u)))
    126           & MHDTL_UINT32_MASK);
    127 }
    128 
    129 
    130 /**
    131  * Mix bits of two 32-bit values and produce a pseudo-random 32-bit value
    132  * based on them.
    133  * @param a the first value to mix
    134  * @param b the second value to mix
    135  * @return the pseudo-random value produced from the input values
    136  */
    137 static inline uint_least32_t
    138 mhdtl_mix32 (uint_fast32_t a,
    139              uint_fast32_t b)
    140 {
    141   uint_fast32_t res;
    142 
    143   /* Combine the inputs: multiplication by the Golden Ratio's fractional
    144    * part diffuses the bits of 'a' before they meet the bits of 'b',
    145    * while the rotation breaks the symmetry between the two inputs. */
    146   res =
    147     ((((a & MHDTL_UINT32_MASK) * 0x9E3779B9u) & MHDTL_UINT32_MASK)
    148      ^ mhdtl_rotl32 ((uint_least32_t)b, 13u));
    149   /* Re-mix bits better.
    150    * The shifts and the multipliers are the MurmurHash3 32-bit finaliser. */
    151   res ^= res >> 16u;
    152   res = (res * 0x85EBCA6Bu) & MHDTL_UINT32_MASK;
    153   res ^= res >> 13u;
    154   res = (res * 0xC2B2AE35u) & MHDTL_UINT32_MASK;
    155   res ^= res >> 16u;
    156 
    157   return (uint_least32_t)res;
    158 }
    159 
    160 
    161 #ifndef mhdt_USE_W32
    162 
    163 /**
    164  * Try to add the specified directory to the list of the directories used
    165  * for temporary files.
    166  *
    167  * The candidate name is normalised before use: a trailing slash is appended
    168  * if the name does not end with a slash already.
    169  * The candidate is silently ignored if the name is NULL or empty, if
    170  * the normalised name is too long to fit into the list entry, or if
    171  * the normalised name duplicates any name already present in the list.
    172  * The @p tmp_dirs array must have a free entry at the index
    173  * @p *dirs_have_ptr.
    174  *
    175  * @param tmp_dir_name the name of the candidate directory, can be NULL
    176  * @param[in,out] tmp_dirs the array of the names of the directories used
    177  *                         for temporary files
    178  * @param[in,out] dirs_have_ptr the pointer to the variable with the number
    179  *                              of the used entries in the @p tmp_dirs array,
    180  *                              the value is incremented if the candidate
    181  *                              is added
    182  */
    183 static void
    184 tmpdirs_try_add (const char *tmp_dir_name,
    185                  char (*tmp_dirs)[MHDT_FILENAME_MAX + 1],
    186                  unsigned int *dirs_have_ptr)
    187 {
    188   unsigned int i;
    189   char tmp_dir[MHDT_FILENAME_MAX + 1];
    190   size_t tmp_dir_name_len;
    191 
    192   if (NULL == tmp_dir_name)
    193     return;
    194   tmp_dir_name_len = strlen (tmp_dir_name);
    195   if (0u == tmp_dir_name_len)
    196     return;
    197   if (((size_t)MHDT_FILENAME_MAX) < tmp_dir_name_len)
    198     return; /* String 'tmp_dir_name' is too long to use */
    199 
    200   memcpy (tmp_dir,
    201           tmp_dir_name,
    202           tmp_dir_name_len + 1);
    203 
    204   if ('/' != tmp_dir[tmp_dir_name_len - 1u])
    205   {
    206     if (((size_t)MHDT_FILENAME_MAX) == tmp_dir_name_len)
    207       return; /* String 'tmp_dir_name' is too long to use */
    208     tmp_dir[tmp_dir_name_len++] = '/';
    209     tmp_dir[tmp_dir_name_len] = '\0';
    210   }
    211 
    212   for (i = 0; i < *dirs_have_ptr; ++i)
    213     if (0 == strcmp (tmp_dir, tmp_dirs[i]))
    214       return; /* Found duplicate */
    215 
    216   memcpy (tmp_dirs[*dirs_have_ptr], tmp_dir, tmp_dir_name_len + 1u);
    217   ++(*dirs_have_ptr);
    218 }
    219 
    220 
    221 /**
    222  * Fill the buffer with the hexadecimal representation of the number.
    223  *
    224  * Exactly @p buf_size - 1 lower hexadecimal digits of the @p number are
    225  * written (zero-padded), followed by the terminating NUL.
    226  * @param buf_size the size of the @p buf buffer in characters,
    227  *                 must not be zero
    228  * @param[out] buf the buffer to fill
    229  * @param number the number to convert
    230  */
    231 static void
    232 mhdt_num_to_hex (size_t buf_size,
    233                  char *buf,
    234                  uint_least32_t number)
    235 {
    236   static const char hex_map[] = "0123456789abcdef";
    237   size_t p = buf_size - 1u;
    238   if (p > buf_size)
    239     abort ();
    240   buf[p] = '\0';
    241   while (--p < buf_size)
    242   {
    243     buf[p] = hex_map[number & 0xFu];
    244     number >>= 4u;
    245   }
    246 }
    247 
    248 
    249 /**
    250  * Get a directory for temporary files
    251  * @param fallback_num the number of the fallback location,
    252  *                     zero for the main location
    253  * @return the temporary directory string (always ends with a slash, the length
    254  *         of the string is not greater than #MHDT_FILENAME_MAX),
    255  *         NULL if @p fallback_num is too high
    256  */
    257 static const char *
    258 mhdt_get_tmpfile_dir (unsigned int fallback_num)
    259 {
    260   /* 'volatile' does not provide thread-safety, but the stored values are
    261      identical for every initialisation.
    262      This simplification is considered acceptable for the testing code. */
    263   static volatile unsigned int dirs_have = 0u;
    264   static char tmp_dirs[7][MHDT_FILENAME_MAX + 1] =
    265   { "", "", "", "", "", "", "" };
    266 
    267   if (0u == dirs_have)
    268   {
    269     unsigned int dirs_found = 0u;
    270 
    271     tmpdirs_try_add (getenv ("TMPDIR"), tmp_dirs, &dirs_found);
    272 
    273     tmpdirs_try_add (getenv ("TMP"), tmp_dirs, &dirs_found);
    274 
    275     tmpdirs_try_add (getenv ("TEMP"), tmp_dirs, &dirs_found);
    276 
    277     tmpdirs_try_add (".", tmp_dirs, &dirs_found);
    278 
    279 #  ifdef P_tmpdir
    280     tmpdirs_try_add (P_tmpdir, tmp_dirs, &dirs_found);
    281 #  endif
    282 
    283     tmpdirs_try_add ("/tmp", tmp_dirs, &dirs_found);
    284 
    285     if (sizeof(tmp_dirs) / sizeof(tmp_dirs[0]) < dirs_found)
    286       abort ();
    287 
    288     if (0u == dirs_found)
    289       abort ();
    290 
    291     dirs_have = dirs_found;
    292   }
    293 
    294   if (dirs_have <= fallback_num)
    295     return NULL;
    296 
    297   return tmp_dirs[fallback_num];
    298 }
    299 
    300 
    301 /**
    302  * Create a temporary file that is deleted automatically when the FD is
    303  * closed.
    304  *
    305  * The file is opened for both reading and writing.
    306  * The close-on-exec flag is set for the FD (if supported by the platform).
    307  *
    308  * The locations for temporary files are tried in the following order:
    309  * + the directories set by the "TMPDIR", the "TMP" and the "TEMP"
    310  *   environment variables,
    311  * + the current directory,
    312  * + the directory specified by the P_tmpdir macro (if defined),
    313  * + and the "/tmp" directory.
    314  * The locations that are unset, too long or duplicated are skipped.
    315  *
    316  * @return the FD of the created file on success,
    317  *         -1 if the file cannot be created in any of the locations
    318  */
    319 static int
    320 mhdt_create_tmpfile_posix (void)
    321 {
    322   static const char basename_prfx[] = MHDT_TMP_FILE_BASENAME_PREFIX;
    323   const char *tmp_dir;
    324   unsigned int i;
    325 
    326   for (i = 0u; NULL != (tmp_dir = mhdt_get_tmpfile_dir (i)); ++i)
    327   {
    328     char tmp_filename[MHDT_FILENAME_MAX
    329                       + mhdt_ARR_CNT (basename_prfx)
    330                       + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN];
    331     int tmpfile_fd;
    332     int need_cloexec;
    333     int need_unlink;
    334     size_t tmp_dir_len;
    335 
    336     tmp_dir_len = strlen (tmp_dir);
    337     memcpy (tmp_filename, tmp_dir, tmp_dir_len);
    338     memcpy (tmp_filename + tmp_dir_len,
    339             basename_prfx,
    340             mhdt_ARR_CNT (basename_prfx) - 1u);
    341     memset (tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u,
    342             'X',
    343             MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN);
    344     tmp_filename[tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u
    345                  + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN] = '\0';
    346 
    347     tmpfile_fd = -1;
    348     need_cloexec = !0;
    349     need_unlink = !0;
    350 
    351 #  ifdef O_TMPFILE
    352     if (0 > tmpfile_fd)
    353     {
    354       int flags;
    355       flags = O_RDWR | O_TMPFILE;
    356 #    ifdef O_EXCL
    357       flags |= O_EXCL;
    358 #    else
    359       /* The missing O_EXCL support cannot be replaced with anything.
    360          Open file without O_EXCL as this is only the testing code. */
    361 #    endif
    362 #    ifdef O_CLOEXEC
    363       flags |= O_CLOEXEC;
    364 #    endif
    365 #    if defined(O_CLOFORK) && !defined(__linux__)
    366       flags |= O_CLOFORK;
    367 #    endif
    368 
    369       tmpfile_fd = open (tmp_dir,
    370                          flags,
    371                          0600);
    372       if (0 <= tmpfile_fd)
    373       {
    374         need_unlink = 0;
    375 #    ifdef O_CLOEXEC
    376         need_cloexec = 0;
    377 #    endif
    378       }
    379     }
    380 #  endif
    381 
    382 #  ifdef HAVE_MKOSTEMP
    383     if (0 > tmpfile_fd)
    384     {
    385       int flags;
    386       flags = 0;
    387 
    388 #    ifdef O_CLOEXEC
    389       flags |= O_CLOEXEC;
    390 #    endif
    391 #    if defined(O_CLOFORK) && !defined(__linux__)
    392       flags |= O_CLOFORK;
    393 #    endif
    394       tmpfile_fd = mkostemp (tmp_filename,
    395                              flags);
    396 #    ifdef O_CLOEXEC
    397       if (0 <= tmpfile_fd)
    398         need_cloexec = 0;
    399 #    endif
    400     }
    401 #  elif defined(HAVE_MKSTEMP)
    402     if (0 > tmpfile_fd)
    403       tmpfile_fd = mkstemp (tmp_filename);
    404 #  endif
    405 
    406     /* Last resort */
    407     if (0 > tmpfile_fd)
    408     {
    409       static volatile unsigned int cnt = 0u;
    410       int flags;
    411       int t;
    412       uint_least32_t suff_num;
    413 
    414 #  if defined(HAVE_MKOSTEMP) || defined(HAVE_MKSTEMP)
    415       /* Restore the filename template in case it was broken */
    416       memcpy (tmp_filename, tmp_dir, tmp_dir_len);
    417       memcpy (tmp_filename + tmp_dir_len,
    418               basename_prfx,
    419               mhdt_ARR_CNT (basename_prfx) - 1u);
    420 #  endif /* HAVE_MKOSTEMP || HAVE_MKSTEMP */
    421 
    422       flags = O_CREAT | O_RDWR;
    423       /* Last resort: do not try O_CLOFORK as it may be unsupported */
    424 #  ifdef O_EXCL
    425       flags |= O_EXCL;
    426 #  else
    427       /* The missing O_EXCL support cannot be replaced with anything.
    428          Open file without O_EXCL as this is only the testing code. */
    429 #  endif
    430 #  ifdef O_CLOEXEC
    431       flags |= O_CLOEXEC;
    432 #  endif
    433       suff_num = mhdtl_mix32 ((uint_least32_t)getpid (),
    434                               (uint_least32_t)mhd_PTR_TO_INT (tmp_filename));
    435       for (t = 0; mhdt_MAX_TRIES > t; ++t)
    436       {
    437         char *suff_ptr;
    438         suff_num = mhdtl_mix32 (suff_num, (uint_least32_t)cnt++);
    439         suff_ptr =
    440           tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u;
    441 
    442         mhdt_num_to_hex (MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN + 1u,
    443                          suff_ptr,
    444                          suff_num);
    445 
    446         tmpfile_fd = open (tmp_filename,
    447                            flags,
    448                            0600);
    449         if (0 <= tmpfile_fd)
    450         {
    451 #  ifdef O_CLOEXEC
    452           need_cloexec = 0;
    453 #  endif
    454           break;
    455         }
    456         else if ((EEXIST != errno) && (EINTR != errno))
    457           break; /* Avoid useless retries */
    458       }
    459     }
    460 
    461     if (0 > tmpfile_fd)
    462       continue; /* Try next directory */
    463 
    464     /* Best effort to set CLOEXEC */
    465 #  ifdef FD_CLOEXEC
    466     if (need_cloexec)
    467     {
    468       int flags;
    469 
    470       flags = fcntl (tmpfile_fd,
    471                      F_GETFD);
    472       if (0 <= flags)
    473       {
    474         flags |= FD_CLOEXEC;
    475 #    if defined(FD_CLOFORK)
    476         if (0 == fcntl (tmpfile_fd,
    477                         F_SETFD,
    478                         flags | FD_CLOFORK))
    479           (void)0;  /* Success. Do nothing. */
    480         else
    481 #    endif
    482         if (!0)
    483         { /* FD_CLOFORK may be unsupported */
    484           int res;
    485           res = fcntl (tmpfile_fd,
    486                        F_SETFD,
    487                        flags);
    488           (void)res;  /* Ignore result */
    489         }
    490       }
    491     }
    492 #  else
    493     (void)need_cloexec;  /* Use without CLOEXEC */
    494 #  endif
    495 
    496     if (need_unlink)
    497     {
    498       if (0 != unlink (tmp_filename))
    499       {
    500         int res;
    501         /* The opened file cannot be unlinked. */
    502         res = close (tmpfile_fd);
    503         (void)res;  /* Ignore result */
    504         res = unlink (tmp_filename); /* Retry the removal with closed FD */
    505         (void)res;  /* Ignore result */
    506         continue; /* Try next directory */
    507       }
    508     }
    509 
    510     return tmpfile_fd;
    511   }
    512 
    513   return -1;
    514 }
    515 
    516 
    517 #else  /* W32 */
    518 
    519 /**
    520  * Add a temporary-directory candidate to the list.
    521  *
    522  * The candidate is normalised in place to end with a backslash. It is ignored
    523  * if NULL, empty, too long, or an exact duplicate of an existing entry.
    524  * @param tmp_dir_name_len length of @p tmp_dir_name, excluding the NUL
    525  * @param[in,out] tmp_dir_name writable NUL-terminated buffer of
    526  *                             #MHDT_FILENAME_MAX + 1 characters
    527  * @param[in,out] tmp_dirs destination array with a free entry at
    528  *                         @p *dirs_have_ptr
    529  * @param[in,out] dirs_have_ptr number of used entries, incremented on addition
    530  */
    531 static void
    532 tmpdirs_try_add_w32 (unsigned long tmp_dir_name_len,
    533                      wchar_t tmp_dir_name[MHDT_FILENAME_MAX + 1],
    534                      wchar_t (*tmp_dirs)[MHDT_FILENAME_MAX + 1],
    535                      unsigned int *dirs_have_ptr)
    536 {
    537   unsigned int i;
    538   size_t tmp_dir_name_len_sz;
    539 
    540   if (NULL == tmp_dir_name)
    541     return;
    542   if (0u == tmp_dir_name_len)
    543     return;
    544   if (((unsigned long)MHDT_FILENAME_MAX) < tmp_dir_name_len)
    545     return; /* String 'tmp_dir_name' cannot fit the buffer */
    546   tmp_dir_name_len_sz = (size_t)tmp_dir_name_len;
    547 
    548 
    549   if (L'/' == tmp_dir_name[tmp_dir_name_len_sz - 1u])
    550     tmp_dir_name[tmp_dir_name_len_sz - 1u] = L'\\';
    551 
    552   if (L'\\' != tmp_dir_name[tmp_dir_name_len_sz - 1u])
    553   {
    554     if (((size_t)MHDT_FILENAME_MAX) == tmp_dir_name_len_sz)
    555       return; /* String 'tmp_dir_name' is too long to use */
    556     /* This converts a drive-relative path "C:" to the root dir "C:\" which is
    557        the desired effect (a drive-relative path is a bad choice). */
    558     tmp_dir_name[tmp_dir_name_len_sz++] = L'\\';
    559     tmp_dir_name[tmp_dir_name_len_sz] = L'\0';
    560   }
    561 
    562   /* Simplified naive deduplication: not all slashes are normalised and
    563      characters are not case-folded.
    564      The filesystems on W32 can be case-sensitive (even with a per-directory
    565      attribute). */
    566   for (i = 0; i < *dirs_have_ptr; ++i)
    567     if (0 == wcscmp (tmp_dir_name, tmp_dirs[i]))
    568       return; /* Found duplicate */
    569 
    570   wmemcpy (tmp_dirs[*dirs_have_ptr],
    571            tmp_dir_name,
    572            tmp_dir_name_len_sz + 1u);
    573   ++(*dirs_have_ptr);
    574 }
    575 
    576 
    577 /**
    578  * Get a directory for temporary files
    579  * @param fallback_num the number of the fallback location,
    580  *                     zero for the main location
    581  * @return the temporary directory string (always ends with a backslash,
    582  *         the length of the string is not greater than #MHDT_FILENAME_MAX),
    583  *         NULL if @p fallback_num is too high
    584  */
    585 static const wchar_t *
    586 mhdt_get_tmpfile_dir_w32 (unsigned int fallback_num)
    587 {
    588   /* 'volatile' does not provide thread-safety, but the stored values are
    589      identical for every initialisation.
    590      This simplification is considered acceptable for the testing code. */
    591   static volatile unsigned int dirs_have = 0u;
    592   static wchar_t tmp_dirs[6][MHDT_FILENAME_MAX + 1] =
    593   { L"", L"", L"", L"", L"", L"" };
    594   wchar_t var_buf[MHDT_FILENAME_MAX + 1];
    595   const unsigned int var_buf_size = (sizeof(var_buf) / sizeof(var_buf[0]));
    596 
    597   if (0u == dirs_have)
    598   {
    599     unsigned int dirs_found = 0u;
    600 
    601     tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"TMP",
    602                                                   var_buf,
    603                                                   var_buf_size),
    604                          var_buf,
    605                          tmp_dirs,
    606                          &dirs_found);
    607 
    608     tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"TEMP",
    609                                                   var_buf,
    610                                                   var_buf_size),
    611                          var_buf,
    612                          tmp_dirs,
    613                          &dirs_found);
    614 
    615     wmemcpy (var_buf, L".\\", 2u + 1u);
    616     tmpdirs_try_add_w32 (2u,
    617                          var_buf,
    618                          tmp_dirs,
    619                          &dirs_found);
    620 
    621     tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"USERPROFILE",
    622                                                   var_buf,
    623                                                   var_buf_size),
    624                          var_buf,
    625                          tmp_dirs,
    626                          &dirs_found);
    627 
    628     /* This is mostly a duplication of one of the previous values */
    629     tmpdirs_try_add_w32 (GetTempPathW (var_buf_size,
    630                                        var_buf),
    631                          var_buf,
    632                          tmp_dirs,
    633                          &dirs_found);
    634 
    635     if (sizeof(tmp_dirs) / sizeof(tmp_dirs[0]) < dirs_found)
    636       abort ();
    637 
    638     if (0u == dirs_found)
    639       abort ();
    640 
    641     dirs_have = dirs_found;
    642   }
    643 
    644   if (dirs_have <= fallback_num)
    645     return NULL;
    646 
    647   return tmp_dirs[fallback_num];
    648 }
    649 
    650 
    651 /**
    652  * Fill the buffer with the hexadecimal representation of the number.
    653  *
    654  * Exactly @p buf_size - 1 lower hexadecimal digits of the @p number are
    655  * written (zero-padded), followed by the terminating NUL.
    656  * @param buf_size the size of the @p buf buffer in characters,
    657  *                 must not be zero
    658  * @param[out] buf the buffer to fill
    659  * @param number the number to convert
    660  */
    661 static void
    662 mhdt_num_to_hex_w (size_t buf_size,
    663                    wchar_t *buf,
    664                    uint_least32_t number)
    665 {
    666   static const wchar_t hex_map[] = L"0123456789abcdef";
    667   size_t p = buf_size - 1u;
    668   if (p > buf_size)
    669     abort ();
    670   buf[p] = L'\0';
    671   while (--p < buf_size)
    672   {
    673     buf[p] = hex_map[number & 0xFu];
    674     number >>= 4u;
    675   }
    676 }
    677 
    678 
    679 #  define mhdt_WSTR_(str) L ## str
    680 #  define mhdt_WSTR(str)  mhdt_WSTR_(str)
    681 
    682 #  define MHDT_TMP_FILE_BASENAME_PREFIX_L\
    683   mhdt_WSTR(MHDT_TMP_FILE_BASENAME_PREFIX)
    684 
    685 /**
    686  * Create a temporary file that is deleted automatically when the FD is
    687  * closed.
    688  *
    689  * The file is opened for both reading and writing in binary mode.
    690  * The FD is not inherited by child processes.
    691  *
    692  * The locations for temporary files are tried in the following order:
    693  * + the directories set by the "TMP" and the "TEMP" environment variables,
    694  * + the current directory,
    695  * + the directory set by "USERPROFILE",
    696  * + and the system default directory for temporary files.
    697  * The locations that are unset, too long or duplicated are skipped.
    698  *
    699  * @return the FD of the created file on success,
    700  *         -1 if the file cannot be created in any of the locations
    701  */
    702 static int
    703 mhdt_create_tmpfile_w32 (void)
    704 {
    705   static const wchar_t basename_prfx[] = MHDT_TMP_FILE_BASENAME_PREFIX_L;
    706   const wchar_t *tmp_dir;
    707   unsigned int i;
    708 
    709   for (i = 0u; NULL != (tmp_dir = mhdt_get_tmpfile_dir_w32 (i)); ++i)
    710   {
    711     wchar_t tmp_filename[MHDT_FILENAME_MAX
    712                          + mhdt_ARR_CNT (basename_prfx)
    713                          + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN];
    714     int tmpfile_fd;
    715     size_t tmp_dir_len;
    716     static volatile unsigned int cnt = 0u;
    717     int t;
    718     int dir_access_checked;
    719     uint_least32_t suff_num;
    720 
    721     tmp_dir_len = wcslen (tmp_dir);
    722     wmemcpy (tmp_filename,
    723              tmp_dir,
    724              tmp_dir_len);
    725     wmemcpy (tmp_filename + tmp_dir_len,
    726              basename_prfx,
    727              mhdt_ARR_CNT (basename_prfx));
    728 
    729     tmpfile_fd = -1;
    730     dir_access_checked = 0;
    731 
    732     suff_num = mhdtl_mix32 ((uint_least32_t)GetCurrentProcessId (),
    733                             (uint_least32_t)mhd_PTR_TO_INT (tmp_filename));
    734     for (t = 0; mhdt_MAX_TRIES > t; ++t)
    735     {
    736       wchar_t *suff_ptr;
    737       suff_num = mhdtl_mix32 (suff_num,
    738                               (uint_least32_t)cnt++);
    739       suff_ptr = tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u;
    740 
    741       mhdt_num_to_hex_w (MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN + 1u,
    742                          suff_ptr,
    743                          suff_num);
    744 
    745       tmpfile_fd = _wopen (tmp_filename,
    746                            _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY
    747                            | _O_TEMPORARY | _O_SHORT_LIVED | _O_NOINHERIT,
    748                            _S_IREAD | _S_IWRITE);
    749       if (0 <= tmpfile_fd)
    750         break;
    751       if (EEXIST == errno)
    752         continue; /* Filename collision, try another filename */
    753       if (EACCES == errno)
    754       {
    755         HANDLE h;
    756         if (dir_access_checked)
    757           continue;
    758 
    759         h = CreateFileW (tmp_dir,
    760                          FILE_ADD_FILE,
    761                          FILE_SHARE_READ | FILE_SHARE_WRITE
    762                          | FILE_SHARE_DELETE,
    763                          NULL,
    764                          OPEN_EXISTING,
    765                          FILE_FLAG_BACKUP_SEMANTICS,  /* Required to open a directory */
    766                          NULL);
    767         if (INVALID_HANDLE_VALUE == h)
    768           break;  /* Directory not accessible, try next directory */
    769         (void)CloseHandle (h);
    770         dir_access_checked = !0;
    771         continue;  /* Conflict with a file being deleted, try another filename */
    772       }
    773       break; /* Avoid useless retries */
    774     }
    775 
    776     if (0 > tmpfile_fd)
    777       continue; /* Try next directory */
    778 
    779     return tmpfile_fd;
    780   }
    781 
    782   return -1;
    783 }
    784 
    785 
    786 #endif /* W32 */
    787 
    788 int
    789 MHDT_create_tmpfile (void)
    790 {
    791 #ifndef mhdt_USE_W32
    792   return mhdt_create_tmpfile_posix ();
    793 #else
    794   return mhdt_create_tmpfile_w32 ();
    795 #endif
    796 }