Path: blob/main/crypto/krb5/src/util/profile/prof_file.c
34889 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* prof_file.c ---- routines that manipulate an individual profile file.3*/45#include "prof_int.h"67#include <stdio.h>8#ifdef HAVE_STDLIB_H9#include <stdlib.h>10#endif11#ifdef HAVE_UNISTD_H12#include <unistd.h>13#endif14#include <string.h>15#include <stddef.h>1617#include <sys/types.h>18#include <sys/stat.h>19#include <errno.h>2021#ifdef HAVE_PWD_H22#include <pwd.h>23#endif2425#if defined(_WIN32)26#include <io.h>27#define HAVE_STAT28#define stat _stat29#ifndef S_ISDIR30#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)31#endif32#endif3334#include "k5-platform.h"3536struct global_shared_profile_data {37/* This is the head of the global list of shared trees */38prf_data_t trees;39/* Lock for above list. */40k5_mutex_t mutex;41};42#define g_shared_trees (krb5int_profile_shared_data.trees)43#define g_shared_trees_mutex (krb5int_profile_shared_data.mutex)4445static struct global_shared_profile_data krb5int_profile_shared_data = {460,47K5_MUTEX_PARTIAL_INITIALIZER48};4950MAKE_INIT_FUNCTION(profile_library_initializer);51MAKE_FINI_FUNCTION(profile_library_finalizer);5253int profile_library_initializer(void)54{55#ifdef SHOW_INITFINI_FUNCS56printf("profile_library_initializer\n");57#endif58add_error_table(&et_prof_error_table);5960return k5_mutex_finish_init(&g_shared_trees_mutex);61}62void profile_library_finalizer(void)63{64if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING()) {65#ifdef SHOW_INITFINI_FUNCS66printf("profile_library_finalizer: skipping\n");67#endif68return;69}70#ifdef SHOW_INITFINI_FUNCS71printf("profile_library_finalizer\n");72#endif73k5_mutex_destroy(&g_shared_trees_mutex);7475remove_error_table(&et_prof_error_table);76}7778static void profile_free_file_data(prf_data_t);7980static int rw_access(const_profile_filespec_t filespec)81{82#ifdef HAVE_ACCESS83if (access(filespec, W_OK) == 0)84return 1;85else86return 0;87#else88/*89* We're on a substandard OS that doesn't support access. So90* we kludge a test using stdio routines, and hope fopen91* checks the r/w permissions.92*/93FILE *f;9495f = fopen(filespec, "r+");96if (f) {97fclose(f);98return 1;99}100return 0;101#endif102}103104static int r_access(const_profile_filespec_t filespec)105{106#ifdef HAVE_ACCESS107if (access(filespec, R_OK) == 0)108return 1;109else110return 0;111#else112/*113* We're on a substandard OS that doesn't support access. So114* we kludge a test using stdio routines, and hope fopen115* checks the r/w permissions.116*/117FILE *f;118119f = fopen(filespec, "r");120if (f) {121fclose(f);122return 1;123}124return 0;125#endif126}127128int profile_file_is_writable(prf_file_t profile)129{130if (profile && profile->data) {131return rw_access(profile->data->filespec);132} else {133return 0;134}135}136137prf_data_t138profile_make_prf_data(const char *filename)139{140prf_data_t d;141size_t len, flen, slen;142char *fcopy;143144flen = strlen(filename);145slen = offsetof(struct _prf_data_t, filespec);146len = slen + flen + 1;147if (len < sizeof(struct _prf_data_t))148len = sizeof(struct _prf_data_t);149d = malloc(len);150if (d == NULL)151return NULL;152memset(d, 0, len);153fcopy = (char *) d + slen;154assert(fcopy == d->filespec);155strlcpy(fcopy, filename, flen + 1);156d->refcount = 1;157d->magic = PROF_MAGIC_FILE_DATA;158d->root = NULL;159d->next = NULL;160d->fslen = flen;161if (k5_mutex_init(&d->lock) != 0) {162free(d);163return NULL;164}165return d;166}167168errcode_t profile_open_file(const_profile_filespec_t filespec,169prf_file_t *ret_prof, char **ret_modspec)170{171prf_file_t prf;172errcode_t retval;173char *home_env = 0;174prf_data_t data;175char *expanded_filename;176177retval = CALL_INIT_FUNCTION(profile_library_initializer);178if (retval)179return retval;180181prf = malloc(sizeof(struct _prf_file_t));182if (!prf)183return ENOMEM;184memset(prf, 0, sizeof(struct _prf_file_t));185prf->magic = PROF_MAGIC_FILE;186187if (filespec[0] == '~' && filespec[1] == '/') {188home_env = secure_getenv("HOME");189#ifdef HAVE_PWD_H190if (home_env == NULL) {191uid_t uid;192struct passwd *pw, pwx;193char pwbuf[BUFSIZ];194195uid = getuid();196if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)197&& pw != NULL && pw->pw_dir[0] != 0)198home_env = pw->pw_dir;199}200#endif201}202if (home_env) {203if (asprintf(&expanded_filename, "%s%s", home_env,204filespec + 1) < 0)205expanded_filename = 0;206} else207expanded_filename = strdup(filespec);208if (expanded_filename == 0) {209free(prf);210return ENOMEM;211}212213k5_mutex_lock(&g_shared_trees_mutex);214for (data = g_shared_trees; data; data = data->next) {215if (!strcmp(data->filespec, expanded_filename)216/* Check that current uid has read access. */217&& r_access(data->filespec))218break;219}220if (data) {221data->refcount++;222data->last_stat = 0; /* Make sure to stat when updating. */223k5_mutex_unlock(&g_shared_trees_mutex);224retval = profile_update_file_data(data, NULL);225free(expanded_filename);226if (retval) {227profile_dereference_data(data);228free(prf);229return retval;230}231prf->data = data;232*ret_prof = prf;233return 0;234}235k5_mutex_unlock(&g_shared_trees_mutex);236data = profile_make_prf_data(expanded_filename);237if (data == NULL) {238free(prf);239free(expanded_filename);240return ENOMEM;241}242free(expanded_filename);243prf->data = data;244245retval = profile_update_file(prf, ret_modspec);246if (retval) {247profile_close_file(prf);248return retval;249}250251k5_mutex_lock(&g_shared_trees_mutex);252data->flags |= PROFILE_FILE_SHARED;253data->next = g_shared_trees;254g_shared_trees = data;255k5_mutex_unlock(&g_shared_trees_mutex);256257*ret_prof = prf;258return 0;259}260261prf_file_t profile_open_memory(void)262{263struct profile_node *root = NULL;264prf_file_t file = NULL;265prf_data_t data;266267file = calloc(1, sizeof(*file));268if (file == NULL)269goto errout;270file->magic = PROF_MAGIC_FILE;271272if (profile_create_node("(root)", NULL, &root) != 0)273goto errout;274275data = profile_make_prf_data("");276if (data == NULL)277goto errout;278279data->root = root;280data->flags = PROFILE_FILE_NO_RELOAD | PROFILE_FILE_DIRTY;281file->data = data;282file->next = NULL;283return file;284285errout:286free(file);287if (root != NULL)288profile_free_node(root);289return NULL;290}291292errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec)293{294errcode_t retval;295#ifdef HAVE_STAT296struct stat st;297unsigned long frac;298time_t now;299#endif300FILE *f;301int isdir = 0;302303/* Don't reload if the backing file isn't a regular file. */304if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL)305return 0;306/* Don't reload a modified data object, as the modifications may be307* important for this object's use. */308if (data->flags & PROFILE_FILE_DIRTY)309return 0;310311#ifdef HAVE_STAT312now = time(0);313if (now == data->last_stat && data->root != NULL) {314return 0;315}316if (stat(data->filespec, &st)) {317return errno;318}319data->last_stat = now;320#if defined HAVE_STRUCT_STAT_ST_MTIMENSEC321frac = st.st_mtimensec;322#elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC323frac = st.st_mtimespec.tv_nsec;324#elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC325frac = st.st_mtim.tv_nsec;326#else327frac = 0;328#endif329if (st.st_mtime == data->timestamp330&& frac == data->frac_ts331&& data->root != NULL) {332return 0;333}334if (data->root) {335profile_free_node(data->root);336data->root = 0;337}338339/* Only try to reload regular files, not devices such as pipes. */340if ((st.st_mode & S_IFMT) != S_IFREG)341data->flags |= PROFILE_FILE_NO_RELOAD;342#else343/*344* If we don't have the stat() call, assume that our in-core345* memory image is correct. That is, we won't reread the346* profile file if it changes.347*/348if (data->root) {349return 0;350}351#endif352353#ifdef HAVE_STAT354isdir = S_ISDIR(st.st_mode);355#endif356if (!isdir) {357errno = 0;358f = fopen(data->filespec, "r");359if (f == NULL)360return (errno != 0) ? errno : ENOENT;361set_cloexec_file(f);362}363364data->upd_serial++;365366if (isdir) {367retval = profile_process_directory(data->filespec, &data->root);368} else {369retval = profile_parse_file(f, &data->root, ret_modspec);370(void)fclose(f);371}372if (retval) {373return retval;374}375assert(data->root != NULL);376#ifdef HAVE_STAT377data->timestamp = st.st_mtime;378data->frac_ts = frac;379#endif380return 0;381}382383errcode_t profile_update_file_data(prf_data_t data, char **ret_modspec)384{385errcode_t retval;386387k5_mutex_lock(&data->lock);388retval = profile_update_file_data_locked(data, ret_modspec);389k5_mutex_unlock(&data->lock);390return retval;391}392393static int394make_hard_link(const char *oldpath, const char *newpath)395{396#ifdef _WIN32397return -1;398#else399return link(oldpath, newpath);400#endif401}402403static errcode_t write_data_to_file(prf_data_t data, const char *outfile,404int can_create)405{406FILE *f;407profile_filespec_t new_file;408profile_filespec_t old_file;409errcode_t retval = 0;410411retval = ENOMEM;412413new_file = old_file = 0;414if (asprintf(&new_file, "%s.$$$", outfile) < 0) {415new_file = NULL;416goto errout;417}418if (asprintf(&old_file, "%s.bak", outfile) < 0) {419old_file = NULL;420goto errout;421}422423errno = 0;424425f = fopen(new_file, "w");426if (!f) {427retval = errno;428if (retval == 0)429retval = PROF_FAIL_OPEN;430goto errout;431}432433set_cloexec_file(f);434profile_write_tree_file(data->root, f);435if (fclose(f) != 0) {436retval = errno;437goto errout;438}439440unlink(old_file);441if (make_hard_link(outfile, old_file) == 0) {442/* Okay, got the hard link. Yay. Now we've got our443backup version, so just put the new version in444place. */445if (rename(new_file, outfile)) {446/* Weird, the rename didn't work. But the old version447should still be in place, so no special cleanup is448needed. */449retval = errno;450goto errout;451}452} else if (errno == ENOENT && can_create) {453if (rename(new_file, outfile)) {454retval = errno;455goto errout;456}457} else {458/* Couldn't make the hard link, so there's going to be a459small window where data->filespec does not refer to460either version. */461#ifndef _WIN32462sync();463#endif464if (rename(outfile, old_file)) {465retval = errno;466goto errout;467}468if (rename(new_file, outfile)) {469retval = errno;470rename(old_file, outfile); /* back out... */471goto errout;472}473}474475retval = 0;476477errout:478if (new_file)479free(new_file);480if (old_file)481free(old_file);482return retval;483}484485errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)486{487errcode_t retval;488489k5_mutex_lock(&data->lock);490retval = profile_write_tree_to_buffer(data->root, bufp);491k5_mutex_unlock(&data->lock);492return retval;493}494495errcode_t profile_flush_file_data(prf_data_t data)496{497errcode_t retval = 0;498499if (!data || data->magic != PROF_MAGIC_FILE_DATA)500return PROF_MAGIC_FILE_DATA;501502/* Do nothing if this data object has no backing file. */503if (*data->filespec == '\0')504return 0;505506k5_mutex_lock(&data->lock);507508if ((data->flags & PROFILE_FILE_DIRTY) == 0) {509k5_mutex_unlock(&data->lock);510return 0;511}512513retval = write_data_to_file(data, data->filespec, 0);514data->flags &= ~PROFILE_FILE_DIRTY;515k5_mutex_unlock(&data->lock);516return retval;517}518519errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)520{521errcode_t retval = 0;522523if (!data || data->magic != PROF_MAGIC_FILE_DATA)524return PROF_MAGIC_FILE_DATA;525526k5_mutex_lock(&data->lock);527retval = write_data_to_file(data, outfile, 1);528k5_mutex_unlock(&data->lock);529return retval;530}531532533534void profile_dereference_data(prf_data_t data)535{536k5_mutex_lock(&g_shared_trees_mutex);537profile_dereference_data_locked(data);538k5_mutex_unlock(&g_shared_trees_mutex);539}540void profile_dereference_data_locked(prf_data_t data)541{542data->refcount--;543if (data->refcount == 0)544profile_free_file_data(data);545}546547void profile_lock_global(void)548{549k5_mutex_lock(&g_shared_trees_mutex);550}551void profile_unlock_global(void)552{553k5_mutex_unlock(&g_shared_trees_mutex);554}555556prf_file_t profile_copy_file(prf_file_t oldfile)557{558prf_file_t file;559560file = calloc(1, sizeof(*file));561if (file == NULL)562return NULL;563file->magic = PROF_MAGIC_FILE;564565/* Shared data objects can just have their reference counts incremented. */566if (oldfile->data->flags & PROFILE_FILE_SHARED) {567profile_lock_global();568oldfile->data->refcount++;569profile_unlock_global();570file->data = oldfile->data;571return file;572}573574/* Otherwise we need to copy the data object. */575file->data = profile_make_prf_data(oldfile->data->filespec);576if (file->data == NULL) {577free(file);578return NULL;579}580k5_mutex_lock(&oldfile->data->lock);581file->data->flags = oldfile->data->flags;582file->data->last_stat = oldfile->data->last_stat;583file->data->frac_ts = oldfile->data->frac_ts;584file->data->root = profile_copy_node(oldfile->data->root);585k5_mutex_unlock(&oldfile->data->lock);586if (file->data->root == NULL) {587profile_free_file(file);588return NULL;589}590591return file;592}593594void profile_free_file(prf_file_t prf)595{596profile_dereference_data(prf->data);597free(prf);598}599600/* Call with mutex locked! */601static void profile_free_file_data(prf_data_t data)602{603if (data->flags & PROFILE_FILE_SHARED) {604/* Remove from linked list. */605if (g_shared_trees == data)606g_shared_trees = data->next;607else {608prf_data_t prev, next;609prev = g_shared_trees;610next = prev->next;611while (next) {612if (next == data) {613prev->next = next->next;614break;615}616prev = next;617next = next->next;618}619}620}621if (data->root)622profile_free_node(data->root);623data->magic = 0;624k5_mutex_destroy(&data->lock);625free(data);626}627628errcode_t profile_close_file(prf_file_t prf)629{630errcode_t retval;631632retval = profile_flush_file(prf);633if (retval)634return retval;635profile_free_file(prf);636return 0;637}638639640