Creating Temporary Files on Win32 in C – Part 2

Last post I talked about the existing options for creating a temporary file on Win32 and the pros and cons of each. This time I’m going to show you the solution that I normally use.

I stated that a temporary file solution would ideally be:

  • Cross-platform
  • Guarantee a unique name
  • Support SBCS/MBCS/Unicode
  • Allow you to have some control over where the file goes
  • Automatically delete itself when you close it or the process exits.

I pretty much gave up on the cross-platform goal after reading through the various existing options. The level of complexity that was going to be required to handle all the nuances just wasn’t worth it to me.

A Partial Solution

I settled on a partial solution. One function which returns a temporary filename, supports SBCS/MBCS/Unicode, checks whether the filename already exists, and allows you to specify either the basepath or the filename (or both). Automatically deleting the file when you close it or the process exits is achieved via CreateFile and FILE_FLAG_DELETE_ON_CLOSE.

Ignoring the cross-platform goal, there are still two problems with this implementation.

  1. By separating the filename creation from the file creation, we still suffer from Race condition 2 “The function only generates filenames which are unique when they are created. By the time the file is opened, it is possible that another process has already created a file with the same name.”
  2. CreateFile returns a HANDLE, not a FILE* so you have to the API calls WriteFile, CloseFile, etc. rather than the CRT calls to fwrite, fclose, etc. [1]
[1] It may be possible to convert a Win32 HANDLE to a FILE* based on the information in this article.

Getting the Temporary Filename

#include <Windows.h>
#include <errno.h>
#include <stdlib.h>
#include <tchar.h>

#define SUCCESS                               +0
#define FAILURE_NULL_ARGUMENT                 -1         
#define FAILURE_INSUFFICIENT_BUFFER           -2
#define FAILURE_API_CALL                      -3
#define FAILURE_INVALID_PATH                  -4
#define FAILURE_FILE_ALREADY_EXISTS           -5

Bool directory_exists( LPCTSTR p_path )
{
  DWORD attributes = GetFileAttributes( p_path );
  return ( attributes != INVALID_FILE_ATTRIBUTES &&
         (attributes & FILE_ATTRIBUTE_DIRECTORY) );
}

Bool file_exists( LPCTSTR p_path )
{
  DWORD attributes = GetFileAttributes( p_path );
  return ( attributes != INVALID_FILE_ATTRIBUTES &&
         !(attributes & FILE_ATTRIBUTE_DIRECTORY) );
}

int get_tmp_filename( LPCTSTR p_filename,
                        LPCTSTR p_basepath,
                        LPTSTR  p_tmp_filename,
                        DWORD   tmp_filename_size )
{
  TCHAR   tmp_path[MAX_PATH]  = { 0 };
  TCHAR   tmp_name[MAX_PATH]  = { 0 };

  // Parameter Validation
  if( p_tmp_filename == NULL )
  {
    return FAILURE_NULL_ARGUMENT;
  }

  // Get a basepath
  if( p_basepath != NULL )
  {
    _tcscpy_s( tmp_path, MAX_PATH, p_basepath );
  }
  else
  { // Use the CWD if a basepath wasn't supplied
    _tcscpy_s( tmp_path, MAX_PATH, TEXT(".\\") );
  }
  if( !directory_exists( tmp_path ) )
  {
    return FAILURE_INVALID_PATH;
  }

  // Form the full filename
  if( p_filename != NULL )
  {
    _tcscpy_s( tmp_name, MAX_PATH, tmp_path );
    _tcscat_s( tmp_name, MAX_PATH, TEXT("\\") ); 
    _tcscat_s( tmp_name, MAX_PATH, p_filename );
  }
  else
  { // Get a temporary filename if one wasn't supplied
    if( GetTempFileName( tmp_path, NULL, 0, tmp_name ) == 0 )
    {
      _ftprintf( stderr, TEXT("Error getting temporary filename in %s.\n"), tmp_path );
      return FAILURE_API_CALL;
    }
  }

  // Copy over the result
  switch( _tcscpy_s( p_tmp_filename, tmp_filename_size, tmp_name ) )
  {
  case 0:
    // Make sure that the file doesn't already exist before we suggest it as a tempfile.
    // They will still get the name in-case they intend to use it, but they have been warned.
    if( file_exists( tmp_name ) )
    {
      return FAILURE_FILE_ALREADY_EXISTS;
    }
    return SUCCESS;
    break;
  case ERANGE:
    return FAILURE_INSUFFICIENT_BUFFER;
    break;
  default:
    return FAILURE_API_CALL;
    break;
  }
}

Create a File that is Automatically Deleted when the Last Handle is Closed or the Program Terminates Normally

HANDLE h_file = CreateFile( tmpfilename, 
                          GENERIC_READ, 
                          FILE_SHARE_READ, 
                          NULL,
                          OPEN_EXISTING, 
                          FILE_FLAG_DELETE_ON_CLOSE,
                          NULL );

An Example of Putting It All Together

  HANDLE h_file;
  int    return_code;
  TCHAR  tmpfilename[_MAX_PATH] = { 0 };

  int return_code = get_tmp_filename( NULL, NULL, tmpfilename, _MAX_PATH );
  switch( return_code )
  {
  case FAILURE_FILE_ALREADY_EXISTS:
    break;
  case SUCCESS:
    break;
  default:
    return return_code;
  }

  // Extract the DLL to disk
  h_file = CreateFile( tmpfilename, 
                         GENERIC_READ, 
                         FILE_SHARE_READ, 
                         NULL,
                         OPEN_EXISTING, 
                         FILE_FLAG_DELETE_ON_CLOSE,
                         NULL );
  if( h_file == INVALID_HANDLE_VALUE )
  {
    _ftprintf( stderr, TEXT("Error creating temporary file %s.\n"), tmpfilename );
    return GetLastError();
  }

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.