xref: /reactos/sdk/lib/ucrt/stdio/tempnam.cpp (revision e98e9000)
1 //
2 // tempnam.cpp
3 //
4 //      Copyright (c) Microsoft Corporation.  All rights reserved.
5 //
6 // Defines _tempnam(), which generates temporary file names.
7 //
8 #include <corecrt_internal_stdio.h>
9 
10 
11 
12 // Strips quotes from a string.  If the source string contains no quotes, it is
13 // returned.  If the source string contains quotes, a new string is allocated
14 // and the string is copied into it, character by character, skipping "'s.  If
15 // a non-null pointer is returned, the caller is responsible for freeing it.
16 template <typename Character>
strip_quotes(Character const * const source)17 static Character const* __cdecl strip_quotes(Character const* const source) throw()
18 {
19     // Count the number of quotation marks in the string, and compute the length
20     // of the string, in case we need to allocate a new string:
21     size_t quote_count   = 0;
22     size_t source_length = 0;
23     for (Character const* it = source; *it; ++it)
24     {
25         if (*it == '\"')
26             ++quote_count;
27 
28         ++source_length;
29     }
30 
31     // No quotes?  No problem!
32     if (quote_count == 0)
33         return nullptr;
34 
35     size_t const destination_length = source_length - quote_count + 1;
36     __crt_unique_heap_ptr<Character> destination(_calloc_crt_t(Character, destination_length));
37     if (destination.get() == nullptr)
38         return nullptr;
39 
40     // Copy the string, stripping quotation marks:
41     Character* destination_it = destination.get();
42     for (Character const* source_it = source; *source_it; ++source_it)
43     {
44         if (*source_it == '\"')
45             continue;
46 
47         *destination_it++ = *source_it;
48     }
49 
50     *destination_it = '\0';
51     return destination.detach();
52 }
53 
54 // Gets the path to the %TMP% directory
55 template <typename Character>
get_tmp_directory()56 static Character const* __cdecl get_tmp_directory() throw()
57 {
58     typedef __acrt_stdio_char_traits<Character> stdio_traits;
59 
60     static Character const tmp_name[] = { 'T', 'M', 'P', '\0' };
61 
62     Character* tmp_value = nullptr;
63     if (_ERRCHECK_EINVAL(stdio_traits::tdupenv_s_crt(&tmp_value, nullptr, tmp_name)) != 0)
64         return nullptr;
65 
66     return tmp_value;
67 }
68 
69 
70 
71 // Gets the directory relative to which the temporary name should be formed.
72 // The directory to be used is returned via the 'result' out parameter.  The
73 // '*result' pointer is never null on return.  If '*result' needs to be freed
74 // by the caller, the function also returns the pointer; if null is returned,
75 // no caller cleanup is required.
76 template <typename Character>
get_directory(Character const * const alternative,Character const ** const result)77 static Character const* __cdecl get_directory(
78     Character const*  const alternative,
79     Character const** const result
80     ) throw()
81 {
82     typedef __acrt_stdio_char_traits<Character> stdio_traits;
83 
84     __crt_unique_heap_ptr<Character const> tmp(get_tmp_directory<Character>());
85     if (tmp.get() != nullptr)
86     {
87         if (stdio_traits::taccess_s(tmp.get(), 0) == 0)
88             return *result = tmp.detach();
89 
90         // Otherwise, try stripping quotes out of the TMP path and check again:
91         __crt_unique_heap_ptr<Character const> unquoted_tmp(strip_quotes(tmp.get()));
92         if (unquoted_tmp.get() != nullptr && stdio_traits::taccess_s(unquoted_tmp.get(), 0) == 0)
93             return *result = unquoted_tmp.detach();
94     }
95 
96     // Otherwise, the TMP path is not usable; use the alternative path if one
97     // was provided and is accessible:
98     if (alternative != nullptr && stdio_traits::taccess_s(alternative, 0) == 0)
99         return (*result = alternative), nullptr;
100 
101     // Otherwise, fall back to \ or .:
102     static Character const root_fallback[] = { '\\', '\0' };
103     static Character const cwd_fallback [] = { '.',  '\0' };
104 
105     if (stdio_traits::taccess_s(root_fallback, 0) == 0)
106         return (*result = root_fallback), nullptr;
107 
108     return (*result = cwd_fallback), nullptr;
109 }
110 
111 
112 
113 // The path_buffer is a pointer to the beginning of the buffer into which we
114 // are formatting the temporary path.  The suffix_pointer is a pointer into
115 // that same buffer, to the position where the unique identifier is to be
116 // written.  The suffix_count is the number of characters that can be written
117 // to the suffix_pointer.  The prefix_length is the length of the prefix that
118 // appears before the suffix in the path_buffer already.
119 template <typename Character>
compute_name(Character const * const path_buffer,Character * const suffix_pointer,size_t const suffix_count,size_t const prefix_length)120 static bool __cdecl compute_name(
121     Character const* const path_buffer,
122     Character*       const suffix_pointer,
123     size_t           const suffix_count,
124     size_t           const prefix_length
125     ) throw()
126 {
127     typedef __acrt_stdio_char_traits<Character> stdio_traits;
128 
129     // Re-initialize _tempoff if necessary.  If we don't re-init _tempoff, we
130     // can get into an infinate loop (e.g., (a) _tempoff is a big number on
131     // entry, (b) prefix is a long string (e.g., 8 chars) and all tempfiles
132     // with that prefix exist, (c) _tempoff will never equal first and we'll
133     // loop forever).
134 
135     // [NOTE: To avoid a conflict that causes the same bug as that discussed
136     // above, _tempnam() uses _tempoff; tmpnam() uses _tmpoff]
137 
138     bool return_value = false;
139 
140     __acrt_lock(__acrt_tempnam_lock);
141     __try
142     {
143         if (_old_pfxlen < prefix_length)
144             _tempoff = 1;
145 
146         _old_pfxlen = static_cast<unsigned int>(prefix_length);
147 
148         unsigned const first = _tempoff;
149 
150         errno_t const saved_errno = errno;
151         do
152         {
153             ++_tempoff;
154             if (_tempoff - first > _TMP_MAX_S)
155             {
156                 errno = saved_errno;
157                 __leave;
158             }
159 
160             // The maximum length string returned by the conversion is ten
161             // characters, assuming a 32-bit unsigned integer, so there is
162             // sufficient room in the result buffer for it.
163             _ERRCHECK(stdio_traits::ultot_s(_tempoff, suffix_pointer, suffix_count, 10));
164             errno = 0;
165         }
166         while (stdio_traits::taccess_s(path_buffer, 0) == 0 || errno == EACCES);
167 
168         errno = saved_errno;
169         return_value = true;
170     }
171     __finally
172     {
173         __acrt_unlock(__acrt_tempnam_lock);
174     }
175     __endtry
176 
177     return return_value;
178 }
179 
180 
181 
182 // Generates a unique file name within the temporary directory.  If the TMP
183 // environment variable is defined and is accessible, that directory is used.
184 // Otherwise, the given 'alternative' directory is used, if that is non-null and
185 // the path is accessible.  Otherwise, the root of the drive is used if it is
186 // accessible; otherwise, the local directory is used.
187 //
188 // Returns a pointer to the resulting file name on success; returns nullptr on
189 // failure.  If a non-null pointer is returned, the caller is responsible for
190 // freeing it by calling 'free()'.
191 template <typename Character>
192 _Success_(return != 0)
common_tempnam(Character const * const alternative,Character const * const prefix,int const block_use,char const * const file_name,int const line_number)193 static Character* __cdecl common_tempnam(
194     Character const* const alternative,
195     Character const* const prefix,
196     int              const block_use,
197     char const*      const file_name,
198     int              const line_number
199     ) throw()
200 {
201     // These are referenced only in the Debug CRT build
202     UNREFERENCED_PARAMETER(block_use);
203     UNREFERENCED_PARAMETER(file_name);
204     UNREFERENCED_PARAMETER(line_number);
205 
206     typedef __acrt_stdio_char_traits<Character> stdio_traits;
207 
208     Character const* directory = nullptr;
209     __crt_unique_heap_ptr<Character const> const directory_cleanup(get_directory(alternative, &directory));
210 
211     unsigned const prefix_length = prefix != nullptr
212         ? static_cast<unsigned>(stdio_traits::tcslen(prefix))
213         : 0;
214 
215     // The 12 allows for a backslash, a ten character temporary string, and a
216     // null terminator.
217     unsigned const buffer_size = static_cast<unsigned>(stdio_traits::tcslen(directory)) + prefix_length + 12;
218 
219     __crt_unique_heap_ptr<Character, __crt_public_free_policy> result(
220             static_cast<Character*>(_calloc_dbg(
221                 buffer_size,
222                 sizeof(Character),
223                 block_use,
224                 file_name,
225                 line_number)));
226 
227     if (!result)
228         return nullptr;
229 
230     *result.get() = 0;
231     _ERRCHECK(stdio_traits::tcscat_s(result.get(), buffer_size, directory));
232 
233     if (__crt_stdio_path_requires_backslash(directory))
234     {
235         static Character const backslash[] = { '\\', '\0' };
236         _ERRCHECK(stdio_traits::tcscat_s(result.get(), buffer_size, backslash));
237     }
238 
239     if (prefix != nullptr)
240     {
241         _ERRCHECK(stdio_traits::tcscat_s(result.get(), buffer_size, prefix));
242     }
243 
244     Character* const ptr = result.get() + stdio_traits::tcslen(result.get());
245     size_t     const ptr_size = buffer_size - (ptr - result.get());
246 
247     if (!compute_name(result.get(), ptr, ptr_size, prefix_length))
248         return nullptr;
249 
250     return result.detach();
251 }
252 
253 
254 
_tempnam(char const * const alternative,char const * const prefix)255 extern "C" char* __cdecl _tempnam(
256     char const* const alternative,
257     char const* const prefix
258     )
259 {
260     return common_tempnam(alternative, prefix, _NORMAL_BLOCK, nullptr, 0);
261 }
262 
_wtempnam(wchar_t const * const alternative,wchar_t const * const prefix)263 extern "C" wchar_t* __cdecl _wtempnam(
264     wchar_t const* const alternative,
265     wchar_t const* const prefix
266     )
267 {
268     return common_tempnam(alternative, prefix, _NORMAL_BLOCK, nullptr, 0);
269 }
270 
271 #ifdef _DEBUG
272 
_tempnam_dbg(char const * const alternative,char const * const prefix,int const block_use,char const * const file_name,int const line_number)273 extern "C" char* __cdecl _tempnam_dbg(
274     char const* const alternative,
275     char const* const prefix,
276     int         const block_use,
277     char const* const file_name,
278     int         const line_number
279     )
280 {
281     return common_tempnam(alternative, prefix, block_use, file_name, line_number);
282 }
283 
_wtempnam_dbg(wchar_t const * const alternative,wchar_t const * const prefix,int const block_use,char const * const file_name,int const line_number)284 extern "C" wchar_t* __cdecl _wtempnam_dbg(
285     wchar_t const* const alternative,
286     wchar_t const* const prefix,
287     int            const block_use,
288     char const*    const file_name,
289     int            const line_number
290     )
291 {
292     return common_tempnam(alternative, prefix, block_use, file_name, line_number);
293 }
294 
295 #endif
296