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