Coverage Report

Created: 2025-08-28 06:28

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