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