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
38struct 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
47static struct global_shared_profile_data krb5int_profile_shared_data = {
48    0,
49    K5_MUTEX_PARTIAL_INITIALIZER
50};
51
52MAKE_INIT_FUNCTION(profile_library_initializer);
53MAKE_FINI_FUNCTION(profile_library_finalizer);
54
55int 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}
65void 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
82static 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
117static 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
141static 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
166prf_data_t
167profile_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
194errcode_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
307errcode_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
396static int
397make_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
406static 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
484errout:
485	if (new_file)
486		free(new_file);
487	if (old_file)
488		free(old_file);
489	return retval;
490}
491
492errcode_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
503errcode_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
524errcode_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
541void 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}
550void 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
559int profile_lock_global()
560{
561    return k5_mutex_lock(&g_shared_trees_mutex);
562}
563int profile_unlock_global()
564{
565    return k5_mutex_unlock(&g_shared_trees_mutex);
566}
567
568void profile_free_file(prf_file_t prf)
569{
570    profile_dereference_data(prf->data);
571    free(prf);
572}
573
574/* Call with mutex locked!  */
575static 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
606errcode_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