/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 | | |