root/usr/src/lib/gss_mechs/mech_krb5/profile/prof_file.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * prof_file.c ---- routines that manipulate an individual profile file.
 */

#include <autoconf.h>
#include "prof_int.h"

#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <stddef.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif

#if defined(_WIN32)
#include <io.h>
#define HAVE_STAT
#define stat _stat
#endif

#include "k5-platform.h"

struct global_shared_profile_data {
        /* This is the head of the global list of shared trees */
        prf_data_t trees;
        /* Lock for above list.  */
        k5_mutex_t mutex;
};
#define g_shared_trees          (krb5int_profile_shared_data.trees)
#define g_shared_trees_mutex    (krb5int_profile_shared_data.mutex)

static struct global_shared_profile_data krb5int_profile_shared_data = {
    0,
    K5_MUTEX_PARTIAL_INITIALIZER
};

MAKE_INIT_FUNCTION(profile_library_initializer);
MAKE_FINI_FUNCTION(profile_library_finalizer);

int profile_library_initializer(void)
{
#ifdef SHOW_INITFINI_FUNCS
    printf("profile_library_initializer\n");
#endif
#if !USE_BUNDLE_ERROR_STRINGS
    add_error_table(&et_prof_error_table);
#endif
    return k5_mutex_finish_init(&g_shared_trees_mutex);
}
void profile_library_finalizer(void)
{
    if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING()) {
#ifdef SHOW_INITFINI_FUNCS
        printf("profile_library_finalizer: skipping\n");
#endif
        return;
    }
#ifdef SHOW_INITFINI_FUNCS
    printf("profile_library_finalizer\n");
#endif
    k5_mutex_destroy(&g_shared_trees_mutex);
#if !USE_BUNDLE_ERROR_STRINGS
    remove_error_table(&et_prof_error_table);
#endif
}

static void profile_free_file_data(prf_data_t);

#if 0

#define scan_shared_trees_locked()                              \
        {                                                       \
            prf_data_t d;                                       \
            k5_mutex_assert_locked(&g_shared_trees_mutex);      \
            for (d = g_shared_trees; d; d = d->next) {          \
                assert(d->magic == PROF_MAGIC_FILE_DATA);       \
                assert((d->flags & PROFILE_FILE_SHARED) != 0);  \
                assert(d->filespec[0] != 0);                    \
                assert(d->fslen <= 1000); /* XXX */             \
                assert(d->filespec[d->fslen] == 0);             \
                assert(d->fslen = strlen(d->filespec));         \
                assert(d->root != NULL);                        \
            }                                                   \
        }

#define scan_shared_trees_unlocked()                    \
        {                                               \
            int r;                                      \
            r = k5_mutex_lock(&g_shared_trees_mutex);   \
            assert (r == 0);                            \
            scan_shared_trees_locked();                 \
            k5_mutex_unlock(&g_shared_trees_mutex);     \
        }

#else

#define scan_shared_trees_locked()      { ; }
#define scan_shared_trees_unlocked()    { ; }

#endif

static int rw_access(const_profile_filespec_t filespec)
{
#ifdef HAVE_ACCESS
        if (access(filespec, W_OK) == 0)
                return 1;
        else
                return 0;
#else
        /*
         * We're on a substandard OS that doesn't support access.  So
         * we kludge a test using stdio routines, and hope fopen
         * checks the r/w permissions.
         */
        FILE    *f;
        /* Solaris Kerberos */
        f = fopen(filespec, "r+F");
        if (f) {
                fclose(f);
                return 1;
        }
        return 0;
#endif
}

static int r_access(const_profile_filespec_t filespec)
{
#ifdef HAVE_ACCESS
        if (access(filespec, R_OK) == 0)
                return 1;
        else
                return 0;
#else
        /*
         * We're on a substandard OS that doesn't support access.  So
         * we kludge a test using stdio routines, and hope fopen
         * checks the r/w permissions.
         */
        FILE    *f;

        /* Solaris Kerberos */
        f = fopen(filespec, "rF");
        if (f) {
                fclose(f);
                return 1;
        }
        return 0;
#endif
}

prf_data_t
profile_make_prf_data(const char *filename)
{
    prf_data_t d;
    size_t len, flen, slen;
    char *fcopy;

    flen = strlen(filename);
    slen = offsetof(struct _prf_data_t, filespec);
    len = slen + flen + 1;
    if (len < sizeof(struct _prf_data_t))
        len = sizeof(struct _prf_data_t);
    d = malloc(len);
    if (d == NULL)
        return NULL;
    memset(d, 0, len);
    fcopy = (char *) d + slen;
    assert(fcopy == d->filespec);
    strcpy(fcopy, filename);
    d->refcount = 1;
    d->comment = NULL;
    d->magic = PROF_MAGIC_FILE_DATA;
    d->root = NULL;
    d->next = NULL;
    d->fslen = flen;
    return d;
}

errcode_t profile_open_file(const_profile_filespec_t filespec,
                            prf_file_t *ret_prof)
{
        prf_file_t      prf;
        errcode_t       retval;
        char            *home_env = 0;
        unsigned int    len;
        prf_data_t      data;
        char            *expanded_filename;

        retval = CALL_INIT_FUNCTION(profile_library_initializer);
        if (retval)
                return retval;

        scan_shared_trees_unlocked();

        prf = malloc(sizeof(struct _prf_file_t));
        if (!prf)
                return ENOMEM;
        memset(prf, 0, sizeof(struct _prf_file_t));
        prf->magic = PROF_MAGIC_FILE;

        len = strlen(filespec)+1;
        if (filespec[0] == '~' && filespec[1] == '/') {
                home_env = getenv("HOME");
#ifdef HAVE_PWD_H
                if (home_env == NULL) {
                    uid_t uid;
                    struct passwd *pw, pwx;
                    char pwbuf[BUFSIZ];

                    uid = getuid();
                    if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
                        && pw != NULL && pw->pw_dir[0] != 0)
                        home_env = pw->pw_dir;
                }
#endif
                if (home_env)
                        len += strlen(home_env);
        }
        expanded_filename = malloc(len);
        if (expanded_filename == 0)
            return errno;
        if (home_env) {
            strcpy(expanded_filename, home_env);
            strcat(expanded_filename, filespec+1);
        } else
            memcpy(expanded_filename, filespec, len);

        retval = k5_mutex_lock(&g_shared_trees_mutex);
        if (retval) {
            free(expanded_filename);
            free(prf);
            scan_shared_trees_unlocked();
            return retval;
        }
        scan_shared_trees_locked();
        for (data = g_shared_trees; data; data = data->next) {
            if (!strcmp(data->filespec, expanded_filename)
                /* Check that current uid has read access.  */
                && r_access(data->filespec))
                break;
        }
        if (data) {
            data->refcount++;
            (void) k5_mutex_unlock(&g_shared_trees_mutex);
            retval = profile_update_file_data(data);
            free(expanded_filename);
            prf->data = data;
            *ret_prof = prf;
            scan_shared_trees_unlocked();
            return retval;
        }
        (void) k5_mutex_unlock(&g_shared_trees_mutex);
        data = profile_make_prf_data(expanded_filename);
        if (data == NULL) {
            free(prf);
            free(expanded_filename);
            return ENOMEM;
        }
        free(expanded_filename);
        prf->data = data;

        retval = k5_mutex_init(&data->lock);
        if (retval) {
            free(data);
            free(prf);
            return retval;
        }

        retval = profile_update_file(prf);
        if (retval) {
                profile_close_file(prf);
                return retval;
        }

        retval = k5_mutex_lock(&g_shared_trees_mutex);
        if (retval) {
            profile_close_file(prf);
            scan_shared_trees_unlocked();
            return retval;
        }
        scan_shared_trees_locked();
        data->flags |= PROFILE_FILE_SHARED;
        data->next = g_shared_trees;
        g_shared_trees = data;
        scan_shared_trees_locked();
        (void) k5_mutex_unlock(&g_shared_trees_mutex);

        *ret_prof = prf;
        return 0;
}

errcode_t profile_update_file_data(prf_data_t data)
{
        errcode_t retval;
#ifdef HAVE_STAT
        struct stat st;
        unsigned long frac;
        time_t now;
#endif
        FILE *f;

        retval = k5_mutex_lock(&data->lock);
        if (retval)
            return retval;

#ifdef HAVE_STAT
        now = time(0);
        if (now == data->last_stat && data->root != NULL) {
            k5_mutex_unlock(&data->lock);
            return 0;
        }
        if (stat(data->filespec, &st)) {
            retval = errno;
            k5_mutex_unlock(&data->lock);
            return retval;
        }
        data->last_stat = now;
#if defined HAVE_STRUCT_STAT_ST_MTIMENSEC
        frac = st.st_mtimensec;
#elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
        frac = st.st_mtimespec.tv_nsec;
#elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
        frac = st.st_mtim.tv_nsec;
#else
        frac = 0;
#endif
        if (st.st_mtime == data->timestamp
            && frac == data->frac_ts
            && data->root != NULL) {
            k5_mutex_unlock(&data->lock);
            return 0;
        }
        if (data->root) {
                profile_free_node(data->root);
                data->root = 0;
        }
        if (data->comment) {
                free(data->comment);
                data->comment = 0;
        }
#else
        /*
         * If we don't have the stat() call, assume that our in-core
         * memory image is correct.  That is, we won't reread the
         * profile file if it changes.
         */
        if (data->root) {
            k5_mutex_unlock(&data->lock);
            return 0;
        }
#endif
        errno = 0;
        /* Solaris Kerberos */
        f = fopen(data->filespec, "rF");
        if (f == NULL) {
                retval = errno;
                k5_mutex_unlock(&data->lock);
                if (retval == 0)
                        retval = ENOENT;
                return retval;
        }
        data->upd_serial++;
        data->flags &= PROFILE_FILE_SHARED;
        if (rw_access(data->filespec))
                data->flags |= PROFILE_FILE_RW;
        retval = profile_parse_file(f, &data->root);
        fclose(f);
        if (retval) {
            k5_mutex_unlock(&data->lock);
            return retval;
        }
        assert(data->root != NULL);
#ifdef HAVE_STAT
        data->timestamp = st.st_mtime;
        data->frac_ts = frac;
#endif
        k5_mutex_unlock(&data->lock);
        return 0;
}

static int
make_hard_link(const char *oldpath, const char *newpath)
{
#ifdef _WIN32
    return -1;
#else
    return link(oldpath, newpath);
#endif
}

static errcode_t write_data_to_file(prf_data_t data, const char *outfile,
                                    int can_create)
{
        FILE            *f;
        profile_filespec_t new_file;
        profile_filespec_t old_file;
        errcode_t       retval = 0;

        retval = ENOMEM;

        new_file = old_file = 0;
        new_file = malloc(strlen(outfile) + 5);
        if (!new_file)
                goto errout;
        old_file = malloc(strlen(outfile) + 5);
        if (!old_file)
                goto errout;

        sprintf(new_file, "%s.$$$", outfile);
        sprintf(old_file, "%s.bak", outfile);

        errno = 0;

        /* Solaris Kerberos */
        f = fopen(new_file, "wF");
        if (!f) {
                retval = errno;
                if (retval == 0)
                        retval = PROF_FAIL_OPEN;
                goto errout;
        }

        profile_write_tree_file(data->root, f);
        if (fclose(f) != 0) {
                retval = errno;
                goto errout;
        }

        unlink(old_file);
        if (make_hard_link(outfile, old_file) == 0) {
            /* Okay, got the hard link.  Yay.  Now we've got our
               backup version, so just put the new version in
               place.  */
            if (rename(new_file, outfile)) {
                /* Weird, the rename didn't work.  But the old version
                   should still be in place, so no special cleanup is
                   needed.  */
                retval = errno;
                goto errout;
            }
        } else if (errno == ENOENT && can_create) {
            if (rename(new_file, outfile)) {
                retval = errno;
                goto errout;
            }
        } else {
            /* Couldn't make the hard link, so there's going to be a
               small window where data->filespec does not refer to
               either version.  */
#ifndef _WIN32
            sync();
#endif
            if (rename(outfile, old_file)) {
                retval = errno;
                goto errout;
            }
            if (rename(new_file, outfile)) {
                retval = errno;
                rename(old_file, outfile); /* back out... */
                goto errout;
            }
        }

        data->flags = 0;
        if (rw_access(outfile))
                data->flags |= PROFILE_FILE_RW;
        retval = 0;

errout:
        if (new_file)
                free(new_file);
        if (old_file)
                free(old_file);
        return retval;
}

errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
{
        errcode_t       retval;
        retval = k5_mutex_lock(&data->lock);
        if (retval)
                return retval;
        retval = profile_write_tree_to_buffer(data->root, bufp);
        k5_mutex_unlock(&data->lock);
        return retval;
}

errcode_t profile_flush_file_data(prf_data_t data)
{
        errcode_t       retval = 0;

        if (!data || data->magic != PROF_MAGIC_FILE_DATA)
                return PROF_MAGIC_FILE_DATA;

        retval = k5_mutex_lock(&data->lock);
        if (retval)
            return retval;

        if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
            k5_mutex_unlock(&data->lock);
            return 0;
        }

        retval = write_data_to_file(data, data->filespec, 0);
        k5_mutex_unlock(&data->lock);
        return retval;
}

errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
{
    errcode_t retval = 0;

    if (!data || data->magic != PROF_MAGIC_FILE_DATA)
        return PROF_MAGIC_FILE_DATA;

    retval = k5_mutex_lock(&data->lock);
    if (retval)
        return retval;
    retval = write_data_to_file(data, outfile, 1);
    k5_mutex_unlock(&data->lock);
    return retval;
}



void profile_dereference_data(prf_data_t data)
{
    int err;
    err = k5_mutex_lock(&g_shared_trees_mutex);
    if (err)
        return;
    profile_dereference_data_locked(data);
    (void) k5_mutex_unlock(&g_shared_trees_mutex);
}
void profile_dereference_data_locked(prf_data_t data)
{
    scan_shared_trees_locked();
    data->refcount--;
    if (data->refcount == 0)
        profile_free_file_data(data);
    scan_shared_trees_locked();
}

int profile_lock_global()
{
    return k5_mutex_lock(&g_shared_trees_mutex);
}
int profile_unlock_global()
{
    return k5_mutex_unlock(&g_shared_trees_mutex);
}

void profile_free_file(prf_file_t prf)
{
    profile_dereference_data(prf->data);
    free(prf);
}

/* Call with mutex locked!  */
static void profile_free_file_data(prf_data_t data)
{
    scan_shared_trees_locked();
    if (data->flags & PROFILE_FILE_SHARED) {
        /* Remove from linked list.  */
        if (g_shared_trees == data)
            g_shared_trees = data->next;
        else {
            prf_data_t prev, next;
            prev = g_shared_trees;
            next = prev->next;
            while (next) {
                if (next == data) {
                    prev->next = next->next;
                    break;
                }
                prev = next;
                next = next->next;
            }
        }
    }
    if (data->root)
        profile_free_node(data->root);
    if (data->comment)
        free(data->comment);
    data->magic = 0;
    k5_mutex_destroy(&data->lock);
    free(data);
    scan_shared_trees_locked();
}

errcode_t profile_close_file(prf_file_t prf)
{
        errcode_t       retval;

        retval = profile_flush_file(prf);
        if (retval)
                return retval;
        profile_free_file(prf);
        return 0;
}