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 
profile_library_initializer(void)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 }
profile_library_finalizer(void)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 
rw_access(const_profile_filespec_t filespec)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 
r_access(const_profile_filespec_t filespec)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
profile_make_prf_data(const char * filename)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 
profile_open_file(const_profile_filespec_t filespec,prf_file_t * ret_prof)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 
profile_update_file_data(prf_data_t data)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
make_hard_link(const char * oldpath,const char * newpath)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 
write_data_to_file(prf_data_t data,const char * outfile,int can_create)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 
profile_flush_file_data_to_buffer(prf_data_t data,char ** bufp)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 
profile_flush_file_data(prf_data_t data)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 
profile_flush_file_data_to_file(prf_data_t data,const char * outfile)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 
profile_dereference_data(prf_data_t data)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 }
profile_dereference_data_locked(prf_data_t data)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 
profile_lock_global()559 int profile_lock_global()
560 {
561     return k5_mutex_lock(&g_shared_trees_mutex);
562 }
profile_unlock_global()563 int profile_unlock_global()
564 {
565     return k5_mutex_unlock(&g_shared_trees_mutex);
566 }
567 
profile_free_file(prf_file_t prf)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!  */
profile_free_file_data(prf_data_t data)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 
profile_close_file(prf_file_t prf)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