1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * prof_file.c ---- routines that manipulate an individual profile file. 4 */ 5 6 #include "prof_int.h" 7 8 #include <stdio.h> 9 #ifdef HAVE_STDLIB_H 10 #include <stdlib.h> 11 #endif 12 #ifdef HAVE_UNISTD_H 13 #include <unistd.h> 14 #endif 15 #include <string.h> 16 #include <stddef.h> 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <errno.h> 21 22 #ifdef HAVE_PWD_H 23 #include <pwd.h> 24 #endif 25 26 #if defined(_WIN32) 27 #include <io.h> 28 #define HAVE_STAT 29 #define stat _stat 30 #ifndef S_ISDIR 31 #define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) 32 #endif 33 #endif 34 35 #include "k5-platform.h" 36 37 struct global_shared_profile_data { 38 /* This is the head of the global list of shared trees */ 39 prf_data_t trees; 40 /* Lock for above list. */ 41 k5_mutex_t mutex; 42 }; 43 #define g_shared_trees (krb5int_profile_shared_data.trees) 44 #define g_shared_trees_mutex (krb5int_profile_shared_data.mutex) 45 46 static struct global_shared_profile_data krb5int_profile_shared_data = { 47 0, 48 K5_MUTEX_PARTIAL_INITIALIZER 49 }; 50 51 MAKE_INIT_FUNCTION(profile_library_initializer); 52 MAKE_FINI_FUNCTION(profile_library_finalizer); 53 54 int profile_library_initializer(void) 55 { 56 #ifdef SHOW_INITFINI_FUNCS 57 printf("profile_library_initializer\n"); 58 #endif 59 add_error_table(&et_prof_error_table); 60 61 return k5_mutex_finish_init(&g_shared_trees_mutex); 62 } 63 void profile_library_finalizer(void) 64 { 65 if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING()) { 66 #ifdef SHOW_INITFINI_FUNCS 67 printf("profile_library_finalizer: skipping\n"); 68 #endif 69 return; 70 } 71 #ifdef SHOW_INITFINI_FUNCS 72 printf("profile_library_finalizer\n"); 73 #endif 74 k5_mutex_destroy(&g_shared_trees_mutex); 75 76 remove_error_table(&et_prof_error_table); 77 } 78 79 static void profile_free_file_data(prf_data_t); 80 81 static int rw_access(const_profile_filespec_t filespec) 82 { 83 #ifdef HAVE_ACCESS 84 if (access(filespec, W_OK) == 0) 85 return 1; 86 else 87 return 0; 88 #else 89 /* 90 * We're on a substandard OS that doesn't support access. So 91 * we kludge a test using stdio routines, and hope fopen 92 * checks the r/w permissions. 93 */ 94 FILE *f; 95 96 f = fopen(filespec, "r+"); 97 if (f) { 98 fclose(f); 99 return 1; 100 } 101 return 0; 102 #endif 103 } 104 105 static int r_access(const_profile_filespec_t filespec) 106 { 107 #ifdef HAVE_ACCESS 108 if (access(filespec, R_OK) == 0) 109 return 1; 110 else 111 return 0; 112 #else 113 /* 114 * We're on a substandard OS that doesn't support access. So 115 * we kludge a test using stdio routines, and hope fopen 116 * checks the r/w permissions. 117 */ 118 FILE *f; 119 120 f = fopen(filespec, "r"); 121 if (f) { 122 fclose(f); 123 return 1; 124 } 125 return 0; 126 #endif 127 } 128 129 int profile_file_is_writable(prf_file_t profile) 130 { 131 if (profile && profile->data) { 132 return rw_access(profile->data->filespec); 133 } else { 134 return 0; 135 } 136 } 137 138 prf_data_t 139 profile_make_prf_data(const char *filename) 140 { 141 prf_data_t d; 142 size_t len, flen, slen; 143 char *fcopy; 144 145 flen = strlen(filename); 146 slen = offsetof(struct _prf_data_t, filespec); 147 len = slen + flen + 1; 148 if (len < sizeof(struct _prf_data_t)) 149 len = sizeof(struct _prf_data_t); 150 d = malloc(len); 151 if (d == NULL) 152 return NULL; 153 memset(d, 0, len); 154 fcopy = (char *) d + slen; 155 assert(fcopy == d->filespec); 156 strlcpy(fcopy, filename, flen + 1); 157 d->refcount = 1; 158 d->magic = PROF_MAGIC_FILE_DATA; 159 d->root = NULL; 160 d->next = NULL; 161 d->fslen = flen; 162 if (k5_mutex_init(&d->lock) != 0) { 163 free(d); 164 return NULL; 165 } 166 return d; 167 } 168 169 errcode_t profile_open_file(const_profile_filespec_t filespec, 170 prf_file_t *ret_prof, char **ret_modspec) 171 { 172 prf_file_t prf; 173 errcode_t retval; 174 char *home_env = 0; 175 prf_data_t data; 176 char *expanded_filename; 177 178 retval = CALL_INIT_FUNCTION(profile_library_initializer); 179 if (retval) 180 return retval; 181 182 prf = malloc(sizeof(struct _prf_file_t)); 183 if (!prf) 184 return ENOMEM; 185 memset(prf, 0, sizeof(struct _prf_file_t)); 186 prf->magic = PROF_MAGIC_FILE; 187 188 if (filespec[0] == '~' && filespec[1] == '/') { 189 home_env = secure_getenv("HOME"); 190 #ifdef HAVE_PWD_H 191 if (home_env == NULL) { 192 uid_t uid; 193 struct passwd *pw, pwx; 194 char pwbuf[BUFSIZ]; 195 196 uid = getuid(); 197 if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw) 198 && pw != NULL && pw->pw_dir[0] != 0) 199 home_env = pw->pw_dir; 200 } 201 #endif 202 } 203 if (home_env) { 204 if (asprintf(&expanded_filename, "%s%s", home_env, 205 filespec + 1) < 0) 206 expanded_filename = 0; 207 } else 208 expanded_filename = strdup(filespec); 209 if (expanded_filename == 0) { 210 free(prf); 211 return ENOMEM; 212 } 213 214 k5_mutex_lock(&g_shared_trees_mutex); 215 for (data = g_shared_trees; data; data = data->next) { 216 if (!strcmp(data->filespec, expanded_filename) 217 /* Check that current uid has read access. */ 218 && r_access(data->filespec)) 219 break; 220 } 221 if (data) { 222 data->refcount++; 223 data->last_stat = 0; /* Make sure to stat when updating. */ 224 k5_mutex_unlock(&g_shared_trees_mutex); 225 retval = profile_update_file_data(data, NULL); 226 free(expanded_filename); 227 if (retval) { 228 profile_dereference_data(data); 229 free(prf); 230 return retval; 231 } 232 prf->data = data; 233 *ret_prof = prf; 234 return 0; 235 } 236 k5_mutex_unlock(&g_shared_trees_mutex); 237 data = profile_make_prf_data(expanded_filename); 238 if (data == NULL) { 239 free(prf); 240 free(expanded_filename); 241 return ENOMEM; 242 } 243 free(expanded_filename); 244 prf->data = data; 245 246 retval = profile_update_file(prf, ret_modspec); 247 if (retval) { 248 profile_close_file(prf); 249 return retval; 250 } 251 252 k5_mutex_lock(&g_shared_trees_mutex); 253 data->flags |= PROFILE_FILE_SHARED; 254 data->next = g_shared_trees; 255 g_shared_trees = data; 256 k5_mutex_unlock(&g_shared_trees_mutex); 257 258 *ret_prof = prf; 259 return 0; 260 } 261 262 prf_file_t profile_open_memory(void) 263 { 264 struct profile_node *root = NULL; 265 prf_file_t file = NULL; 266 prf_data_t data; 267 268 file = calloc(1, sizeof(*file)); 269 if (file == NULL) 270 goto errout; 271 file->magic = PROF_MAGIC_FILE; 272 273 if (profile_create_node("(root)", NULL, &root) != 0) 274 goto errout; 275 276 data = profile_make_prf_data(""); 277 if (data == NULL) 278 goto errout; 279 280 data->root = root; 281 data->flags = PROFILE_FILE_NO_RELOAD | PROFILE_FILE_DIRTY; 282 file->data = data; 283 file->next = NULL; 284 return file; 285 286 errout: 287 free(file); 288 if (root != NULL) 289 profile_free_node(root); 290 return NULL; 291 } 292 293 errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec) 294 { 295 errcode_t retval; 296 #ifdef HAVE_STAT 297 struct stat st; 298 unsigned long frac; 299 time_t now; 300 #endif 301 FILE *f; 302 int isdir = 0; 303 304 /* Don't reload if the backing file isn't a regular file. */ 305 if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL) 306 return 0; 307 /* Don't reload a modified data object, as the modifications may be 308 * important for this object's use. */ 309 if (data->flags & PROFILE_FILE_DIRTY) 310 return 0; 311 312 #ifdef HAVE_STAT 313 now = time(0); 314 if (now == data->last_stat && data->root != NULL) { 315 return 0; 316 } 317 if (stat(data->filespec, &st)) { 318 return errno; 319 } 320 data->last_stat = now; 321 #if defined HAVE_STRUCT_STAT_ST_MTIMENSEC 322 frac = st.st_mtimensec; 323 #elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 324 frac = st.st_mtimespec.tv_nsec; 325 #elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 326 frac = st.st_mtim.tv_nsec; 327 #else 328 frac = 0; 329 #endif 330 if (st.st_mtime == data->timestamp 331 && frac == data->frac_ts 332 && data->root != NULL) { 333 return 0; 334 } 335 if (data->root) { 336 profile_free_node(data->root); 337 data->root = 0; 338 } 339 340 /* Only try to reload regular files, not devices such as pipes. */ 341 if ((st.st_mode & S_IFMT) != S_IFREG) 342 data->flags |= PROFILE_FILE_NO_RELOAD; 343 #else 344 /* 345 * If we don't have the stat() call, assume that our in-core 346 * memory image is correct. That is, we won't reread the 347 * profile file if it changes. 348 */ 349 if (data->root) { 350 return 0; 351 } 352 #endif 353 354 #ifdef HAVE_STAT 355 isdir = S_ISDIR(st.st_mode); 356 #endif 357 if (!isdir) { 358 errno = 0; 359 f = fopen(data->filespec, "r"); 360 if (f == NULL) 361 return (errno != 0) ? errno : ENOENT; 362 set_cloexec_file(f); 363 } 364 365 data->upd_serial++; 366 367 if (isdir) { 368 retval = profile_process_directory(data->filespec, &data->root); 369 } else { 370 retval = profile_parse_file(f, &data->root, ret_modspec); 371 (void)fclose(f); 372 } 373 if (retval) { 374 return retval; 375 } 376 assert(data->root != NULL); 377 #ifdef HAVE_STAT 378 data->timestamp = st.st_mtime; 379 data->frac_ts = frac; 380 #endif 381 return 0; 382 } 383 384 errcode_t profile_update_file_data(prf_data_t data, char **ret_modspec) 385 { 386 errcode_t retval; 387 388 k5_mutex_lock(&data->lock); 389 retval = profile_update_file_data_locked(data, ret_modspec); 390 k5_mutex_unlock(&data->lock); 391 return retval; 392 } 393 394 static int 395 make_hard_link(const char *oldpath, const char *newpath) 396 { 397 #ifdef _WIN32 398 return -1; 399 #else 400 return link(oldpath, newpath); 401 #endif 402 } 403 404 static errcode_t write_data_to_file(prf_data_t data, const char *outfile, 405 int can_create) 406 { 407 FILE *f; 408 profile_filespec_t new_file; 409 profile_filespec_t old_file; 410 errcode_t retval = 0; 411 412 retval = ENOMEM; 413 414 new_file = old_file = 0; 415 if (asprintf(&new_file, "%s.$$$", outfile) < 0) { 416 new_file = NULL; 417 goto errout; 418 } 419 if (asprintf(&old_file, "%s.bak", outfile) < 0) { 420 old_file = NULL; 421 goto errout; 422 } 423 424 errno = 0; 425 426 f = fopen(new_file, "w"); 427 if (!f) { 428 retval = errno; 429 if (retval == 0) 430 retval = PROF_FAIL_OPEN; 431 goto errout; 432 } 433 434 set_cloexec_file(f); 435 profile_write_tree_file(data->root, f); 436 if (fclose(f) != 0) { 437 retval = errno; 438 goto errout; 439 } 440 441 unlink(old_file); 442 if (make_hard_link(outfile, old_file) == 0) { 443 /* Okay, got the hard link. Yay. Now we've got our 444 backup version, so just put the new version in 445 place. */ 446 if (rename(new_file, outfile)) { 447 /* Weird, the rename didn't work. But the old version 448 should still be in place, so no special cleanup is 449 needed. */ 450 retval = errno; 451 goto errout; 452 } 453 } else if (errno == ENOENT && can_create) { 454 if (rename(new_file, outfile)) { 455 retval = errno; 456 goto errout; 457 } 458 } else { 459 /* Couldn't make the hard link, so there's going to be a 460 small window where data->filespec does not refer to 461 either version. */ 462 #ifndef _WIN32 463 sync(); 464 #endif 465 if (rename(outfile, old_file)) { 466 retval = errno; 467 goto errout; 468 } 469 if (rename(new_file, outfile)) { 470 retval = errno; 471 rename(old_file, outfile); /* back out... */ 472 goto errout; 473 } 474 } 475 476 retval = 0; 477 478 errout: 479 if (new_file) 480 free(new_file); 481 if (old_file) 482 free(old_file); 483 return retval; 484 } 485 486 errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp) 487 { 488 errcode_t retval; 489 490 k5_mutex_lock(&data->lock); 491 retval = profile_write_tree_to_buffer(data->root, bufp); 492 k5_mutex_unlock(&data->lock); 493 return retval; 494 } 495 496 errcode_t profile_flush_file_data(prf_data_t data) 497 { 498 errcode_t retval = 0; 499 500 if (!data || data->magic != PROF_MAGIC_FILE_DATA) 501 return PROF_MAGIC_FILE_DATA; 502 503 /* Do nothing if this data object has no backing file. */ 504 if (*data->filespec == '\0') 505 return 0; 506 507 k5_mutex_lock(&data->lock); 508 509 if ((data->flags & PROFILE_FILE_DIRTY) == 0) { 510 k5_mutex_unlock(&data->lock); 511 return 0; 512 } 513 514 retval = write_data_to_file(data, data->filespec, 0); 515 data->flags &= ~PROFILE_FILE_DIRTY; 516 k5_mutex_unlock(&data->lock); 517 return retval; 518 } 519 520 errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile) 521 { 522 errcode_t retval = 0; 523 524 if (!data || data->magic != PROF_MAGIC_FILE_DATA) 525 return PROF_MAGIC_FILE_DATA; 526 527 k5_mutex_lock(&data->lock); 528 retval = write_data_to_file(data, outfile, 1); 529 k5_mutex_unlock(&data->lock); 530 return retval; 531 } 532 533 534 535 void profile_dereference_data(prf_data_t data) 536 { 537 k5_mutex_lock(&g_shared_trees_mutex); 538 profile_dereference_data_locked(data); 539 k5_mutex_unlock(&g_shared_trees_mutex); 540 } 541 void profile_dereference_data_locked(prf_data_t data) 542 { 543 data->refcount--; 544 if (data->refcount == 0) 545 profile_free_file_data(data); 546 } 547 548 void profile_lock_global(void) 549 { 550 k5_mutex_lock(&g_shared_trees_mutex); 551 } 552 void profile_unlock_global(void) 553 { 554 k5_mutex_unlock(&g_shared_trees_mutex); 555 } 556 557 prf_file_t profile_copy_file(prf_file_t oldfile) 558 { 559 prf_file_t file; 560 561 file = calloc(1, sizeof(*file)); 562 if (file == NULL) 563 return NULL; 564 file->magic = PROF_MAGIC_FILE; 565 566 /* Shared data objects can just have their reference counts incremented. */ 567 if (oldfile->data->flags & PROFILE_FILE_SHARED) { 568 profile_lock_global(); 569 oldfile->data->refcount++; 570 profile_unlock_global(); 571 file->data = oldfile->data; 572 return file; 573 } 574 575 /* Otherwise we need to copy the data object. */ 576 file->data = profile_make_prf_data(oldfile->data->filespec); 577 if (file->data == NULL) { 578 free(file); 579 return NULL; 580 } 581 k5_mutex_lock(&oldfile->data->lock); 582 file->data->flags = oldfile->data->flags; 583 file->data->last_stat = oldfile->data->last_stat; 584 file->data->frac_ts = oldfile->data->frac_ts; 585 file->data->root = profile_copy_node(oldfile->data->root); 586 k5_mutex_unlock(&oldfile->data->lock); 587 if (file->data->root == NULL) { 588 profile_free_file(file); 589 return NULL; 590 } 591 592 return file; 593 } 594 595 void profile_free_file(prf_file_t prf) 596 { 597 profile_dereference_data(prf->data); 598 free(prf); 599 } 600 601 /* Call with mutex locked! */ 602 static void profile_free_file_data(prf_data_t data) 603 { 604 if (data->flags & PROFILE_FILE_SHARED) { 605 /* Remove from linked list. */ 606 if (g_shared_trees == data) 607 g_shared_trees = data->next; 608 else { 609 prf_data_t prev, next; 610 prev = g_shared_trees; 611 next = prev->next; 612 while (next) { 613 if (next == data) { 614 prev->next = next->next; 615 break; 616 } 617 prev = next; 618 next = next->next; 619 } 620 } 621 } 622 if (data->root) 623 profile_free_node(data->root); 624 data->magic = 0; 625 k5_mutex_destroy(&data->lock); 626 free(data); 627 } 628 629 errcode_t profile_close_file(prf_file_t prf) 630 { 631 errcode_t retval; 632 633 retval = profile_flush_file(prf); 634 if (retval) 635 return retval; 636 profile_free_file(prf); 637 return 0; 638 } 639