file.c revision 290001
1/*
2 * Copyright (C) 2004, 2007, 2009, 2011, 2012  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000-2002  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id$ */
19
20#include <config.h>
21
22#undef rename
23#include <errno.h>
24#include <limits.h>
25#include <stdlib.h>
26#include <io.h>
27#include <process.h>
28
29#include <sys/stat.h>
30#include <fcntl.h>
31#include <sys/utime.h>
32
33#include <isc/file.h>
34#include <isc/mem.h>
35#include <isc/result.h>
36#include <isc/time.h>
37#include <isc/util.h>
38#include <isc/stat.h>
39#include <isc/string.h>
40
41#include "errno2result.h"
42
43/*
44 * Emulate UNIX mkstemp, which returns an open FD to the new file
45 *
46 */
47static int
48gettemp(char *path, int *doopen) {
49	char *start, *trv;
50	struct stat sbuf;
51	int pid;
52
53	trv = strrchr(path, 'X');
54	trv++;
55	pid = getpid();
56	/* extra X's get set to 0's */
57	while (*--trv == 'X') {
58		*trv = (pid % 10) + '0';
59		pid /= 10;
60	}
61	/*
62	 * check the target directory; if you have six X's and it
63	 * doesn't exist this runs for a *very* long time.
64	 */
65	for (start = trv + 1;; --trv) {
66		if (trv <= path)
67			break;
68		if (*trv == '\\') {
69			*trv = '\0';
70			if (stat(path, &sbuf))
71				return (0);
72			if (!S_ISDIR(sbuf.st_mode)) {
73				errno = ENOTDIR;
74				return (0);
75			}
76			*trv = '\\';
77			break;
78		}
79	}
80
81	for (;;) {
82		if (doopen) {
83			if ((*doopen =
84			    open(path, O_CREAT|O_EXCL|O_RDWR,
85				 _S_IREAD | _S_IWRITE)) >= 0)
86				return (1);
87			if (errno != EEXIST)
88				return (0);
89		} else if (stat(path, &sbuf))
90			return (errno == ENOENT ? 1 : 0);
91
92		/* tricky little algorithm for backward compatibility */
93		for (trv = start;;) {
94			if (!*trv)
95				return (0);
96			if (*trv == 'z')
97				*trv++ = 'a';
98			else {
99				if (isdigit(*trv))
100					*trv = 'a';
101				else
102					++*trv;
103				break;
104			}
105		}
106	}
107	/*NOTREACHED*/
108}
109
110static int
111mkstemp(char *path) {
112	int fd;
113
114	return (gettemp(path, &fd) ? fd : -1);
115}
116
117/*
118 * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
119 * it might be good to provide a mechanism that allows for the results
120 * of a previous stat() to be used again without having to do another stat,
121 * such as perl's mechanism of using "_" in place of a file name to indicate
122 * that the results of the last stat should be used.  But then you get into
123 * annoying MP issues.   BTW, Win32 has stat().
124 */
125static isc_result_t
126file_stats(const char *file, struct stat *stats) {
127	isc_result_t result = ISC_R_SUCCESS;
128
129	REQUIRE(file != NULL);
130	REQUIRE(stats != NULL);
131
132	if (stat(file, stats) != 0)
133		result = isc__errno2result(errno);
134
135	return (result);
136}
137
138/*
139 * isc_file_safemovefile is needed to be defined here to ensure that
140 * any file with the new name is renamed to a backup name and then the
141 * rename is done. If all goes well then the backup can be deleted,
142 * otherwise it gets renamed back.
143 */
144
145int
146isc_file_safemovefile(const char *oldname, const char *newname) {
147	BOOL filestatus;
148	char buf[512];
149	struct stat sbuf;
150	BOOL exists = FALSE;
151	int tmpfd;
152
153	/*
154	 * Make sure we have something to do
155	 */
156	if (stat(oldname, &sbuf) != 0) {
157		errno = ENOENT;
158		return (-1);
159	}
160
161	/*
162	 * Rename to a backup the new file if it still exists
163	 */
164	if (stat(newname, &sbuf) == 0) {
165		exists = TRUE;
166		strcpy(buf, newname);
167		strcat(buf, ".XXXXX");
168		tmpfd = mkstemp(buf);
169		if (tmpfd > 0)
170			_close(tmpfd);
171		DeleteFile(buf);
172		_chmod(newname, _S_IREAD | _S_IWRITE);
173
174		filestatus = MoveFile(newname, buf);
175	}
176	/* Now rename the file to the new name
177	 */
178	_chmod(oldname, _S_IREAD | _S_IWRITE);
179
180	filestatus = MoveFile(oldname, newname);
181	if (filestatus == 0) {
182		/*
183		 * Try to rename the backup back to the original name
184		 * if the backup got created
185		 */
186		if (exists == TRUE) {
187			filestatus = MoveFile(buf, newname);
188			if (filestatus == 0)
189				errno = EACCES;
190		}
191		return (-1);
192	}
193
194	/*
195	 * Delete the backup file if it got created
196	 */
197	if (exists == TRUE)
198		filestatus = DeleteFile(buf);
199	return (0);
200}
201
202isc_result_t
203isc_file_getmodtime(const char *file, isc_time_t *time) {
204	int fh;
205
206	REQUIRE(file != NULL);
207	REQUIRE(time != NULL);
208
209	if ((fh = open(file, _O_RDONLY | _O_BINARY)) < 0)
210		return (isc__errno2result(errno));
211
212	if (!GetFileTime((HANDLE) _get_osfhandle(fh),
213			 NULL,
214			 NULL,
215			 &time->absolute))
216	{
217		close(fh);
218		errno = EINVAL;
219		return (isc__errno2result(errno));
220	}
221	close(fh);
222	return (ISC_R_SUCCESS);
223}
224
225isc_result_t
226isc_file_settime(const char *file, isc_time_t *time) {
227	int fh;
228
229	REQUIRE(file != NULL && time != NULL);
230
231	if ((fh = open(file, _O_RDWR | _O_BINARY)) < 0)
232		return (isc__errno2result(errno));
233
234	/*
235	 * Set the date via the filedate system call and return.  Failing
236	 * this call implies the new file times are not supported by the
237	 * underlying file system.
238	 */
239	if (!SetFileTime((HANDLE) _get_osfhandle(fh),
240			 NULL,
241			 &time->absolute,
242			 &time->absolute))
243	{
244		close(fh);
245		errno = EINVAL;
246		return (isc__errno2result(errno));
247	}
248
249	close(fh);
250	return (ISC_R_SUCCESS);
251
252}
253
254#undef TEMPLATE
255#define TEMPLATE "XXXXXXXXXX.tmp" /* 14 characters. */
256
257isc_result_t
258isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
259	return (isc_file_template(path, TEMPLATE, buf, buflen));
260}
261
262isc_result_t
263isc_file_template(const char *path, const char *templet, char *buf,
264			size_t buflen) {
265	char *s;
266
267	REQUIRE(path != NULL);
268	REQUIRE(templet != NULL);
269	REQUIRE(buf != NULL);
270
271	s = strrchr(templet, '\\');
272	if (s != NULL)
273		templet = s + 1;
274
275	s = strrchr(path, '\\');
276
277	if (s != NULL) {
278		if ((s - path + 1 + strlen(templet) + 1) > buflen)
279			return (ISC_R_NOSPACE);
280
281		strncpy(buf, path, s - path + 1);
282		buf[s - path + 1] = '\0';
283		strcat(buf, templet);
284	} else {
285		if ((strlen(templet) + 1) > buflen)
286			return (ISC_R_NOSPACE);
287
288		strcpy(buf, templet);
289	}
290
291	return (ISC_R_SUCCESS);
292}
293
294isc_result_t
295isc_file_renameunique(const char *file, char *templet) {
296	int fd = -1;
297	int res = 0;
298	isc_result_t result = ISC_R_SUCCESS;
299
300	REQUIRE(file != NULL);
301	REQUIRE(templet != NULL);
302
303	fd = mkstemp(templet);
304	if (fd == -1)
305		result = isc__errno2result(errno);
306	else
307		close(fd);
308
309	if (result == ISC_R_SUCCESS) {
310		res = isc_file_safemovefile(file, templet);
311		if (res != 0) {
312			result = isc__errno2result(errno);
313			(void)unlink(templet);
314		}
315	}
316	return (result);
317}
318
319isc_result_t
320isc_file_openuniqueprivate(char *templet, FILE **fp) {
321	int mode = _S_IREAD | _S_IWRITE;
322	return (isc_file_openuniquemode(templet, mode, fp));
323}
324
325isc_result_t
326isc_file_openunique(char *templet, FILE **fp) {
327	int mode = _S_IREAD | _S_IWRITE;
328	return (isc_file_openuniquemode(templet, mode, fp));
329}
330
331isc_result_t
332isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
333	int fd;
334	FILE *f;
335	isc_result_t result = ISC_R_SUCCESS;
336
337	REQUIRE(templet != NULL);
338	REQUIRE(fp != NULL && *fp == NULL);
339
340	/*
341	 * Win32 does not have mkstemp. Using emulation above.
342	 */
343	fd = mkstemp(templet);
344
345	if (fd == -1)
346		result = isc__errno2result(errno);
347	if (result == ISC_R_SUCCESS) {
348#if 1
349		UNUSED(mode);
350#else
351		(void)fchmod(fd, mode);
352#endif
353		f = fdopen(fd, "w+");
354		if (f == NULL) {
355			result = isc__errno2result(errno);
356			(void)remove(templet);
357			(void)close(fd);
358		} else
359			*fp = f;
360	}
361
362	return (result);
363}
364
365isc_result_t
366isc_file_remove(const char *filename) {
367	int r;
368
369	REQUIRE(filename != NULL);
370
371	r = unlink(filename);
372	if (r == 0)
373		return (ISC_R_SUCCESS);
374	else
375		return (isc__errno2result(errno));
376}
377
378isc_result_t
379isc_file_rename(const char *oldname, const char *newname) {
380	int r;
381
382	REQUIRE(oldname != NULL);
383	REQUIRE(newname != NULL);
384
385	r = isc_file_safemovefile(oldname, newname);
386	if (r == 0)
387		return (ISC_R_SUCCESS);
388	else
389		return (isc__errno2result(errno));
390}
391
392isc_boolean_t
393isc_file_exists(const char *pathname) {
394	struct stat stats;
395
396	REQUIRE(pathname != NULL);
397
398	return (ISC_TF(file_stats(pathname, &stats) == ISC_R_SUCCESS));
399}
400
401isc_result_t
402isc_file_isplainfile(const char *filename) {
403	/*
404	 * This function returns success if filename is a plain file.
405	 */
406	struct stat filestat;
407	memset(&filestat,0,sizeof(struct stat));
408
409	if ((stat(filename, &filestat)) == -1)
410		return(isc__errno2result(errno));
411
412	if(! S_ISREG(filestat.st_mode))
413		return(ISC_R_INVALIDFILE);
414
415	return(ISC_R_SUCCESS);
416}
417
418isc_boolean_t
419isc_file_isabsolute(const char *filename) {
420	REQUIRE(filename != NULL);
421	/*
422	 * Look for c:\path\... style, c:/path/... or \\computer\shar\path...
423	 * the UNC style file specs
424	 */
425	if ((filename[0] == '\\') && (filename[1] == '\\'))
426		return (ISC_TRUE);
427	if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '\\')
428		return (ISC_TRUE);
429	if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/')
430		return (ISC_TRUE);
431	return (ISC_FALSE);
432}
433
434isc_boolean_t
435isc_file_iscurrentdir(const char *filename) {
436	REQUIRE(filename != NULL);
437	return (ISC_TF(filename[0] == '.' && filename[1] == '\0'));
438}
439
440isc_boolean_t
441isc_file_ischdiridempotent(const char *filename) {
442	REQUIRE(filename != NULL);
443
444	if (isc_file_isabsolute(filename))
445		return (ISC_TRUE);
446	if (filename[0] == '\\')
447		return (ISC_TRUE);
448	if (filename[0] == '/')
449		return (ISC_TRUE);
450	if (isc_file_iscurrentdir(filename))
451		return (ISC_TRUE);
452	return (ISC_FALSE);
453}
454
455const char *
456isc_file_basename(const char *filename) {
457	char *s;
458
459	REQUIRE(filename != NULL);
460
461	s = strrchr(filename, '\\');
462	if (s == NULL)
463		return (filename);
464	return (s + 1);
465}
466
467isc_result_t
468isc_file_progname(const char *filename, char *progname, size_t namelen) {
469	const char *s;
470	char *p;
471	size_t len;
472
473	REQUIRE(filename != NULL);
474	REQUIRE(progname != NULL);
475
476	/*
477	 * Strip the path from the name
478	 */
479	s = isc_file_basename(filename);
480	if (s == NULL) {
481		return (ISC_R_NOSPACE);
482	}
483
484	/*
485	 * Strip any and all suffixes
486	 */
487	p = strchr(s, '.');
488	if (p == NULL) {
489		if (namelen <= strlen(s))
490			return (ISC_R_NOSPACE);
491
492		strcpy(progname, s);
493		return (ISC_R_SUCCESS);
494	}
495
496	/*
497	 * Copy the result to the buffer
498	 */
499	len = p - s;
500	if (len >= namelen)
501		return (ISC_R_NOSPACE);
502
503	strncpy(progname, s, len);
504	progname[len] = '\0';
505	return (ISC_R_SUCCESS);
506}
507
508isc_result_t
509isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
510	char *ptrname;
511	DWORD retval;
512
513	REQUIRE(filename != NULL);
514	REQUIRE(path != NULL);
515
516	retval = GetFullPathName(filename, pathlen, path, &ptrname);
517
518	/* Something went wrong in getting the path */
519	if (retval == 0)
520		return (ISC_R_NOTFOUND);
521	/* Caller needs to provide a larger buffer to contain the string */
522	if (retval >= pathlen)
523		return (ISC_R_NOSPACE);
524	return (ISC_R_SUCCESS);
525}
526
527isc_result_t
528isc_file_truncate(const char *filename, isc_offset_t size) {
529	int fh;
530
531	REQUIRE(filename != NULL && size >= 0);
532
533	if ((fh = open(filename, _O_RDWR | _O_BINARY)) < 0)
534		return (isc__errno2result(errno));
535
536	if(_chsize(fh, size) != 0) {
537		close(fh);
538		return (isc__errno2result(errno));
539	}
540	close(fh);
541
542	return (ISC_R_SUCCESS);
543}
544
545isc_result_t
546isc_file_safecreate(const char *filename, FILE **fp) {
547	isc_result_t result;
548	int flags;
549	struct stat sb;
550	FILE *f;
551	int fd;
552
553	REQUIRE(filename != NULL);
554	REQUIRE(fp != NULL && *fp == NULL);
555
556	result = file_stats(filename, &sb);
557	if (result == ISC_R_SUCCESS) {
558		if ((sb.st_mode & S_IFREG) == 0)
559			return (ISC_R_INVALIDFILE);
560		flags = O_WRONLY | O_TRUNC;
561	} else if (result == ISC_R_FILENOTFOUND) {
562		flags = O_WRONLY | O_CREAT | O_EXCL;
563	} else
564		return (result);
565
566	fd = open(filename, flags, S_IRUSR | S_IWUSR);
567	if (fd == -1)
568		return (isc__errno2result(errno));
569
570	f = fdopen(fd, "w");
571	if (f == NULL) {
572		result = isc__errno2result(errno);
573		close(fd);
574		return (result);
575	}
576
577	*fp = f;
578	return (ISC_R_SUCCESS);
579}
580
581isc_result_t
582isc_file_splitpath(isc_mem_t *mctx, char *path, char **dirname, char **basename)
583{
584	char *dir, *file, *slash;
585	char *backslash;
586
587	slash = strrchr(path, '/');
588
589	backslash = strrchr(path, '\\');
590	if ((slash != NULL && backslash != NULL && backslash > slash) ||
591	    (slash == NULL && backslash != NULL))
592		slash = backslash;
593
594	if (slash == path) {
595		file = ++slash;
596		dir = isc_mem_strdup(mctx, "/");
597	} else if (slash != NULL) {
598		file = ++slash;
599		dir = isc_mem_allocate(mctx, slash - path);
600		if (dir != NULL)
601			strlcpy(dir, path, slash - path);
602	} else {
603		file = path;
604		dir = isc_mem_strdup(mctx, ".");
605	}
606
607	if (dir == NULL)
608		return (ISC_R_NOMEMORY);
609
610	if (*file == '\0') {
611		isc_mem_free(mctx, dir);
612		return (ISC_R_INVALIDFILE);
613	}
614
615	*dirname = dir;
616	*basename = file;
617
618	return (ISC_R_SUCCESS);
619}
620