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