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 */
47 static int
gettemp(char * path,int * doopen)48 gettemp(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
110 static int
mkstemp(char * path)111 mkstemp(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 */
125 static isc_result_t
file_stats(const char * file,struct stat * stats)126 file_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
145 int
isc_file_safemovefile(const char * oldname,const char * newname)146 isc_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
202 isc_result_t
isc_file_getmodtime(const char * file,isc_time_t * time)203 isc_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
225 isc_result_t
isc_file_settime(const char * file,isc_time_t * time)226 isc_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
257 isc_result_t
isc_file_mktemplate(const char * path,char * buf,size_t buflen)258 isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
259 return (isc_file_template(path, TEMPLATE, buf, buflen));
260 }
261
262 isc_result_t
isc_file_template(const char * path,const char * templet,char * buf,size_t buflen)263 isc_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
294 isc_result_t
isc_file_renameunique(const char * file,char * templet)295 isc_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
319 isc_result_t
isc_file_openuniqueprivate(char * templet,FILE ** fp)320 isc_file_openuniqueprivate(char *templet, FILE **fp) {
321 int mode = _S_IREAD | _S_IWRITE;
322 return (isc_file_openuniquemode(templet, mode, fp));
323 }
324
325 isc_result_t
isc_file_openunique(char * templet,FILE ** fp)326 isc_file_openunique(char *templet, FILE **fp) {
327 int mode = _S_IREAD | _S_IWRITE;
328 return (isc_file_openuniquemode(templet, mode, fp));
329 }
330
331 isc_result_t
isc_file_openuniquemode(char * templet,int mode,FILE ** fp)332 isc_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
365 isc_result_t
isc_file_remove(const char * filename)366 isc_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
378 isc_result_t
isc_file_rename(const char * oldname,const char * newname)379 isc_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
392 isc_boolean_t
isc_file_exists(const char * pathname)393 isc_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
401 isc_result_t
isc_file_isplainfile(const char * filename)402 isc_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
418 isc_boolean_t
isc_file_isabsolute(const char * filename)419 isc_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
434 isc_boolean_t
isc_file_iscurrentdir(const char * filename)435 isc_file_iscurrentdir(const char *filename) {
436 REQUIRE(filename != NULL);
437 return (ISC_TF(filename[0] == '.' && filename[1] == '\0'));
438 }
439
440 isc_boolean_t
isc_file_ischdiridempotent(const char * filename)441 isc_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
455 const char *
isc_file_basename(const char * filename)456 isc_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
467 isc_result_t
isc_file_progname(const char * filename,char * progname,size_t namelen)468 isc_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
508 isc_result_t
isc_file_absolutepath(const char * filename,char * path,size_t pathlen)509 isc_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
527 isc_result_t
isc_file_truncate(const char * filename,isc_offset_t size)528 isc_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
545 isc_result_t
isc_file_safecreate(const char * filename,FILE ** fp)546 isc_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
581 isc_result_t
isc_file_splitpath(isc_mem_t * mctx,char * path,char ** dirname,char ** basename)582 isc_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