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