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