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 return d; 163 } 164 165 errcode_t profile_open_file(const_profile_filespec_t filespec, 166 prf_file_t *ret_prof, char **ret_modspec) 167 { 168 prf_file_t prf; 169 errcode_t retval; 170 char *home_env = 0; 171 prf_data_t data; 172 char *expanded_filename; 173 174 retval = CALL_INIT_FUNCTION(profile_library_initializer); 175 if (retval) 176 return retval; 177 178 prf = malloc(sizeof(struct _prf_file_t)); 179 if (!prf) 180 return ENOMEM; 181 memset(prf, 0, sizeof(struct _prf_file_t)); 182 prf->magic = PROF_MAGIC_FILE; 183 184 if (filespec[0] == '~' && filespec[1] == '/') { 185 home_env = secure_getenv("HOME"); 186 #ifdef HAVE_PWD_H 187 if (home_env == NULL) { 188 uid_t uid; 189 struct passwd *pw, pwx; 190 char pwbuf[BUFSIZ]; 191 192 uid = getuid(); 193 if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw) 194 && pw != NULL && pw->pw_dir[0] != 0) 195 home_env = pw->pw_dir; 196 } 197 #endif 198 } 199 if (home_env) { 200 if (asprintf(&expanded_filename, "%s%s", home_env, 201 filespec + 1) < 0) 202 expanded_filename = 0; 203 } else 204 expanded_filename = strdup(filespec); 205 if (expanded_filename == 0) { 206 free(prf); 207 return ENOMEM; 208 } 209 210 k5_mutex_lock(&g_shared_trees_mutex); 211 for (data = g_shared_trees; data; data = data->next) { 212 if (!strcmp(data->filespec, expanded_filename) 213 /* Check that current uid has read access. */ 214 && r_access(data->filespec)) 215 break; 216 } 217 if (data) { 218 data->refcount++; 219 data->last_stat = 0; /* Make sure to stat when updating. */ 220 k5_mutex_unlock(&g_shared_trees_mutex); 221 retval = profile_update_file_data(data, NULL); 222 free(expanded_filename); 223 if (retval) { 224 profile_dereference_data(data); 225 free(prf); 226 return retval; 227 } 228 prf->data = data; 229 *ret_prof = prf; 230 return 0; 231 } 232 k5_mutex_unlock(&g_shared_trees_mutex); 233 data = profile_make_prf_data(expanded_filename); 234 if (data == NULL) { 235 free(prf); 236 free(expanded_filename); 237 return ENOMEM; 238 } 239 free(expanded_filename); 240 prf->data = data; 241 242 retval = k5_mutex_init(&data->lock); 243 if (retval) { 244 free(data); 245 free(prf); 246 return retval; 247 } 248 249 retval = profile_update_file(prf, ret_modspec); 250 if (retval) { 251 profile_close_file(prf); 252 return retval; 253 } 254 255 k5_mutex_lock(&g_shared_trees_mutex); 256 data->flags |= PROFILE_FILE_SHARED; 257 data->next = g_shared_trees; 258 g_shared_trees = data; 259 k5_mutex_unlock(&g_shared_trees_mutex); 260 261 *ret_prof = prf; 262 return 0; 263 } 264 265 errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec) 266 { 267 errcode_t retval; 268 #ifdef HAVE_STAT 269 struct stat st; 270 unsigned long frac; 271 time_t now; 272 #endif 273 FILE *f; 274 int isdir = 0; 275 276 if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL) 277 return 0; 278 279 #ifdef HAVE_STAT 280 now = time(0); 281 if (now == data->last_stat && data->root != NULL) { 282 return 0; 283 } 284 if (stat(data->filespec, &st)) { 285 return errno; 286 } 287 data->last_stat = now; 288 #if defined HAVE_STRUCT_STAT_ST_MTIMENSEC 289 frac = st.st_mtimensec; 290 #elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 291 frac = st.st_mtimespec.tv_nsec; 292 #elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 293 frac = st.st_mtim.tv_nsec; 294 #else 295 frac = 0; 296 #endif 297 if (st.st_mtime == data->timestamp 298 && frac == data->frac_ts 299 && data->root != NULL) { 300 return 0; 301 } 302 if (data->root) { 303 profile_free_node(data->root); 304 data->root = 0; 305 } 306 307 /* Only try to reload regular files, not devices such as pipes. */ 308 if ((st.st_mode & S_IFMT) != S_IFREG) 309 data->flags |= PROFILE_FILE_NO_RELOAD; 310 #else 311 /* 312 * If we don't have the stat() call, assume that our in-core 313 * memory image is correct. That is, we won't reread the 314 * profile file if it changes. 315 */ 316 if (data->root) { 317 return 0; 318 } 319 #endif 320 321 #ifdef HAVE_STAT 322 isdir = S_ISDIR(st.st_mode); 323 #endif 324 if (!isdir) { 325 errno = 0; 326 f = fopen(data->filespec, "r"); 327 if (f == NULL) 328 return (errno != 0) ? errno : ENOENT; 329 set_cloexec_file(f); 330 } 331 332 data->upd_serial++; 333 data->flags &= ~PROFILE_FILE_DIRTY; 334 335 if (isdir) { 336 retval = profile_process_directory(data->filespec, &data->root); 337 } else { 338 retval = profile_parse_file(f, &data->root, ret_modspec); 339 (void)fclose(f); 340 } 341 if (retval) { 342 return retval; 343 } 344 assert(data->root != NULL); 345 #ifdef HAVE_STAT 346 data->timestamp = st.st_mtime; 347 data->frac_ts = frac; 348 #endif 349 return 0; 350 } 351 352 errcode_t profile_update_file_data(prf_data_t data, char **ret_modspec) 353 { 354 errcode_t retval; 355 356 k5_mutex_lock(&data->lock); 357 retval = profile_update_file_data_locked(data, ret_modspec); 358 k5_mutex_unlock(&data->lock); 359 return retval; 360 } 361 362 static int 363 make_hard_link(const char *oldpath, const char *newpath) 364 { 365 #ifdef _WIN32 366 return -1; 367 #else 368 return link(oldpath, newpath); 369 #endif 370 } 371 372 static errcode_t write_data_to_file(prf_data_t data, const char *outfile, 373 int can_create) 374 { 375 FILE *f; 376 profile_filespec_t new_file; 377 profile_filespec_t old_file; 378 errcode_t retval = 0; 379 380 retval = ENOMEM; 381 382 new_file = old_file = 0; 383 if (asprintf(&new_file, "%s.$$$", outfile) < 0) { 384 new_file = NULL; 385 goto errout; 386 } 387 if (asprintf(&old_file, "%s.bak", outfile) < 0) { 388 old_file = NULL; 389 goto errout; 390 } 391 392 errno = 0; 393 394 f = fopen(new_file, "w"); 395 if (!f) { 396 retval = errno; 397 if (retval == 0) 398 retval = PROF_FAIL_OPEN; 399 goto errout; 400 } 401 402 set_cloexec_file(f); 403 profile_write_tree_file(data->root, f); 404 if (fclose(f) != 0) { 405 retval = errno; 406 goto errout; 407 } 408 409 unlink(old_file); 410 if (make_hard_link(outfile, old_file) == 0) { 411 /* Okay, got the hard link. Yay. Now we've got our 412 backup version, so just put the new version in 413 place. */ 414 if (rename(new_file, outfile)) { 415 /* Weird, the rename didn't work. But the old version 416 should still be in place, so no special cleanup is 417 needed. */ 418 retval = errno; 419 goto errout; 420 } 421 } else if (errno == ENOENT && can_create) { 422 if (rename(new_file, outfile)) { 423 retval = errno; 424 goto errout; 425 } 426 } else { 427 /* Couldn't make the hard link, so there's going to be a 428 small window where data->filespec does not refer to 429 either version. */ 430 #ifndef _WIN32 431 sync(); 432 #endif 433 if (rename(outfile, old_file)) { 434 retval = errno; 435 goto errout; 436 } 437 if (rename(new_file, outfile)) { 438 retval = errno; 439 rename(old_file, outfile); /* back out... */ 440 goto errout; 441 } 442 } 443 444 retval = 0; 445 446 errout: 447 if (new_file) 448 free(new_file); 449 if (old_file) 450 free(old_file); 451 return retval; 452 } 453 454 errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp) 455 { 456 errcode_t retval; 457 458 k5_mutex_lock(&data->lock); 459 retval = profile_write_tree_to_buffer(data->root, bufp); 460 k5_mutex_unlock(&data->lock); 461 return retval; 462 } 463 464 errcode_t profile_flush_file_data(prf_data_t data) 465 { 466 errcode_t retval = 0; 467 468 if (!data || data->magic != PROF_MAGIC_FILE_DATA) 469 return PROF_MAGIC_FILE_DATA; 470 471 k5_mutex_lock(&data->lock); 472 473 if ((data->flags & PROFILE_FILE_DIRTY) == 0) { 474 k5_mutex_unlock(&data->lock); 475 return 0; 476 } 477 478 retval = write_data_to_file(data, data->filespec, 0); 479 data->flags &= ~PROFILE_FILE_DIRTY; 480 k5_mutex_unlock(&data->lock); 481 return retval; 482 } 483 484 errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile) 485 { 486 errcode_t retval = 0; 487 488 if (!data || data->magic != PROF_MAGIC_FILE_DATA) 489 return PROF_MAGIC_FILE_DATA; 490 491 k5_mutex_lock(&data->lock); 492 retval = write_data_to_file(data, outfile, 1); 493 k5_mutex_unlock(&data->lock); 494 return retval; 495 } 496 497 498 499 void profile_dereference_data(prf_data_t data) 500 { 501 k5_mutex_lock(&g_shared_trees_mutex); 502 profile_dereference_data_locked(data); 503 k5_mutex_unlock(&g_shared_trees_mutex); 504 } 505 void profile_dereference_data_locked(prf_data_t data) 506 { 507 data->refcount--; 508 if (data->refcount == 0) 509 profile_free_file_data(data); 510 } 511 512 void profile_lock_global() 513 { 514 k5_mutex_lock(&g_shared_trees_mutex); 515 } 516 void profile_unlock_global() 517 { 518 k5_mutex_unlock(&g_shared_trees_mutex); 519 } 520 521 void profile_free_file(prf_file_t prf) 522 { 523 profile_dereference_data(prf->data); 524 free(prf); 525 } 526 527 /* Call with mutex locked! */ 528 static void profile_free_file_data(prf_data_t data) 529 { 530 if (data->flags & PROFILE_FILE_SHARED) { 531 /* Remove from linked list. */ 532 if (g_shared_trees == data) 533 g_shared_trees = data->next; 534 else { 535 prf_data_t prev, next; 536 prev = g_shared_trees; 537 next = prev->next; 538 while (next) { 539 if (next == data) { 540 prev->next = next->next; 541 break; 542 } 543 prev = next; 544 next = next->next; 545 } 546 } 547 } 548 if (data->root) 549 profile_free_node(data->root); 550 data->magic = 0; 551 k5_mutex_destroy(&data->lock); 552 free(data); 553 } 554 555 errcode_t profile_close_file(prf_file_t prf) 556 { 557 errcode_t retval; 558 559 retval = profile_flush_file(prf); 560 if (retval) 561 return retval; 562 profile_free_file(prf); 563 return 0; 564 } 565