xref: /reactos/sdk/lib/ucrt/stdio/tmpfile.cpp (revision a6a07059)
1 //
2 // tmpfile.cpp
3 //
4 //      Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // Defines tmpfile() and tmpfile_s(), which create temporary files, and tmpnam()
7 // and tmpnam_s(), which generate temporary file names (and are used by the
8 // tmpfile() functions).
9 //
10 #include <corecrt_internal_stdio.h>
11 #include <sys/stat.h>
12 
13 
14 
15 // These are static buffers used by tmpnam() and tmpfile() to build file names.
16 // The sizes are computed as follows:  The tmpname string looks like so:
17 //
18 //     PrefixFirstPart.SecondPart
19 //
20 // Prefix is "s" (1 character in length).  FirstPart is generated by converting
21 // the process identifier to a base 32 string.  The maximum process identifier
22 // so converted is 7 characters in length.  The "." is one character in length.
23 // This gives a total of 1 + 7 + 1 = 9 for the "PrefixFirstPart." part of the
24 // file name.
25 //
26 // The SecondPart is generated by converting a number to a string.  In tmpnam,
27 // the maximum number is SHRT_MAX, the maximum length of which is 3 characters.
28 // In tmpnam_s, the maximum number is INT_MAX, which is 7 characters long.
29 //
30 // The tmpnam paths are generated relative to "\\", so the total lengths for the
31 // two functions are the length of that string plus 12 or plus 16 characters.
32 //
33 // The tmpfile path is generated in a manner similar to tmpnam, but relative to
34 // an arbitrary path, so MAX_PATH is used in lieu of _countof("\\").
35 namespace {
36 
37     enum class buffer_id
38     {
39         tmpnam,
40         tmpfile,
41         tmpnam_s,
42         count
43     };
44 }
45 
46 static char*    narrow_tmpfile_buffer_pointers[static_cast<size_t>(buffer_id::count)];
47 static wchar_t* wide_tmpfile_buffer_pointers  [static_cast<size_t>(buffer_id::count)];
48 
49 extern "C" void __cdecl __acrt_stdio_free_tmpfile_name_buffers_nolock()
50 {
51     for (char*& p : narrow_tmpfile_buffer_pointers)
52     {
53         _free_crt(p);
54         p = nullptr;
55     }
56 
57     for (wchar_t*& p : wide_tmpfile_buffer_pointers)
58     {
59         _free_crt(p);
60         p = nullptr;
61     }
62 }
63 
64 static char*& __cdecl get_tmpfile_buffer_pointer_nolock(buffer_id const id, char) throw()
65 {
66     return narrow_tmpfile_buffer_pointers[static_cast<size_t>(id)];
67 }
68 
69 static wchar_t*& __cdecl get_tmpfile_buffer_pointer_nolock(buffer_id const id, wchar_t) throw()
70 {
71     return wide_tmpfile_buffer_pointers[static_cast<size_t>(id)];
72 }
73 
74 template <typename Character>
75 _Success_(return != nullptr)
76 static Character* __cdecl get_tmpfile_buffer_nolock(buffer_id const id) throw()
77 {
78     Character*& buffer_pointer = get_tmpfile_buffer_pointer_nolock(id, Character());
79     if (!buffer_pointer)
80     {
81         buffer_pointer = _calloc_crt_t(Character, L_tmpnam).detach();
82     }
83 
84     return buffer_pointer;
85 }
86 
87 template <typename Character>
88 _Success_(return == true)
89 static bool __cdecl initialize_tmpfile_buffer_nolock(buffer_id const buffer_id) throw()
90 {
91     typedef __acrt_stdio_char_traits<Character> stdio_traits;
92 
93     Character* const buffer       = get_tmpfile_buffer_nolock<Character>(buffer_id);
94     size_t     const buffer_count = L_tmpnam;
95 
96     if (!buffer)
97     {
98         return false;
99     }
100 
101     // The temporary path must be short enough so that we can append a file name
102     // of the form [buffer id][process id].[unique id], which is at most 21
103     // characters in length (plus we must leave room for the null terminator).
104     //    1 Buffer Id              ("s", "t", or "u")
105     //    7 Base-36 Process Id     (maximum: "1z141z3")
106     //   13 Base-36 Unique File Id (maximum: "3w5e11264sgsf")
107     DWORD const max_supported_temp_path_length = buffer_count - 22;
108 
109     // Generate the path prefix; make sure it ends with a slash or backslash:
110     // CRT_REFACTOR TODO We need to use the WinRT temp path logic here.
111     DWORD const temp_path_length = stdio_traits::get_temp_path(static_cast<DWORD>(buffer_count), buffer);
112     if (temp_path_length == 0 || temp_path_length > max_supported_temp_path_length)
113     {
114         buffer[0] = '\0';
115         return false;
116     }
117 
118     Character* tail = buffer + temp_path_length;
119 
120     auto tail_count = [&](){ return buffer_count - (tail - buffer); };
121 
122     // Append the buffer identifier part of the file name:
123     switch (buffer_id)
124     {
125     case buffer_id::tmpnam:   *tail++ = sizeof(Character) == 1 ? 's' : 'v'; break;
126     case buffer_id::tmpfile:  *tail++ = sizeof(Character) == 1 ? 't' : 'w'; break;
127     case buffer_id::tmpnam_s: *tail++ = sizeof(Character) == 1 ? 'u' : 'x'; break;
128     }
129 
130     // Append the process identifier part of the file name:
131     _ERRCHECK(stdio_traits::ulltot_s(GetCurrentProcessId(), tail, tail_count(), 36));
132     tail += stdio_traits::tcslen(tail);
133 
134     // Append the dot part of the file name and the initial unique id:
135     *tail++ = '.';
136     *tail++ = '0';
137     *tail++ = '\0';
138 
139     return true;
140 }
141 
142 
143 
144 template <typename Character>
145 _Success_(return == true)
146 static bool __cdecl generate_tmpfile_file_name(
147     _Inout_updates_z_(file_name_count) Character* const file_name,
148     _In_ size_t                                   const file_name_count
149     ) throw()
150 {
151     typedef __acrt_stdio_char_traits<Character> stdio_traits;
152 
153     Character* const dot = reinterpret_cast<Character*>(stdio_traits::tcsrchr(file_name, '.'));
154     _VALIDATE_RETURN_NOERRNO(dot != nullptr,                                         false);
155     _VALIDATE_RETURN_NOERRNO(dot >= file_name,                                       false);
156     _VALIDATE_RETURN_NOERRNO(file_name_count > static_cast<size_t>(dot - file_name), false);
157 
158     Character* const unique_id = dot + 1;
159     size_t     const unique_id_count = file_name_count - (unique_id - file_name);
160 
161     uint64_t const next_identifier = stdio_traits::tcstoull(unique_id, nullptr, 36) + 1;
162     if (next_identifier == 0)
163         return false;
164 
165 #pragma warning(disable:__WARNING_POSTCONDITION_NULLTERMINATION_VIOLATION) // 26036 Prefast doesn't understand perfect forwarding.
166     _ERRCHECK(stdio_traits::ulltot_s(next_identifier, unique_id, unique_id_count, 36));
167     return true;
168 }
169 
170 
171 
172 static char** __cdecl get_tmpnam_ptd_buffer(char) throw()
173 {
174     __acrt_ptd* const ptd = __acrt_getptd_noexit();
175     if (ptd == nullptr)
176         return nullptr;
177 
178     return &ptd->_tmpnam_narrow_buffer;
179 }
180 
181 static wchar_t** __cdecl get_tmpnam_ptd_buffer(wchar_t) throw()
182 {
183     __acrt_ptd* const ptd = __acrt_getptd_noexit();
184     if (ptd == nullptr)
185         return nullptr;
186 
187     return &ptd->_tmpnam_wide_buffer;
188 }
189 
190 
191 
192 template <typename Character>
193 _Success_(return == 0)
194 static errno_t __cdecl common_tmpnam_nolock(
195     _Out_writes_opt_z_(result_buffer_count) Character* const result_buffer,
196     size_t                                             const result_buffer_count,
197     buffer_id                                          const buffer_id
198     ) throw()
199 {
200     typedef __acrt_stdio_char_traits<Character> stdio_traits;
201 
202     Character* const global_buffer       = get_tmpfile_buffer_nolock<Character>(buffer_id);
203     size_t     const global_buffer_count = L_tmpnam;
204 
205     if (!global_buffer)
206     {
207         return ENOMEM;
208     }
209 
210     // Initialize the tmpnam buffer if it has not yet been initialized.
211     // Otherwise, generate the next file name:
212     if (global_buffer[0] == 0)
213     {
214         initialize_tmpfile_buffer_nolock<Character>(buffer_id);
215     }
216     else if (!generate_tmpfile_file_name(global_buffer, global_buffer_count))
217     {
218         return ENOENT;
219     }
220 
221     // Generate a file name that does not already exist:
222     while (stdio_traits::taccess_s(global_buffer, 0) == 0)
223     {
224         if (!generate_tmpfile_file_name(global_buffer, global_buffer_count))
225         {
226             return ENOENT;
227         }
228     }
229 
230     // If the result buffer is non-null, copy the file name there, if it will fit:
231     if (result_buffer != nullptr)
232     {
233         if (buffer_id != buffer_id::tmpnam &&
234             stdio_traits::tcslen(global_buffer) >= result_buffer_count)
235         {
236             if (result_buffer_count != 0)
237             {
238                 result_buffer[0] = 0;
239             }
240 
241             return ERANGE;
242         }
243 
244 #pragma warning(suppress:__WARNING_POSTCONDITION_NULLTERMINATION_VIOLATION) // 26036 Prefast doesn't understand perfect forwarding.
245         _ERRCHECK(stdio_traits::tcscpy_s(result_buffer, result_buffer_count, global_buffer));
246 
247         return 0;
248     }
249 
250     // If the result buffer is null, use a buffer owned by the per-thread data:
251     _ASSERTE(buffer_id == buffer_id::tmpnam);
252 
253     Character** const ptd_buffer = get_tmpnam_ptd_buffer(Character());
254     if (ptd_buffer == nullptr)
255     {
256         return ENOMEM;
257     }
258 
259     if (*ptd_buffer == nullptr)
260     {
261         *ptd_buffer = _calloc_crt_t(Character, global_buffer_count).detach();
262         if (*ptd_buffer == nullptr)
263         {
264             return ENOMEM;
265         }
266     }
267 
268     _ERRCHECK(stdio_traits::tcscpy_s(*ptd_buffer, global_buffer_count, global_buffer));
269     return 0;
270 }
271 
272 
273 
274 // Generates a temporary file name that is unique in the directory specified by
275 // the related macros in <stdio.h>.  Returns the file name in the result_buffer,
276 // or in a per-thread buffer if the result_buffer is null.  Returns zero on
277 // success; returns an error code and sets errno on failure.
278 template <typename Character>
279 _Success_(return == 0)
280 static errno_t common_tmpnam(
281     _Out_writes_opt_z_(result_buffer_count) Character* const result_buffer,
282     _In_ size_t                                        const result_buffer_count,
283     _In_ buffer_id                                     const buffer_id,
284     _Outptr_result_z_                      Character** const result_pointer
285     ) throw()
286 {
287     errno_t return_value = 0;
288 
289     __acrt_lock(__acrt_tempnam_lock);
290     __try
291     {
292         errno_t const saved_errno = errno;
293 
294         return_value = common_tmpnam_nolock(result_buffer, result_buffer_count, buffer_id);
295         if (return_value != 0)
296         {
297             *result_pointer = result_buffer;
298             errno = return_value;
299             __leave;
300         }
301 
302         *result_pointer = result_buffer != nullptr
303             ? result_buffer
304             : *get_tmpnam_ptd_buffer(Character());
305 
306         errno = saved_errno;
307     }
308     __finally
309     {
310         __acrt_unlock(__acrt_tempnam_lock);
311     }
312     __endtry
313 
314     return return_value;
315 }
316 
317 
318 
319 _Success_(return == 0)
320 static errno_t __cdecl common_tmpfile_nolock(_Out_ FILE** const stream, int const sh_flag) throw()
321 {
322     char*  const global_buffer       = get_tmpfile_buffer_nolock<char>(buffer_id::tmpfile);
323     size_t const global_buffer_count = L_tmpnam;
324 
325     if (!global_buffer)
326     {
327         return ENOMEM;
328     }
329 
330     // Initialize the tmpfile buffer if it has not yet been initialized.
331     // Otherwise, generate the next file name:
332     if (*global_buffer == 0)
333     {
334         if (!initialize_tmpfile_buffer_nolock<char>(buffer_id::tmpfile))
335         {
336             return EINVAL; // REVIEW Which error?
337         }
338     }
339     else if (!generate_tmpfile_file_name(global_buffer, global_buffer_count))
340     {
341         return EINVAL; // REVIEW Which error?
342     }
343 
344     __crt_stdio_stream const local_stream = __acrt_stdio_allocate_stream();
345     if (!local_stream.valid())
346     {
347         return EMFILE;
348     }
349 
350     errno_t result = 0;
351 
352     __try
353     {
354         errno_t const saved_errno = errno;
355         errno = 0;
356 
357         // Create a temporary file.  Note that the loop below will only create a
358         // new file.  It will not open and truncate an existing behavior.  This
359         // behavior is permitted under ISO C.  The behavior implemented below is
360         // compatible with prior versions of the C Runtime and makes error
361         // checking easier.
362         int fh = 0;
363 
364         int const open_flag       = _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY | _O_TEMPORARY;
365         int const permission_mode = _S_IREAD | _S_IWRITE;
366 
367         while ((result = _sopen_s(&fh, global_buffer, open_flag, sh_flag, permission_mode)) == EEXIST)
368         {
369             if (!generate_tmpfile_file_name(global_buffer, global_buffer_count))
370             {
371                 break;
372             }
373         }
374 
375         if (errno == 0)
376         {
377             errno = saved_errno;
378         }
379 
380         // Ensure that the loop above did indeed create the file:
381         if (fh == -1)
382         {
383             __leave;
384         }
385 
386         // Initialize the stream:
387         local_stream->_tmpfname = _strdup_crt(global_buffer);
388         if (local_stream->_tmpfname == nullptr)
389         {
390             _close(fh);
391             result = ENOMEM;
392             __leave;
393         }
394 
395         local_stream->_cnt  = 0;
396         local_stream->_base = nullptr;
397         local_stream->_ptr  = nullptr;
398         local_stream.set_flags(_commode | _IOUPDATE);
399         local_stream->_file = fh;
400         *stream = local_stream.public_stream();
401         result = 0;
402     }
403     __finally
404     {
405         // If we didn't complete initialization of the stream successfully, we
406         // must free the allocated stream:
407         if (local_stream->_file == -1)
408             __acrt_stdio_free_stream(local_stream);
409 
410         local_stream.unlock();
411     }
412     __endtry
413 
414     return result;
415 }
416 
417 
418 
419 _Success_(return == 0)
420 static errno_t __cdecl common_tmpfile(_Out_ FILE** const stream, int const sh_flag) throw()
421 {
422     _VALIDATE_RETURN_ERRCODE(stream != nullptr, EINVAL);
423     *stream = nullptr;
424 
425     errno_t return_value = 0;
426 
427     __acrt_lock(__acrt_tempnam_lock);
428     __try
429     {
430         return_value = common_tmpfile_nolock(stream, sh_flag);
431         if (return_value != 0)
432         {
433             errno = return_value;
434         }
435     }
436     __finally
437     {
438         __acrt_unlock(__acrt_tempnam_lock);
439     }
440     __endtry
441 
442     return return_value;
443 }
444 
445 
446 
447 extern "C" errno_t __cdecl tmpnam_s(
448     char*  const result_buffer,
449     size_t const result_buffer_count
450     )
451 {
452     char* result = nullptr;
453     _VALIDATE_RETURN_ERRCODE(result_buffer != nullptr, EINVAL);
454     return common_tmpnam(result_buffer, result_buffer_count, buffer_id::tmpnam_s, &result);
455 }
456 
457 extern "C" errno_t __cdecl _wtmpnam_s(
458     wchar_t* const result_buffer,
459     size_t   const result_buffer_count
460     )
461 {
462     wchar_t* result = nullptr;
463     _VALIDATE_RETURN_ERRCODE(result_buffer != nullptr, EINVAL);
464     return common_tmpnam(result_buffer, result_buffer_count, buffer_id::tmpnam_s, &result);
465 }
466 
467 extern "C" char* __cdecl tmpnam(char* const result_buffer)
468 {
469     char* result = nullptr;
470     common_tmpnam(result_buffer, L_tmpnam, buffer_id::tmpnam, &result);
471     return result;
472 }
473 
474 extern "C" wchar_t* __cdecl _wtmpnam(wchar_t* const result_buffer)
475 {
476     wchar_t* result = nullptr;
477     common_tmpnam(result_buffer, L_tmpnam, buffer_id::tmpnam, &result);
478     return result;
479 }
480 
481 // Creates a temporary file with the file mode "w+b".  The file will be deleted
482 // automatically when it is closed or when the program terminates normally.
483 // On success, returns a stream; on failure, returns nullptr.
484 extern "C" FILE* __cdecl tmpfile()
485 {
486     FILE* stream = nullptr;
487     common_tmpfile(&stream, _SH_DENYNO);
488     return stream;
489 }
490 
491 // Creates a temporary file with the file mode "w+b".  The file will be deleted
492 // automatically when it is closed or when the program terminates normally.
493 // On success, returns zero and '*stream' refers to the newly opened stream.
494 // On failure, returns an error code and '*stream' is null.
495 extern "C" errno_t __cdecl tmpfile_s(FILE** const stream)
496 {
497     return common_tmpfile(stream, _SH_DENYRW);
498 }
499 
500 
501 
502 // Ensure that _rmtmp is called during static CRT termination:
503 #ifndef CRTDLL
504 
505     extern "C" unsigned __acrt_tmpfile_used;
506 
507     extern "C" void __acrt_force_use_of_tmpfile()
508     {
509         ++__acrt_tmpfile_used;
510     }
511 
512 #endif
513