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