An all-singing, all-dancing C function to create a temporary file
This page gives some C source code to create a temporary file avoiding the problems with existing standard ANSI and POSIX functions. We explain what we wanted, review the existing standard functions, and give our source code with examples. Please feel free to use it in your own applications. Version 2 is released under an MPL v2.0 license. If you think it is wrong or could be improved, please tell us at the contact link below.
What we wanted
We wanted a C function that would create a temporary file with the following properties:
- Will work on both Windows using MSVC and Linux using gcc.
- Returns an ANSI-compliant stdio.h file pointer to a binary file opened exclusively for reading and writing. This avoids any race conditions between choosing a name and creating the file.
- Gives us the actual name of the file, if we want it.
- Allows us to specify the directory in which the file is created, so we can use a secure directory of our choice. Otherwise, if not specified, the default temporary directory should be "sensible".
- Allows us to specify a prefix for the temporary file name.
- Appends a decent number of random characters to the filename (we are happy with ten).
- Allows us to specify whether or not the file is deleted after it is closed. By default the file should automatically be deleted, but we want the option to keep the file for debugging purposes.
Existing functions
The existing ANSI and POSIX temporary file functions all have a problem.
Function | Description | Problems | tmpfileplus equivalent |
---|---|---|---|
FILE *tmpfile(void) |
Returns a pointer to the stream of a temporary file with a guaranteed non-conflicting name that will be removed when the program closes it. | User has no control over where the file is created and does not know the filename. | FILE *fp; |
char *tmpnam(char *s) |
Creates a unique filename and returns a pointer to the file, which is created in the P_tmpdir directory, defined in stdio.h. If s is non-NULL, the file name is copied to the buffer it references. Otherwise, the file name is copied to a static buffer. | Race condition exists between selecting file name and creating it. The P_tmpdir directory may be insecure (in MSVC it is "\", which is useless!). Potential for overflow error if buffer s is too short. The number of unique filenames is limited to a small range. | char *s; |
char *tempnam(const char *dir, const char *pfx) |
Similar to tmpnam(), but the user can specify the directory which will contain the temporary file and the file name prefix. Returns a pointer to a file name in allocated memory that the user should free later. | Race condition exists between selecting file name and creating it. | char *dir="/mypath/mysub"; |
char *mktemp(char *template) |
Takes the given file name template and overwrites a portion of it to create a file name. template may contain a directory path and requires a number of 'X's at the end, e.g. /tmp/temp.XXXXXX. The 'X' will be replaced by some unique letter combination. Returns a pointer to a string in allocated memory. | Race condition exists between selecting file name and creating it. Number of 'X's in template may be limited to six. | fp=tmpfileplus(dir,pfx,&s,0); |
int mkstemp(char *template) |
Similar to mktemp but returns a file descriptor opened for reading and writing. | User does not know the filename. Non-ANSI file descriptor. Not available in MSVC. | fp=tmpfileplus(dir,pfx,NULL,0); |
Our solution
NAME tmpfileplus - create a unique temporary file SYNOPSIS FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep) DESCRIPTION The tmpfileplus() function opens a unique temporary file in binary read/write (w+b) mode. The file is opened with the O_EXCL flag, guaranteeing that the caller is the only user. The filename will consist of the string given by `prefix` followed by 10 random characters. If `prefix` is NULL, then the string "tmp." will be used instead. The file will be created in an appropriate directory chosen by the first successful attempt in the following sequence: a) The directory given by the `dir` argument (so the caller can specify a secure directory to take precedence). b) The directory name in the environment variables: (i) "TMP" [Windows only] (ii) "TEMP" [Windows only] (iii) "TMPDIR" [Unix only] c) `P_tmpdir` as defined in <stdio.h> [Unix only] (in Windows, this is usually "\", which is no good). d) The current working directory. If a file cannot be created in any of the above directories, then the function fails and NULL is returned. If the argument `pathname` is not a null pointer, then it will point to the full pathname of the file. The pathname is allocated using `malloc` and therefore should be freed by `free`. If `keep` is nonzero and `pathname` is not a null pointer, then the file will be kept after it is closed. Otherwise the file will be automatically deleted when it is closed or the program terminates. RETURN VALUE The tmpfileplus() function returns a pointer to the open file stream, or NULL if a unique file cannot be opened. ERRORS ENOMEM Not enough memory to allocate filename.
ADDED IN v2.0:
NAME tmpfileplus_f - create a unique temporary file with filename stored in a fixed-length buffer SYNOPSIS FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep); DESCRIPTION Same as tmpfileplus() except receives filename in a fixed-length buffer. No allocated memory to free. ERRORS E2BIG Resulting filename is too big for the buffer `pathnamebuf`.
Source code
- Source: tmpfileplus.c
- Include file: tmpfileplus.h
- Test code: t_tmpfileplus.c
Zipped source files: tmpfileplus-2.0.1.zip (6.2 kB) [sha1=9e7c24ba1c194c0abbd6acb134420484fe441aec]
. Code last updated 2020-04-06.
Examples
FILE *fp; char *pathname; int keep; /* Test with no pathname, like tmpfile() */ fp = tmpfileplus(NULL, NULL, NULL, 0); /* ... */ fclose(fp); /* Test with a pathname */ fp = tmpfileplus(NULL, NULL, &pathname, 0); /* ... */ fclose(fp); if (pathname) free(pathname); /* Test with a given prefix */ fp = tmpfileplus(NULL, "THIS-", &pathname, 0); /* ... */ fclose(fp); if (pathname) free(pathname); /* Test with a specified directory -- directory "C:\Test" must exist -- otherwise default is used */ fp = tmpfileplus("C:\Test", "THAT~", &pathname, 0); /* ... */ fclose(fp); if (pathname) free(pathname); /* Test with a specified directory but keep the file */ keep = 1; fp = tmpfileplus("C:\Test", NULL, &pathname, keep); /* ... */ fclose(fp); if (pathname) free(pathname);
Sample output of test code: on Windows and on Linux.
Programmer's notes
- Random characters and security
- We reckon that using the stdlib rand() is sufficient here provided you keep control of the seed.
And we reckon that ten random characters is sufficient, and that's more to prevent name collisions.
An adversary who wants to access your temp file can simply watch the temp directory for a new file that appears at the
same time as you make the call, and can act on that.
What's more important is that file is locked for exclusive use and there's no opportunity for a race condition to occur between
checking the name is free and creating the file.
Anyway, feel free to change the code for the random characters and use, say, SHA-512 to digest input from your Geiger counter and lump of Uranium-235 to get truly random input. You are probably better off making sure the destination directory is secured properly.
- Deleting the file after use
- On Linux you can simply unlink the file immediately after creating it and it will be deleted once closed. Windows will not let you do that: you cannot unlink an open file. So we use the _O_TEMPORARY flag when opening the file, instead.
Contact us
To comment on this page or to contact us, please send us a message.
This page first published 16 May 2013. Last updated 3 April 2022.