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