tmpfileplus.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /* $Id: tmpfileplus.c $ */
  2. /*
  3. * $Date: 2016-06-01 03:31Z $
  4. * $Revision: 2.0.0 $
  5. * $Author: dai $
  6. */
  7. /*
  8. * This Source Code Form is subject to the terms of the Mozilla Public
  9. * License, v. 2.0. If a copy of the MPL was not distributed with this
  10. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  11. *
  12. * Copyright (c) 2012-16 David Ireland, DI Management Services Pty Ltd
  13. * <http://www.di-mgt.com.au/contact/>.
  14. */
  15. /*
  16. * NAME
  17. * tmpfileplus - create a unique temporary file
  18. *
  19. * SYNOPSIS
  20. * FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep)
  21. *
  22. * DESCRIPTION
  23. * The tmpfileplus() function opens a unique temporary file in binary
  24. * read/write (w+b) mode. The file is opened with the O_EXCL flag,
  25. * guaranteeing that the caller is the only user. The filename will consist
  26. * of the string given by `prefix` followed by 10 random characters. If
  27. * `prefix` is NULL, then the string "tmp." will be used instead. The file
  28. * will be created in an appropriate directory chosen by the first
  29. * successful attempt in the following sequence:
  30. *
  31. * a) The directory given by the `dir` argument (so the caller can specify
  32. * a secure directory to take precedence).
  33. *
  34. * b) The directory name in the environment variables:
  35. *
  36. * (i) "TMP" [Windows only]
  37. * (ii) "TEMP" [Windows only]
  38. * (iii) "TMPDIR" [Unix only]
  39. *
  40. * c) `P_tmpdir` as defined in <stdio.h> [Unix only] (in Windows, this is
  41. * usually "\", which is no good).
  42. *
  43. * d) The current working directory.
  44. *
  45. * If a file cannot be created in any of the above directories, then the
  46. * function fails and NULL is returned.
  47. *
  48. * If the argument `pathname` is not a null pointer, then it will point to
  49. * the full pathname of the file. The pathname is allocated using `malloc`
  50. * and therefore should be freed by `free`.
  51. *
  52. * If `keep` is nonzero and `pathname` is not a null pointer, then the file
  53. * will be kept after it is closed. Otherwise the file will be
  54. * automatically deleted when it is closed or the program terminates.
  55. *
  56. *
  57. * RETURN VALUE
  58. * The tmpfileplus() function returns a pointer to the open file stream,
  59. * or NULL if a unique file cannot be opened.
  60. *
  61. *
  62. * ERRORS
  63. * ENOMEM Not enough memory to allocate filename.
  64. *
  65. */
  66. /* ADDED IN v2.0 */
  67. /*
  68. * NAME
  69. * tmpfileplus_f - create a unique temporary file with filename stored in a fixed-length buffer
  70. *
  71. * SYNOPSIS
  72. * FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep);
  73. *
  74. * DESCRIPTION
  75. * Same as tmpfileplus() except receives filename in a fixed-length buffer. No allocated memory to free.
  76. * ERRORS
  77. * E2BIG Resulting filename is too big for the buffer `pathnamebuf`.
  78. */
  79. #include "tmpfileplus.h"
  80. #include <stdio.h>
  81. #include <stdlib.h>
  82. #include <string.h>
  83. #include <time.h>
  84. #include <errno.h>
  85. /* Non-ANSI include files that seem to work in both MSVC and Linux */
  86. #include <sys/types.h>
  87. #include <sys/stat.h>
  88. #include <fcntl.h>
  89. #ifdef _WIN32
  90. #include <io.h>
  91. #else
  92. #include <unistd.h>
  93. #endif
  94. #ifdef _WIN32
  95. /* MSVC nags to enforce ISO C++ conformant function names with leading "_",
  96. * so we define our own function names to avoid whingeing compilers...
  97. */
  98. #define OPEN_ _open
  99. #define FDOPEN_ _fdopen
  100. #else
  101. #define OPEN_ open
  102. #define FDOPEN_ fdopen
  103. #endif
  104. /* DEBUGGING STUFF */
  105. #if defined(_DEBUG) && defined(SHOW_DPRINTF)
  106. #define DPRINTF1(s, a1) printf(s, a1)
  107. #else
  108. #define DPRINTF1(s, a1)
  109. #endif
  110. #ifdef _WIN32
  111. #define FILE_SEPARATOR "\\"
  112. #else
  113. #define FILE_SEPARATOR "/"
  114. #endif
  115. #define RANDCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  116. #define NRANDCHARS (sizeof(RANDCHARS) - 1)
  117. /** Replace each byte in string s with a random character from TEMPCHARS */
  118. static char *set_randpart(char *s)
  119. {
  120. size_t i;
  121. unsigned int r;
  122. static unsigned int seed; /* NB static */
  123. if (seed == 0)
  124. { /* First time set our seed using current time and clock */
  125. seed = ((unsigned)time(NULL)<<8) ^ (unsigned)clock();
  126. }
  127. srand(seed++);
  128. for (i = 0; i < strlen(s); i++)
  129. {
  130. r = rand() % NRANDCHARS;
  131. s[i] = (RANDCHARS)[r];
  132. }
  133. return s;
  134. }
  135. /** Return 1 if path is a valid directory otherwise 0 */
  136. static int is_valid_dir(const char *path)
  137. {
  138. struct stat st;
  139. if ((stat(path, &st) == 0) && (st.st_mode & S_IFDIR))
  140. return 1;
  141. return 0;
  142. }
  143. /** Call getenv and save a copy in buf */
  144. static char *getenv_save(const char *varname, char *buf, size_t bufsize)
  145. {
  146. char *ptr = getenv(varname);
  147. buf[0] = '\0';
  148. if (ptr)
  149. {
  150. strncpy(buf, ptr, bufsize);
  151. buf[bufsize-1] = '\0';
  152. return buf;
  153. }
  154. return NULL;
  155. }
  156. /**
  157. * Try and create a randomly-named file in directory `tmpdir`.
  158. * If successful, allocate memory and set `tmpname_ptr` to full filepath, and return file pointer;
  159. * otherwise return NULL.
  160. * If `keep` is zero then create the file as temporary and it should not exist once closed.
  161. */
  162. static FILE *mktempfile_internal(const char *tmpdir, const char *pfx, char **tmpname_ptr, int keep)
  163. /* PRE:
  164. * pfx is not NULL and points to a valid null-terminated string
  165. * tmpname_ptr is not NULL.
  166. */
  167. {
  168. FILE *fp;
  169. int fd;
  170. char randpart[] = "1234567890";
  171. size_t lentempname;
  172. int i;
  173. char *tmpname = NULL;
  174. int oflag, pmode;
  175. /* In Windows, we use the _O_TEMPORARY flag with `open` to ensure the file is deleted when closed.
  176. * In Unix, we use the unlink function after opening the file. (This does not work in Windows,
  177. * which does not allow an open file to be unlinked.)
  178. */
  179. #ifdef _WIN32
  180. /* MSVC flags */
  181. oflag = _O_BINARY|_O_CREAT|_O_EXCL|_O_RDWR;
  182. if (!keep)
  183. oflag |= _O_TEMPORARY;
  184. pmode = _S_IREAD | _S_IWRITE;
  185. #else
  186. /* Standard POSIX flags */
  187. oflag = O_CREAT|O_EXCL|O_RDWR;
  188. pmode = S_IRUSR|S_IWUSR;
  189. #endif
  190. if (!tmpdir || !is_valid_dir(tmpdir)) {
  191. errno = ENOENT;
  192. return NULL;
  193. }
  194. lentempname = strlen(tmpdir) + strlen(FILE_SEPARATOR) + strlen(pfx) + strlen(randpart);
  195. DPRINTF1("lentempname=%d\n", lentempname);
  196. tmpname = malloc(lentempname + 1);
  197. if (!tmpname)
  198. {
  199. errno = ENOMEM;
  200. return NULL;
  201. }
  202. /* If we don't manage to create a file after 10 goes, there is something wrong... */
  203. for (i = 0; i < 10; i++)
  204. {
  205. sprintf(tmpname, "%s%s%s%s", tmpdir, FILE_SEPARATOR, pfx, set_randpart(randpart));
  206. DPRINTF1("[%s]\n", tmpname);
  207. fd = OPEN_(tmpname, oflag, pmode);
  208. if (fd != -1) break;
  209. }
  210. DPRINTF1("strlen(tmpname)=%d\n", strlen(tmpname));
  211. if (fd != -1)
  212. { /* Success, so return user a proper ANSI C file pointer */
  213. fp = FDOPEN_(fd, "w+b");
  214. errno = 0;
  215. #ifndef _WIN32
  216. /* [Unix only] And make sure the file will be deleted once closed */
  217. if (!keep) unlink(tmpname);
  218. #endif
  219. }
  220. else
  221. { /* We failed */
  222. fp = NULL;
  223. }
  224. if (!fp)
  225. {
  226. free(tmpname);
  227. tmpname = NULL;
  228. }
  229. *tmpname_ptr = tmpname;
  230. return fp;
  231. }
  232. /**********************/
  233. /* EXPORTED FUNCTIONS */
  234. /**********************/
  235. FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep)
  236. {
  237. FILE *fp = NULL;
  238. char *tmpname = NULL;
  239. char *tmpdir = NULL;
  240. const char *pfx = (prefix ? prefix : "tmp.");
  241. char *tempdirs[12] = { 0 };
  242. #ifdef _WIN32
  243. char env1[FILENAME_MAX+1] = { 0 };
  244. char env2[FILENAME_MAX+1] = { 0 };
  245. #else
  246. char env3[FILENAME_MAX+1] = { 0 };
  247. #endif
  248. int ntempdirs = 0;
  249. int i;
  250. /* Set up a list of temp directories we will try in order */
  251. i = 0;
  252. tempdirs[i++] = (char *)dir;
  253. #ifdef _WIN32
  254. tempdirs[i++] = getenv_save("TMP", env1, sizeof(env1));
  255. tempdirs[i++] = getenv_save("TEMP", env2, sizeof(env2));
  256. #else
  257. tempdirs[i++] = getenv_save("TMPDIR", env3, sizeof(env3));
  258. tempdirs[i++] = P_tmpdir;
  259. #endif
  260. tempdirs[i++] = ".";
  261. ntempdirs = i;
  262. errno = 0;
  263. /* Work through list we set up before, and break once we are successful */
  264. for (i = 0; i < ntempdirs; i++)
  265. {
  266. tmpdir = tempdirs[i];
  267. DPRINTF1("Trying tmpdir=[%s]\n", tmpdir);
  268. fp = mktempfile_internal(tmpdir, pfx, &tmpname, keep);
  269. if (fp) break;
  270. }
  271. /* If we succeeded and the user passed a pointer, set it to the alloc'd pathname: the user must free this */
  272. if (fp && pathname)
  273. *pathname = tmpname;
  274. else /* Otherwise, free the alloc'd memory */
  275. free(tmpname);
  276. return fp;
  277. }
  278. /* Same as tmpfileplus() but with fixed length buffer for output filename and no memory allocation */
  279. FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep)
  280. {
  281. char *tmpbuf = NULL;
  282. FILE *fp;
  283. /* If no buffer provided, do the normal way */
  284. if (!pathnamebuf || (int)pathsize <= 0) {
  285. return tmpfileplus(dir, prefix, NULL, keep);
  286. }
  287. /* Call with a temporary buffer */
  288. fp = tmpfileplus(dir, prefix, &tmpbuf, keep);
  289. if (fp && strlen(tmpbuf) > pathsize - 1) {
  290. /* Succeeded but not enough room in output buffer, so clean up and return an error */
  291. pathnamebuf[0] = 0;
  292. fclose(fp);
  293. if (keep) remove(tmpbuf);
  294. free(tmpbuf);
  295. errno = E2BIG;
  296. return NULL;
  297. }
  298. /* Copy name into buffer */
  299. strcpy(pathnamebuf, tmpbuf);
  300. free(tmpbuf);
  301. return fp;
  302. }