2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * prof_file.c ---- routines that manipulate an individual profile file.
2N/A */
2N/A
2N/A#include <autoconf.h>
2N/A#include "prof_int.h"
2N/A
2N/A#include <stdio.h>
2N/A#ifdef HAVE_STDLIB_H
2N/A#include <stdlib.h>
2N/A#endif
2N/A#ifdef HAVE_UNISTD_H
2N/A#include <unistd.h>
2N/A#endif
2N/A#include <string.h>
2N/A#include <stddef.h>
2N/A
2N/A#include <sys/types.h>
2N/A#include <sys/stat.h>
2N/A#include <errno.h>
2N/A
2N/A#ifdef HAVE_PWD_H
2N/A#include <pwd.h>
2N/A#endif
2N/A
2N/A#if defined(_WIN32)
2N/A#include <io.h>
2N/A#define HAVE_STAT
2N/A#define stat _stat
2N/A#endif
2N/A
2N/A#include "k5-platform.h"
2N/A
2N/Astruct global_shared_profile_data {
2N/A /* This is the head of the global list of shared trees */
2N/A prf_data_t trees;
2N/A /* Lock for above list. */
2N/A k5_mutex_t mutex;
2N/A};
2N/A#define g_shared_trees (krb5int_profile_shared_data.trees)
2N/A#define g_shared_trees_mutex (krb5int_profile_shared_data.mutex)
2N/A
2N/Astatic struct global_shared_profile_data krb5int_profile_shared_data = {
2N/A 0,
2N/A K5_MUTEX_PARTIAL_INITIALIZER
2N/A};
2N/A
2N/AMAKE_INIT_FUNCTION(profile_library_initializer);
2N/AMAKE_FINI_FUNCTION(profile_library_finalizer);
2N/A
2N/Aint profile_library_initializer(void)
2N/A{
2N/A#ifdef SHOW_INITFINI_FUNCS
2N/A printf("profile_library_initializer\n");
2N/A#endif
2N/A#if !USE_BUNDLE_ERROR_STRINGS
2N/A add_error_table(&et_prof_error_table);
2N/A#endif
2N/A return k5_mutex_finish_init(&g_shared_trees_mutex);
2N/A}
2N/Avoid profile_library_finalizer(void)
2N/A{
2N/A if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING()) {
2N/A#ifdef SHOW_INITFINI_FUNCS
2N/A printf("profile_library_finalizer: skipping\n");
2N/A#endif
2N/A return;
2N/A }
2N/A#ifdef SHOW_INITFINI_FUNCS
2N/A printf("profile_library_finalizer\n");
2N/A#endif
2N/A k5_mutex_destroy(&g_shared_trees_mutex);
2N/A#if !USE_BUNDLE_ERROR_STRINGS
2N/A remove_error_table(&et_prof_error_table);
2N/A#endif
2N/A}
2N/A
2N/Astatic void profile_free_file_data(prf_data_t);
2N/A
2N/A#if 0
2N/A
2N/A#define scan_shared_trees_locked() \
2N/A { \
2N/A prf_data_t d; \
2N/A k5_mutex_assert_locked(&g_shared_trees_mutex); \
2N/A for (d = g_shared_trees; d; d = d->next) { \
2N/A assert(d->magic == PROF_MAGIC_FILE_DATA); \
2N/A assert((d->flags & PROFILE_FILE_SHARED) != 0); \
2N/A assert(d->filespec[0] != 0); \
2N/A assert(d->fslen <= 1000); /* XXX */ \
2N/A assert(d->filespec[d->fslen] == 0); \
2N/A assert(d->fslen = strlen(d->filespec)); \
2N/A assert(d->root != NULL); \
2N/A } \
2N/A }
2N/A
2N/A#define scan_shared_trees_unlocked() \
2N/A { \
2N/A int r; \
2N/A r = k5_mutex_lock(&g_shared_trees_mutex); \
2N/A assert (r == 0); \
2N/A scan_shared_trees_locked(); \
2N/A k5_mutex_unlock(&g_shared_trees_mutex); \
2N/A }
2N/A
2N/A#else
2N/A
2N/A#define scan_shared_trees_locked() { ; }
2N/A#define scan_shared_trees_unlocked() { ; }
2N/A
2N/A#endif
2N/A
2N/Astatic int rw_access(const_profile_filespec_t filespec)
2N/A{
2N/A#ifdef HAVE_ACCESS
2N/A if (access(filespec, W_OK) == 0)
2N/A return 1;
2N/A else
2N/A return 0;
2N/A#else
2N/A /*
2N/A * We're on a substandard OS that doesn't support access. So
2N/A * we kludge a test using stdio routines, and hope fopen
2N/A * checks the r/w permissions.
2N/A */
2N/A FILE *f;
2N/A /* Solaris Kerberos */
2N/A f = fopen(filespec, "r+F");
2N/A if (f) {
2N/A fclose(f);
2N/A return 1;
2N/A }
2N/A return 0;
2N/A#endif
2N/A}
2N/A
2N/Astatic int r_access(const_profile_filespec_t filespec)
2N/A{
2N/A#ifdef HAVE_ACCESS
2N/A if (access(filespec, R_OK) == 0)
2N/A return 1;
2N/A else
2N/A return 0;
2N/A#else
2N/A /*
2N/A * We're on a substandard OS that doesn't support access. So
2N/A * we kludge a test using stdio routines, and hope fopen
2N/A * checks the r/w permissions.
2N/A */
2N/A FILE *f;
2N/A
2N/A /* Solaris Kerberos */
2N/A f = fopen(filespec, "rF");
2N/A if (f) {
2N/A fclose(f);
2N/A return 1;
2N/A }
2N/A return 0;
2N/A#endif
2N/A}
2N/A
2N/Aint profile_file_is_writable(prf_file_t profile)
2N/A{
2N/A if (profile && profile->data) {
2N/A return rw_access(profile->data->filespec);
2N/A } else {
2N/A return 0;
2N/A }
2N/A}
2N/A
2N/Aprf_data_t
2N/Aprofile_make_prf_data(const char *filename)
2N/A{
2N/A prf_data_t d;
2N/A size_t len, flen, slen;
2N/A char *fcopy;
2N/A
2N/A flen = strlen(filename);
2N/A slen = offsetof(struct _prf_data_t, filespec);
2N/A len = slen + flen + 1;
2N/A if (len < sizeof(struct _prf_data_t))
2N/A len = sizeof(struct _prf_data_t);
2N/A d = malloc(len);
2N/A if (d == NULL)
2N/A return NULL;
2N/A memset(d, 0, len);
2N/A fcopy = (char *) d + slen;
2N/A assert(fcopy == d->filespec);
2N/A strlcpy(fcopy, filename, flen + 1);
2N/A d->refcount = 1;
2N/A d->magic = PROF_MAGIC_FILE_DATA;
2N/A d->root = NULL;
2N/A d->next = NULL;
2N/A d->fslen = flen;
2N/A return d;
2N/A}
2N/A
2N/Aerrcode_t profile_open_file(const_profile_filespec_t filespec,
2N/A prf_file_t *ret_prof)
2N/A{
2N/A prf_file_t prf;
2N/A errcode_t retval;
2N/A char *home_env = 0;
2N/A prf_data_t data;
2N/A char *expanded_filename;
2N/A
2N/A retval = CALL_INIT_FUNCTION(profile_library_initializer);
2N/A if (retval)
2N/A return retval;
2N/A
2N/A scan_shared_trees_unlocked();
2N/A
2N/A prf = malloc(sizeof(struct _prf_file_t));
2N/A if (!prf)
2N/A return ENOMEM;
2N/A memset(prf, 0, sizeof(struct _prf_file_t));
2N/A prf->magic = PROF_MAGIC_FILE;
2N/A
2N/A if (filespec[0] == '~' && filespec[1] == '/') {
2N/A home_env = getenv("HOME");
2N/A#ifdef HAVE_PWD_H
2N/A if (home_env == NULL) {
2N/A uid_t uid;
2N/A struct passwd *pw, pwx;
2N/A char pwbuf[BUFSIZ];
2N/A
2N/A uid = getuid();
2N/A if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
2N/A && pw != NULL && pw->pw_dir[0] != 0)
2N/A home_env = pw->pw_dir;
2N/A }
2N/A#endif
2N/A }
2N/A if (home_env) {
2N/A if (asprintf(&expanded_filename, "%s%s", home_env,
2N/A filespec + 1) < 0)
2N/A expanded_filename = 0;
2N/A } else
2N/A expanded_filename = strdup(filespec);
2N/A if (expanded_filename == 0) {
2N/A free(prf);
2N/A return ENOMEM;
2N/A }
2N/A
2N/A retval = k5_mutex_lock(&g_shared_trees_mutex);
2N/A if (retval) {
2N/A free(expanded_filename);
2N/A free(prf);
2N/A scan_shared_trees_unlocked();
2N/A return retval;
2N/A }
2N/A scan_shared_trees_locked();
2N/A for (data = g_shared_trees; data; data = data->next) {
2N/A if (!strcmp(data->filespec, expanded_filename)
2N/A /* Check that current uid has read access. */
2N/A && r_access(data->filespec))
2N/A break;
2N/A }
2N/A if (data) {
2N/A data->refcount++;
2N/A (void) k5_mutex_unlock(&g_shared_trees_mutex);
2N/A retval = profile_update_file_data(data);
2N/A free(expanded_filename);
2N/A prf->data = data;
2N/A *ret_prof = prf;
2N/A scan_shared_trees_unlocked();
2N/A return retval;
2N/A }
2N/A (void) k5_mutex_unlock(&g_shared_trees_mutex);
2N/A data = profile_make_prf_data(expanded_filename);
2N/A if (data == NULL) {
2N/A free(prf);
2N/A free(expanded_filename);
2N/A return ENOMEM;
2N/A }
2N/A free(expanded_filename);
2N/A prf->data = data;
2N/A
2N/A retval = k5_mutex_init(&data->lock);
2N/A if (retval) {
2N/A free(data);
2N/A free(prf);
2N/A return retval;
2N/A }
2N/A
2N/A retval = profile_update_file(prf);
2N/A if (retval) {
2N/A profile_close_file(prf);
2N/A return retval;
2N/A }
2N/A
2N/A retval = k5_mutex_lock(&g_shared_trees_mutex);
2N/A if (retval) {
2N/A profile_close_file(prf);
2N/A scan_shared_trees_unlocked();
2N/A return retval;
2N/A }
2N/A scan_shared_trees_locked();
2N/A data->flags |= PROFILE_FILE_SHARED;
2N/A data->next = g_shared_trees;
2N/A g_shared_trees = data;
2N/A scan_shared_trees_locked();
2N/A (void) k5_mutex_unlock(&g_shared_trees_mutex);
2N/A
2N/A *ret_prof = prf;
2N/A return 0;
2N/A}
2N/A
2N/Aerrcode_t profile_update_file_data_locked(prf_data_t data)
2N/A{
2N/A errcode_t retval;
2N/A#ifdef HAVE_STAT
2N/A struct stat st;
2N/A unsigned long frac;
2N/A time_t now;
2N/A#endif
2N/A FILE *f;
2N/A
2N/A#ifdef HAVE_STAT
2N/A now = time(0);
2N/A if (now == data->last_stat && data->root != NULL) {
2N/A return 0;
2N/A }
2N/A if (stat(data->filespec, &st)) {
2N/A return errno;
2N/A }
2N/A data->last_stat = now;
2N/A#if defined HAVE_STRUCT_STAT_ST_MTIMENSEC
2N/A frac = st.st_mtimensec;
2N/A#elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
2N/A frac = st.st_mtimespec.tv_nsec;
2N/A#elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
2N/A frac = st.st_mtim.tv_nsec;
2N/A#else
2N/A frac = 0;
2N/A#endif
2N/A if (st.st_mtime == data->timestamp
2N/A && frac == data->frac_ts
2N/A && data->root != NULL) {
2N/A return 0;
2N/A }
2N/A if (data->root) {
2N/A profile_free_node(data->root);
2N/A data->root = 0;
2N/A }
2N/A#else
2N/A /*
2N/A * If we don't have the stat() call, assume that our in-core
2N/A * memory image is correct. That is, we won't reread the
2N/A * profile file if it changes.
2N/A */
2N/A if (data->root) {
2N/A return 0;
2N/A }
2N/A#endif
2N/A errno = 0;
2N/A /* Solaris Kerberos */
2N/A f = fopen(data->filespec, "rF");
2N/A if (f == NULL) {
2N/A retval = errno;
2N/A if (retval == 0)
2N/A retval = ENOENT;
2N/A return retval;
2N/A }
2N/A set_cloexec_file(f);
2N/A data->upd_serial++;
2N/A data->flags &= PROFILE_FILE_SHARED; /* FIXME same as '=' operator */
2N/A retval = profile_parse_file(f, &data->root);
2N/A fclose(f);
2N/A if (retval) {
2N/A return retval;
2N/A }
2N/A assert(data->root != NULL);
2N/A#ifdef HAVE_STAT
2N/A data->timestamp = st.st_mtime;
2N/A data->frac_ts = frac;
2N/A#endif
2N/A return 0;
2N/A}
2N/A
2N/Aerrcode_t profile_update_file_data(prf_data_t data)
2N/A{
2N/A errcode_t retval, retval2;
2N/A
2N/A retval = k5_mutex_lock(&data->lock);
2N/A if (retval)
2N/A return retval;
2N/A retval = profile_update_file_data_locked(data);
2N/A retval2 = k5_mutex_unlock(&data->lock);
2N/A return retval ? retval : retval2;
2N/A}
2N/A
2N/Astatic int
2N/Amake_hard_link(const char *oldpath, const char *newpath)
2N/A{
2N/A#ifdef _WIN32
2N/A return -1;
2N/A#else
2N/A return link(oldpath, newpath);
2N/A#endif
2N/A}
2N/A
2N/Astatic errcode_t write_data_to_file(prf_data_t data, const char *outfile,
2N/A int can_create)
2N/A{
2N/A FILE *f;
2N/A profile_filespec_t new_file;
2N/A profile_filespec_t old_file;
2N/A errcode_t retval = 0;
2N/A
2N/A retval = ENOMEM;
2N/A
2N/A new_file = old_file = 0;
2N/A if (asprintf(&new_file, "%s.$$$", outfile) < 0) {
2N/A new_file = NULL;
2N/A goto errout;
2N/A }
2N/A if (asprintf(&old_file, "%s.bak", outfile) < 0) {
2N/A old_file = NULL;
2N/A goto errout;
2N/A }
2N/A
2N/A errno = 0;
2N/A
2N/A /* Solaris Kerberos */
2N/A f = fopen(new_file, "wF");
2N/A if (!f) {
2N/A retval = errno;
2N/A if (retval == 0)
2N/A retval = PROF_FAIL_OPEN;
2N/A goto errout;
2N/A }
2N/A
2N/A set_cloexec_file(f);
2N/A profile_write_tree_file(data->root, f);
2N/A if (fclose(f) != 0) {
2N/A retval = errno;
2N/A goto errout;
2N/A }
2N/A
2N/A unlink(old_file);
2N/A if (make_hard_link(outfile, old_file) == 0) {
2N/A /* Okay, got the hard link. Yay. Now we've got our
2N/A backup version, so just put the new version in
2N/A place. */
2N/A if (rename(new_file, outfile)) {
2N/A /* Weird, the rename didn't work. But the old version
2N/A should still be in place, so no special cleanup is
2N/A needed. */
2N/A retval = errno;
2N/A goto errout;
2N/A }
2N/A } else if (errno == ENOENT && can_create) {
2N/A if (rename(new_file, outfile)) {
2N/A retval = errno;
2N/A goto errout;
2N/A }
2N/A } else {
2N/A /* Couldn't make the hard link, so there's going to be a
2N/A small window where data->filespec does not refer to
2N/A either version. */
2N/A#ifndef _WIN32
2N/A sync();
2N/A#endif
2N/A if (rename(outfile, old_file)) {
2N/A retval = errno;
2N/A goto errout;
2N/A }
2N/A if (rename(new_file, outfile)) {
2N/A retval = errno;
2N/A rename(old_file, outfile); /* back out... */
2N/A goto errout;
2N/A }
2N/A }
2N/A
2N/A data->flags = 0;
2N/A retval = 0;
2N/A
2N/Aerrout:
2N/A if (new_file)
2N/A free(new_file);
2N/A if (old_file)
2N/A free(old_file);
2N/A return retval;
2N/A}
2N/A
2N/Aerrcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
2N/A{
2N/A errcode_t retval;
2N/A retval = k5_mutex_lock(&data->lock);
2N/A if (retval)
2N/A return retval;
2N/A retval = profile_write_tree_to_buffer(data->root, bufp);
2N/A k5_mutex_unlock(&data->lock);
2N/A return retval;
2N/A}
2N/A
2N/Aerrcode_t profile_flush_file_data(prf_data_t data)
2N/A{
2N/A errcode_t retval = 0;
2N/A
2N/A if (!data || data->magic != PROF_MAGIC_FILE_DATA)
2N/A return PROF_MAGIC_FILE_DATA;
2N/A
2N/A retval = k5_mutex_lock(&data->lock);
2N/A if (retval)
2N/A return retval;
2N/A
2N/A if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
2N/A k5_mutex_unlock(&data->lock);
2N/A return 0;
2N/A }
2N/A
2N/A retval = write_data_to_file(data, data->filespec, 0);
2N/A k5_mutex_unlock(&data->lock);
2N/A return retval;
2N/A}
2N/A
2N/Aerrcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
2N/A{
2N/A errcode_t retval = 0;
2N/A
2N/A if (!data || data->magic != PROF_MAGIC_FILE_DATA)
2N/A return PROF_MAGIC_FILE_DATA;
2N/A
2N/A retval = k5_mutex_lock(&data->lock);
2N/A if (retval)
2N/A return retval;
2N/A retval = write_data_to_file(data, outfile, 1);
2N/A k5_mutex_unlock(&data->lock);
2N/A return retval;
2N/A}
2N/A
2N/A
2N/A
2N/Avoid profile_dereference_data(prf_data_t data)
2N/A{
2N/A int err;
2N/A err = k5_mutex_lock(&g_shared_trees_mutex);
2N/A if (err)
2N/A return;
2N/A profile_dereference_data_locked(data);
2N/A (void) k5_mutex_unlock(&g_shared_trees_mutex);
2N/A}
2N/Avoid profile_dereference_data_locked(prf_data_t data)
2N/A{
2N/A scan_shared_trees_locked();
2N/A data->refcount--;
2N/A if (data->refcount == 0)
2N/A profile_free_file_data(data);
2N/A scan_shared_trees_locked();
2N/A}
2N/A
2N/Aint profile_lock_global()
2N/A{
2N/A return k5_mutex_lock(&g_shared_trees_mutex);
2N/A}
2N/Aint profile_unlock_global()
2N/A{
2N/A return k5_mutex_unlock(&g_shared_trees_mutex);
2N/A}
2N/A
2N/Avoid profile_free_file(prf_file_t prf)
2N/A{
2N/A profile_dereference_data(prf->data);
2N/A free(prf);
2N/A}
2N/A
2N/A/* Call with mutex locked! */
2N/Astatic void profile_free_file_data(prf_data_t data)
2N/A{
2N/A scan_shared_trees_locked();
2N/A if (data->flags & PROFILE_FILE_SHARED) {
2N/A /* Remove from linked list. */
2N/A if (g_shared_trees == data)
2N/A g_shared_trees = data->next;
2N/A else {
2N/A prf_data_t prev, next;
2N/A prev = g_shared_trees;
2N/A next = prev->next;
2N/A while (next) {
2N/A if (next == data) {
2N/A prev->next = next->next;
2N/A break;
2N/A }
2N/A prev = next;
2N/A next = next->next;
2N/A }
2N/A }
2N/A }
2N/A if (data->root)
2N/A profile_free_node(data->root);
2N/A data->magic = 0;
2N/A k5_mutex_destroy(&data->lock);
2N/A free(data);
2N/A scan_shared_trees_locked();
2N/A}
2N/A
2N/Aerrcode_t profile_close_file(prf_file_t prf)
2N/A{
2N/A errcode_t retval;
2N/A
2N/A retval = profile_flush_file(prf);
2N/A if (retval)
2N/A return retval;
2N/A profile_free_file(prf);
2N/A return 0;
2N/A}